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.

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
- https://docs.docker.com/engine/reference/run/#clean-up—rm
- https://docs.docker.com/engine/reference/commandline/exec/
- http://nginx.org/en/docs/http/load_balancing.html