Infrastructure web redondée nginx et docker

Dans cet article, je vais configurer une infrastructure docker de répartition de charge qui va dispatcher le trafic vers 6 serveurs web nginx. Ces 7 serveurs sont tous hébergés sur des conteneurs docker.

Environnement

  • Ubuntu 20.10
  • Docker version 19.03.13

Attention : Toutes les commandes docker lancées ici le sont en tant qu’utilisateur root. Faites attention à ce que vous faites !

Infrastructure cible

Le schéma ci-dessous indique la configuration cible.

Infrastructure cible

Serveurs web nginx

La première étape consiste à créer un répertoire racine pour chaque serveur web. Ensuite, un répertoire de logs et un fichier index.html pour chaque serveur web afin de pouvoir les identifier facilement. Cette étape peut être réalisée manuellement. Pour simplifier le travail, j’ai crée le script create_env.sh dans le répertoire scripts du repo Github. L’arborescence finale doit être celle indiqué ci-dessous.

.
├── lb
│   └── logs
└── web
    ├── www1
    │   ├── index.html
    │   └── logs
    ├── www2
    │   ├── index.html
    │   └── logs
    ├── www3
    │   ├── index.html
    │   └── logs
    ├── www4
    │   ├── index.html
    │   └── logs
    ├── www5
    │   ├── index.html
    │   └── logs
    └── www6
        ├── index.html
        └── logs

Fichiers de configuration nginx

