Devops

Tuto : Load balancer avec Nginx dans un unique container Docker

Ce tuto concerne la mise en place à titre d’illustration d’un load balancer pour en comprendre les principes, dans un seul container docker. Dans ce container docker, il ya aura Linux Debian et tous les programmes nécessaires, dont Nginx bien sûr, mais aussi Supervisor. En effet faire tourner dans un docker Nginx et plusieurs instances de serveur n’est pas très compatible, puisque Docker n’assure qu’un process, dans le cas du load balancer on a Nginx et trois instances de serveur.

Supervisor permet de gérer à l’instar de SystemD les process, c’est un programme qui manage les autres programmes.

On se procure l’image Docker de Debian Linux officiel

docker pull debian
docker run -it debian

Ensuite on installe les programmes nécessaire pour faire tourner le load balancer

// ici on installe dans le docker les programmes nécessaire
// mise à jour de la BDD des packages
apt update


apt install nginx python3 supervisor  curl

Configuration du fichier supervisord.conf

Tout d’abord nous allons créer deux répertoires, un pour stocker le fichier de configuration, et un pour les logs

mkdir -p /etc/supervisor/conf.d/
mkdir -p /var/log/supervisor/
// créons le fichier de configuration qui n'existe pas au début
nano /etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
user=root

[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/nginx_err.log
stdout_logfile=/var/log/supervisor/nginx_out.log

[program:python-server-1]
command=python3 /app/app.py 8001
directory=/app
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/python1_err.log
stdout_logfile=/var/log/supervisor/python1_out.log

[program:python-server-2]
command=python3 /app/app.py 8002
directory=/app
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/python2_err.log
stdout_logfile=/var/log/supervisor/python2_out.log

[program:python-server-3]
command=python3 /app/app.py 8003
directory=/app
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/python3_err.log
stdout_logfile=/var/log/supervisor/python3_out.log

Le fichier de Supervisor démarre les 3 serveurs python, nous allons faire le script de serveur Python

Script Python du serveur

// Création du répertoire de l'application

mkdir -p /app
nano /app/app.py

// code du fichier serveur minimal

#!/usr/bin/env python3
import http.server
import socketserver
import sys

# Get port from command line argument
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8001

class MyHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        response = f"""
        <h1>Hello from Server {PORT}</h1>
        <p>Server ID: {PORT}</p>
        <p>Path: {self.path}</p>
        """
        self.wfile.write(response.encode())

if __name__ == '__main__':
    with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
        print(f"Server running on port {PORT}")
        httpd.serve_forever()

Configuration de Nginx

events {
    worker_connections 1024;
}

http {
    # Define upstream servers (Python backends)
    upstream backend {
        server 127.0.0.1:8001;
        server 127.0.0.1:8002;
        server 127.0.0.1:8003;
        
        # Load balancing method options:
        # least_conn;     # Route to server with fewest active connections
        # ip_hash;        # Route based on client IP (sticky sessions)
        # Default is round-robin
    }
    
    # Health check configuration
    server {
        listen 80;
        
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # Connection and timeout settings
            proxy_connect_timeout 5s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
            
            # Health check
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        }
        
        # Health check endpoint
        location /health {
            access_log off;
            return 200 "OK\n";
            add_header Content-Type text/plain;
        }
    }
}

Lancement du serveur Nginx

comme on n'a pas systemd on lance en direct depuis Supervisor

sudo supervisord -c /etc/supervisor/conf.d/supervisord.conf

Normalement tous les services sont lancés, et vous pouvez faire un curl pour tester le serveur frontal

curl http://localhost

<h1>Hello from Server 8001</h1>
        <p>Server ID: 8001</p>
        <p>Path: /</p>


curl http://localhost

<h1>Hello from Server 8002</h1>
        <p>Server ID: 8002</p>
        <p>Path: /</p>

curl http://localhost

<h1>Hello from Server 8003</h1>
        <p>Server ID: 8003</p>
        <p>Path: /</p>

curl http://localhost

<h1>Hello from Server 8001</h1>
        <p>Server ID: 8001</p>
        <p>Path: /</p>

Là nous voyons quelque chose de très intéressant, on requête la même url, mais ce n’est pas le même serveur qui nous retourne la réponse. Vous y êtes !

Et maintenant?

Là nous avons un seul container docker, c’est de l’expérience de laboratoire. Le docker est là pour des raisons de convénience, nonobstant la plateforme (mac, linux ou Windows) vous pouvez faire les mêmes commandes.