Afin que les serveurs web nginx fonctionnent correctement, il est nécessaire de leur fournir un fichier de configuration fonctionnel à placer dans le répertoire web. La création du fichier de configuration nginx sort du cadre de ce tutoriel, je ne vais pas en détailler chaque section.

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    access_log  /var/log/nginx/access.log combined;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    server {
        listen 80;

        root /usr/share/nginx/html;

        location /nginx_status {
            stub_status on;
            access_log   off;
        }
    }

    include /etc/nginx/conf.d/*.conf;
}

Répartiteur de charge nginx

C’est également un conteneur Docker Nginx qui effectuera la répartition de charge du trafic. Pour les serveurs web, le fichier de configuration par défaut était suffisant, mais ce n’est pas le cas pour le loadbalancer. Donc il va donc falloir créer un fichier de configuration spécifique, et le rendre accessible au conteneur. En effet, ce n’est pas conseillé de modifier la configuration directement dans le conteneur, car celle-ci serait perdue en cas de redémarrage de celui-ci.

Fichier de configuration nginx

Les lignes importantes à modifier/ajouter sont les lignes 6 à 12 et 18 à 23.

Les premières permettent d’indiquer les serveurs destination de la répartition de charge. si aucune option n’est ajoutée avant la liste des serveurs, ils vont être choisis en mode round robin, donc les uns après les autres dans la liste. Il est possible une répartition selon le nombre de connexion (option least_conn;) ou encore un hash IP en fonction de l’adresse IP source utilisée (option ip_hash;). Il est également possible d’assigner un poids à chaque serveur, en indiquant l’option weight=x sur la même ligne que la déclaration du serveur. Dans cet exemple, le serveur www1 sera choisi deux fois de suite pour recevoir du trafic

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server www1 weight=2;
        server www2;
        server www3;
        server www4;
        server www5;
        server www6;
    }

    server {
        listen 80 default_server;

        location / {
        	proxy_redirect      off;
	        proxy_set_header    X-Real-IP $remote_addr;
	        proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
	        proxy_set_header    Host $http_host;
            proxy_pass http://backend;
        }
        
    }

    access_log  /var/log/nginx/access.log combined;

    include /etc/nginx/conf.d/*.conf;

}

Ce fichier de configuration est mis dans un répertoire lb pour être utilisé par le conteneur.

Fichiers de configuration Docker

Une fois que tous les fichiers sont crées, on peut construire les conteneurs. Les conteneurs utilisés sont construits à l’aide du fichier Dockerfile suivant, situé à la racine du projet. Il seul fichier Dockerfile est suffisant pour toute l’infrastructure :

FROM nginx

# Timezone configuration
ENV TZ=Europe/Paris
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# Service start
CMD service nginx start && tail -F /var/log/nginx/error.log

La construction des conteneurs se fait à l’aide d’utilitaire docker-compose. Celui-ci a besoin du fichier Dockerfile ci-dessus ainsi que du fichier docker-compose.yml ci-dessous.

version: "3"
services:

# Webserver
  www1:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www1
    volumes:
     - "./web/www1:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www1/logs:/var/log/nginx"
    ports:
     - "8081:80"

  www2:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www2
    volumes:
     - "./web/www2:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www2/logs:/var/log/nginx"
    ports:
     - "8082:80"

  www3:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www3
    volumes:
     - "./web/www3:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www3/logs:/var/log/nginx"
    ports:
     - "8083:80"

  www4:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www4
    volumes:
     - "./web/www4:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www4/logs:/var/log/nginx"
    ports:
     - "8084:80"

  www5:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www5
    volumes:
     - "./web/www5:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www5/logs:/var/log/nginx"
    ports:
     - "8085:80"

  www6:
    # image: nginx
    build:
      context: .
      dockerfile: Dockerfile
    container_name: www6
    volumes:
     - "./web/www6:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www6/logs:/var/log/nginx"
    ports:
     - "8086:80"

# Loadbalancer
  lb:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: lb
    volumes:
     - "./lb/nginx.conf:/etc/nginx/nginx.conf"
     - "./lb/logs:/var/log/nginx"
    ports:
     - "80:80"
    depends_on:
      - "www1"
      - "www2"
      - "www3"
      - "www4"
      - "www5"
      - "www6"

Chaque section du fichier docker-compose.yml est composée de manière similaire.

Section web

  www1:                                           # <- Nom du conteneur
    build:
      context: .
      dockerfile: Dockerfile                      # <- Nom du fichier Dockerfile à utiliser
    container_name: www1                          # <- Nom du conteneur
    volumes:                                      # <- Fichiers locaux à monter sur le conteneur.
     - "./web/www1:/usr/share/nginx/html"
     - "./web/nginx.conf:/etc/nginx/nginx.conf"
     - "./web/www1/logs:/var/log/nginx"
    ports:                                        # <- Ports locaux à mapper sur le conteneur
     - "8081:80"

Section lb

La sous-section depends_on est différente entre les sections de configuration des serveurs web, et du répartiteur de charge. Cette section indique que le conteneur lb dépend des autres conteneurs pour son lancement. docker-compose va donc attendre que tous les autres conteneurs soient lancés pour lancer celui-ci.

  lb:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: lb
    volumes:
     - "./lb/nginx.conf:/etc/nginx/nginx.conf"
     - "./lb/logs:/var/log/nginx"
    ports:
     - "80:80"
    depends_on:
      - "www1"
      - "www2"
      - "www3"
      - "www4"
      - "www5"
      - "www6"

Lancement

Il est maintenant temps de lancer l’infrastructure de répartition de charge.

Pour commencer, il faut d’abord construire les conteneurs. Cela se fait dans le répertoire racine, celui qui contient les fichiers docker-compose et Dockerfile.

/home/fox/Documents/reverse-proxy-dev$ docker-compose build

Cette commande va créer tous les conteneurs à partir du fichier Dockerfile. Le résultat attendu pour chaque conteneur (web et lb) est indiqué ci-dessous.

Building www1
Step 1/4 : FROM nginx
 ---> 9beeba249f3e
Step 2/4 : ENV TZ=Europe/Paris
 ---> Running in 0d45f6bb7717
Removing intermediate container 0d45f6bb7717
 ---> f4d809c7f364
Step 3/4 : RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 ---> Running in 248d4e3c9b1c
Removing intermediate container 248d4e3c9b1c
 ---> f85bdcb9bba2
Step 4/4 : CMD service nginx start && tail -F /var/log/nginx/error.log
 ---> Running in ffd6e46db72e
Removing intermediate container ffd6e46db72e
 ---> 9b53cacf0745
Successfully built 9b53cacf0745
Successfully tagged reverse-proxy-dev_www1:latest

Une fois que les conteneurs ont été construits, on peut vérifier que ceux-ci existent bien à l’aide de la commande docker container ls -a.

 /home/fox/Documents/reverse-proxy-dev$ docker container ls -a
CONTAINER ID        IMAGE                    COMMAND                  CREATED              STATUS                        PORTS               NAMES
1f0352f4d108        reverse-proxy-dev_lb     "/bin/sh -c 'service…"   About a minute ago   Exited (137) 38 seconds ago                       lb
1d4086052e63        reverse-proxy-dev_www4   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www4
3270b84652a3        reverse-proxy-dev_www2   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www2
0e0912d226d0        reverse-proxy-dev_www6   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www6
37fc57d0d1f5        reverse-proxy-dev_www5   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www5
d1ed2871bd7e        reverse-proxy-dev_www1   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www1
277be1da9a60        reverse-proxy-dev_www3   "/bin/sh -c 'service…"   About a minute ago   Exited (137) 28 seconds ago                       www3

Finalement, on peut lancer l’infrastructure.

/home/fox/Documents/reverse-proxy-dev$ docker-compose up     
Starting www6 ... done
Starting www4 ... done
Starting www2 ... done
Starting www1 ... done
Starting www3 ... done
Starting www5 ... done
Starting lb   ... done
Attaching to www6, www1, www2, www3, www5, www4, lb
[ ... ]

Vérifications

Une fois les 7 conteneurs lancés, on peut vérifier qu’ils sont bien lancés :

 ~fox/Documents/reverse-proxy $ docker container ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                  NAMES
1f0352f4d108        reverse-proxy-dev_lb     "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:80->80/tcp     lb
1d4086052e63        reverse-proxy-dev_www4   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8084->80/tcp   www4
3270b84652a3        reverse-proxy-dev_www2   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8082->80/tcp   www2
0e0912d226d0        reverse-proxy-dev_www6   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8086->80/tcp   www6
37fc57d0d1f5        reverse-proxy-dev_www5   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8085->80/tcp   www5
d1ed2871bd7e        reverse-proxy-dev_www1   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8081->80/tcp   www1
277be1da9a60        reverse-proxy-dev_www3   "/bin/sh -c 'service…"   21 minutes ago      Up About a minute   0.0.0.0:8083->80/tcp   www3

On va également tester leur bon fonctionnement en se rendant aux l’URL http://127.0.0.1:8081 à http://127.0.0.1:8086 afin d’accéder en direct aux serveurs web www1 à www6. On devrait voir s’afficher les pages suivantes.

Finalement, l’url http://127.0.0.1 permet de visualiser le bon fonctionnement de l’infrastructure complète. En actualisant la plage plusieurs fois, on remarque bien que le serveur 1 (Hello 1) est joint 2 fois de suite, suivi par les 5 autres, puis le répartiteur de charge recommence à joindre le premier.

Conclusion

La prochaine étape est de mettre en place un monitoring de ces conteneurs. On générera du trafic vers le répartiteur de charge afin de vérifier que celui-ci répartit bien le trafic de manière équitable entre les serveurs (ou d’une autre manière, en fonction de la configuration).

Sources

Les fichiers de configuration et le script d’initialisation se trouvent sur mon repo github : https://github.com/n1c0x/reverse-proxy-dev

Installer, réinitialiser et désinstaller WSL (windows subsystem for linux)

Cet article explique comment installer, réinitialiser et désinstaller le sous-système Linux pour Windows (Windows Subsystem for Linux, WSL)

Continuer à lire « Installer, réinitialiser et désinstaller WSL (windows subsystem for linux) »

Compilation de kernel avec Windows Subsystem for Linux

Bash for Windows

Windows a intégré un outil (que je trouve formidable) dans sa mise à jour de l’année dernière: le Windows Subsystem for Linux, ou plus communément appelé Bash for Windows.

Continuer à lire « Compilation de kernel avec Windows Subsystem for Linux »