Vous pouvez faire ce tuto dans WSL, dans Linux. Cependant voyons quels scénarios nous pouvons explorer

-Load balancing vers des serveurs dans d’autres containers docker au sein d’une même plateforme.

-load balancing vers des serveurs par leurs urls

ff

Installer NginX en tant que reverse proxy

C’est quoi Nginx?

Nginx est un serveur web de contenu statique. Mais il a d’autres fonctionnalités qui font son attrait, et parmi les plus souvent cités, le reverse proxy.

On a vu comment installer Nginx en tant que serveur web, mais on va faire du reverse proxy dans cet article.

C’est quoi un reverse proxy?

Habituellement que vous sollicitez une page web, votre navigateur va directement tutoyer le serveur Apache par exemple ou NodeJs. Par contre si vous mettez un reverse proxy, c’est le reverse proxy qui va être sollicité en premier, et passer la requête ensuite au serveur Apache. Il agit comme un intermédiaire.

Source Wikipedia

Pourquoi s’embête-t-on à mettre un intermédiaire entre le serveur et le navigateur? L’utilité la plus évidente est que c’est une solution pour exposer votre serveur NodeJS qui fonctionne sur le port 3000, ou votre serveur Python, tout en montrant au monde extérieur un port 80, qui est celui par défaut du Web.

Installation de Nginx

Pour faire ce tuto je vais utiliser digitalOcean, une Paas, très pratique et peu cher, à condition de détruire votre instance après usage, bref, pour apprendre le cloud et Docker par exemple c’est idéal.

Je vais créer un droplet Ubuntu avec autenthification par clé SSH, pour éviter d’avoir à me connecter à chaque fois que je vais en mode console.

sudo apt update

sudo apt install nginx

nginx -v

cette commande sert en fait à tester si nginx est bien installé (en plus d'avoir sa version)

Maintenant on va pouvoir démarrer Nginx.

sudo systemctl start nginx

Pour que Nginx puisse démmarer automatiquement au reboot, vous devriez le redémarrer avec la command esuivante :

sudo systemctl enable nginx

# pour checker l'état de Nginx
sudo systemctl status nginx
#il doit mentionner active

Casser le lien avec le fichier de configuration d’origine.

Comme nous allons utiliser Nginx en mode reverse proxy, nous allons casser le lien avec l’encien fichier de configuration et en établir un nouveau.

sudo unlink /etc/nginx/sites-enabled/default

# création d'un nouveau fichier de configuration
sudo vim /etc/nginx/sites-available/reverse-proxy

avec le contenu cidessous dans ce fichier:


server {
    listen 80;
    server_name localhost;
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

La configuration consiste en un bloc server {…} qui gère toutes les requêtes correspondant aux conditions spécifiées. Il contient les éléments suivants :

listen 80 : Le serveur écoute les requêtes HTTP entrantes sur le port 80.
server_name localhost : Le nom de domaine pour lequel ce bloc serveur est responsable. L’exemple utilise localhost pour illustrer le fonctionnement du reverse proxy .
location / {…} : La configuration pour le chemin d’URI spécifié. Ici, il correspond à toutes les requêtes (/).
proxy_pass http://127.0.0.1:8000 : Le serveur backend vers lequel NGINX redirige les requêtes. L’exemple utilise l’adresse locale sur le port 8000 (127.0.0.1:8000) comme backend de substitution. Dans un cas réel, utilisez l’URL du serveur backend approprié.
proxy_set_header : La directive permettant de définir des en-têtes HTTP pour la requête proxy. Ces en-têtes sont transmis au serveur backend et fournissent des informations supplémentaires sur la requête du client. L’exemple inclut l’en-tête Host, l’adresse IP du client et le schéma (HTTP ou HTTPS).
Remplacez le numéro de port, le nom du serveur et l’adresse du serveur backend par les données réelles. Cet exemple redirige toutes les requêtes faites à localhost vers l’adresse http://127.0.0.1:8000.

Lier le nouveau fichier de configuration

sudo ln -s /etc/nginx/sites-available/reverse-proxy /etc/nginx/sites-enabled/

#test nginx
sudo nginx -t

#redémarre Nginx
sudo systemctl restart nginx

Tester le reverse proxy, exemple avec Python

Créer un répertoire, faire un fichier index.html, et lancer le serveur web Python depuis ce même répertoire. Par exemple /var/www

<html>
<head>
  <title>NGINX backend</title>
</head>
<body>
  <h1>ça marche !</h1>
</body>
</html>

#lancer le serveur

python3 -m http.server 8000

Il se peut que Python ne soit pas installé, pour Debian faites : sudo apt install python3

Test avec Curl de cette page et accès à cette même page par un navigateur.

curl http://14.35.123.45/index.html

Cas d’une application NodeJS sur le port 3000

On applique la même stratégie, on modifie le fichier pour remplacer le port 8000 par 3000.

Explication reverse proxy avec Nginx en vidéo

DigitalOcean recouvrez l’accès à votre console de droplet

Si vous vouslez accéder à votre console et que l’écran tourne en boucle, c’est que sans doute vous avez touché au FireWall. Vous avez touché au FireWall à l’occasion de l’installation de Nginx.

Pour recouvrer l’accès à votre console, il faut utiliser la Recovery Console, pour y accéder, aller dans Access dans la page principale du droplet.

Après le démarrage de la Recovery Console, il vous sera demandé de rentrer le mot de passe du user root, le problème est que vous n’avez pas de mot de passe root si vous avez choisi d’accéder pas clé SSH.

Il vous faudra donc resetter votre mot de passe root, en choisissant l’option juste après la Recovery Console, Reset Root Password.

Vous voila prêt à changer la configuration du Firewall une fois loggé

ufw allow ssh

Installer Nginx sur Ubuntu sur DigitalOcean

Une fois que vous avez créé votre droplet, il est conseillé de créer un utilisateur sudoable, sinon faites les opérations suivantes avec l’utilisateur root.

Nous allons mettre à jour vos paquets et installer Nginx

sudo apt update
sudo apt install nginx

Si vous êtes sous root pas la peine de mettre sudo. Sudo permet d’élever les privilèges d’un utilisateur non root.

Paramétrage du firewall ufw (uncomplicated Fire Wall)

sudo ufw app list   // liste les applications connue par ufw

Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

Si on n’a pas encore configuré un SSL pour le serveur, on va utiliser le profil Nginx HTTP.

>ufw allow 'Nginx HTTP'

Afficher le status du firewall

>ufw status

Status: inactive    // si vous avez ça c'est que ufw n'est pas activé

faite la commande suivante:
>ufw enable

>ufw status

Status: active

To                         Action      From
--                         ------      ----
Nginx HTTP                 ALLOW       Anywhere                  
Nginx HTTP (v6)            ALLOW       Anywhere (v6)      

Vérifier que le serveur web tourne

A la fin de l’installation, Unbuntu démarre le serveur Nginx. Nous allons vérifier avec la commande

systemctl status nginx

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Sat 2024-07-27 21:06:47 UTC; 16min ago
       Docs: man:nginx(8)
    Process: 2697 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUC>
    Process: 2699 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 2701 (nginx)
      Tasks: 2 (limit: 509)
     Memory: 2.2M (peak: 2.3M)
        CPU: 16ms
     CGroup: /system.slice/nginx.service
             ├─2701 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             └─2702 "nginx: worker process"

Jul 27 21:06:47 ubuntu-s-1vcpu-512mb-10gb-ams3-01 systemd[1]: Starting nginx.service - A high performance web se>
Jul 27 21:06:47 ubuntu-s-1vcpu-512mb-10gb-ams3-01 systemd[1]: Started nginx.service - A high performance web ser>
lines 1-16/16 (END)

troisième ligne indique active running.

Important ! autorisez le SSH, sinon vous risquez de ne plus avoir accès à la console, si c’est le cas voici une solution

ufw allow ssh

Nous allons visiter dans le navigateur une page pour vérifier que Nginx fonctionne. Faite la commande pour connaitre l’IP qui doit vous afficher une page:

ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'

165.232.90.185
10.18.0.5
fe80::28e7:4eff:fee5:b7fe

//commande alternative
curl -4 icanhazip.com

Je passe par l’IP V4, et j’obtiens bien une page web, la page d’accueil de Nginx.

f

Mémento sur les github actions

Concept essentiels de github actions

  • Workflow : un process fait d’un ou plusieur jobs. Le workflow est un fichier YAML
  • job : un ensemble de steps exécuté sur un même runner, tournent en parallèle en général et spécifie l’OS
  • step : une tâche dans un job, peut être une action ou une commande shell., share filesystem
  • runner : le serveur qui exécute le workflow

Qu’est ce qu’un action?

Une action est un ensemble de code au sein d’un workflow. En savoir plus sur les actions (EN)

Dans le contexte de GitHub Actions, une action est une application personnalisée pour la plateforme GitHub Actions qui automatise une tâche spécifique ou un ensemble de tâches au sein d’un workflow. Les actions sont les éléments de base des workflows et peuvent être réutilisées dans plusieurs workflows et dépôts.

Points Clés sur les Actions

  1. Unités de Code Réutilisables : Les actions sont conçues pour être réutilisables. Vous pouvez les partager entre différents dépôts et avec la communauté GitHub.
  2. Préconstruites ou Personnalisées : Vous pouvez utiliser des actions préconstruites disponibles sur le GitHub Marketplace ou créer vos propres actions personnalisées adaptées à vos besoins spécifiques.
  3. Encapsulent des Tâches : Les actions encapsulent des tâches telles que le checkout du code, la configuration des environnements, l’exécution de tests et le déploiement des applications.
  4. Configurables : Les actions acceptent souvent des entrées et produisent des sorties, ce qui permet de les personnaliser et de les composer ensemble pour créer des workflows complexes.

Types d’Actions

  1. Actions JavaScript : Actions construites en utilisant Node.js qui peuvent exécuter du code JavaScript.
  2. Actions de Conteneur Docker : Actions qui s’exécutent dans un conteneur Docker, ce qui vous permet de les empaqueter avec toutes leurs dépendances.
  3. Actions Composites : Actions qui combinent plusieurs étapes en une seule action, définie en utilisant une série de commandes shell et d’autres actions.

Qu’est ce que actions/checkout@v3?

C’est une action prédéfinie de Github, qui clone un repository dans l’environnement du workflow.

Glossaire Github action:

logs

uses : est utlisé pour sélectionner une action déjà définie dans un docker, dans un repository.

name: le nom du workflow

on : écouteur d’événement

artifact

runs-on: spécifie le type de machine (ou « runner ») sur laquelle un job doit s’exécuter. Un runner est un serveur qui exécute les workflows définis dans vos fichiers YAML. Un runner peut être auto-hébergé.

Types de Runners Disponibles

GitHub Actions offre plusieurs types de runners hébergés (préconfigurés par GitHub) que vous pouvez utiliser :

  • ubuntu-latest : Une machine virtuelle exécutant la dernière version stable d’Ubuntu.
  • ubuntu-20.04 : Une machine virtuelle exécutant Ubuntu 20.04.
  • ubuntu-22.04 : Une machine virtuelle exécutant Ubuntu 22.04.
  • windows-latest : Une machine virtuelle exécutant la dernière version stable de Windows.
  • windows-2019 : Une machine virtuelle exécutant Windows Server 2019.
  • macos-latest : Une machine virtuelle exécutant la dernière version stable de macOS.
  • macos-11 : Une machine virtuelle exécutant macOS 11 (Big Sur).

steps:

Liens externes:

https://www.digitalocean.com/community/tech-talks/deploying-to-digitalocean-with-github-actions

Installer docker sur DigitalOcean Droplet avec Docker

Dans cet article on va voir comment déployer des container docker d’une application multicontainer dans un VPS à l’aide de Docker-compose. Sur DigitalOcean, la manipulation est plus facile que sur Azure ou AWS (le plus complexe)

Création du Droplet

Un droplet sur DigitalOcean (un prestataire Cloud abordable et pas trop compliqué à utiliser),

1/Créer un compte sur DigitalOcean

2/créer un droplet l’équivalent d’un VPS, choisir Ubuntu comme OS avec Docker préinstallé en vous aidant du Marketplace. Vous créerez par la même occasion un projet qui apparaitra dans la sidebar.

Ensuite vous choisissez le Datacenter le plus proche de chez vous, en France l’on choisira Amsterdam par exemple.

Dans la section « Choose an image« , cliquez sur l’onglet Marketplace, puis sur l’image Docker on Ubuntu, ici l’image représente une machine virtuelle bien sûr et pas une image Docker.

Ensuite choisissez les options les moins chères, dans Droplet Type, choisissez « Basic« , dans CPU options, choisissez « Regular« , et la formule au mois la moins chère.

Dans « Choose Authentication Method« , choisissez Password pour faire simple, et SSH Key pour faire pratique (pas de mémorisation de mot de passe et login nécessaire). Allez sur ce post pour générer et mettre votre clé sur le service. Enfin cliquez sur « Create Droplet ». Au bout d’un certain temps votre droplet sera créée avec Docker installé, cependant Docker-Compose ne sera pas encore installé. Il vous faudra faire via la console.

Si vous voulez installer à la main Docker, les instruction sont sur cette page :

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04

Cloner votre projet depuis Github

Pour vous connecter en console dans votre Droplet dans la sidebar « Projects« , cliquez sur votre projet

Installer docker-compose

Une fois dedans cliquez sur « Console« , cela ouvrira un shell. Installez docker-compose

///sudo apt-get install docker-compose-plugin

ou tapez docker-compose, une erreur avec un message sur comment installer docker-compose s'affichera.
apt install docker-compose

Vous êtes en connecté en root, allez à /home et cloner le projet. Le projet consiste en un front end en ReactJS, un backend en Node/Express et de MongoDB. Ce dernier est directement tiré du hub de Docker. Alors que le front et le back vont être transformé en image via leurs Dockerfiles respectifs.

Clonage du projet depuis Github

/home# git clone git@github.com:refschool/vidly.git

On clone soit en SSH car Github ne supporte plus l’authentification par login et mot de passe. ou alors il faut utiliser un PAT personal access token. L’ennui avec la clé SSH est qu’il faut en créer une sur le droplet et la mettre sur Github, cela fait beaucoup de manipulations. Le mieux est de créer le PAT sur Github. Il s’utilise comme un mot de passe.

git clone https://<PAT>@github.com/refschool/vidly.git

Copie du fichier docker-compose-prod.yml depuis votre poste vers votre droplet

A ce stade il n’y a pas grand d’installé ni de configuré pour votre droplet (pas de FTP etc). Le moyen le plus simple d’envoyer un fichier vers le droplet est d’utiliser l’utilitaire rsync pour envoyer ce fichier. Mais le problème est que ce n’est pas simple d’avoir rsync sur Windows (pas un problème sur Linux). On va utiliser scp depuis le terminal DOS.

scp -i C:\Users\admin\.ssh\id_rsa E:\mosh\vidly\docker-compose-prod.yml root@188.166.87.69:/home/

Update :

Si le fichier docker-compose-prod.yml est dans le git, pas besoin de faire cette manipulation finalement.

Démarrage du docker-compose avec le fichier de prod

Dans la racine du projet où il y a le fichier docker-compose-prod.yml

docker-compose -f docker-compose-prod.yml up -d

Cette unique ligne va créer ou importer les images, démarrer les containers et in fine votre application.

Accès à l’application sur DigitalOcean

Si tout s’est bien passé, vous allez pouvoir accéder à votre application sur le droplet à l’adresse IP que vous pouvez retrouver dans votre interface chez DigitalOcean, sur le port 3000.

Les problèmes rencontrés et leurs solutions

An accédant via l’adresse IP l’application, on rencontre une première erreur :

Problème de CORS

La preière fois que vous accédez à votre application, vous allez rencontrer ce message « Could not fetch movie »

Ceci est vraisemblablement dû à un problème de CORS (cross origin), si on ouvre les devtools, onglet Réseau, en inspectant la requête, vous aurez les indices. La requête est faite à http://localhost:3001/api alors que notre serveur possède une adresse IP (voir image)

En effet en phase de développement on a http://localhost:3001/api (voir le fichier src/services/api.js) , si la variable d’environnement REACT_APP_API_URL est settée, elle surchargera localhost.

const baseUrl = process.env.REACT_APP_API_URL || "http://localhost:3001/api";

A la racine du projet, il y a un fichier docker-compose-prod.yml. Dans le Dockerfile du front il vous faudra remplacer dans le fichier Dockerfile après COPY

#Dockerfile
...
COPY . .
ENV REACT_APP_API_URL=http://adresse-ip-du-droplet:3001/api
...

Problème de droits de fichier

Vérifiez aussi que dans le dossier backend, les fichiers docker-entrypoint.sh et wait-for soient exécutables en faisant la commande chmod +x <fichier>.

Une fois que la commande docker-compose est faite, vous avez la main dans le shell, Inspectez les containers

docker ps

//Voir les logs d'un container
docker logs <id-container>

//Par exemple si le container du backend ne fonctionne pas correctement, lisez les logs.

//Pour arrêter
docker-compose down


//Pour relancer avec un build d'images

docker-compose -f docker-compose-prod.yml up -d --build
Retour en haut