Annexe 2: Première infrastructure de démonstration (2018)
- Description de l’infrastructure
- Utilisation de l’infrastructure de démonstration
- Mise en place de l’infrastructure
- Outils de monitoring
- Évolution de l’infrastructure au cours du temps
- Résolution de problèmes sur l’infrastructure
- Tous les conteneurs sont instanciés sur un seul des 2 workers
- Un seul replica du proxy des workers a été instancié
- Aucun conteneur ne se lance à part les conteneurs système (registry, portainer...)
- Le service du proxy des workers ne démarre pas
- Erreur 502 persistante
- Désactiver et réactiver un service
Note : Ce chapitre décrit la première infrastructure de démonstration et de développement basée sur Docker Swarm que j'ai mise en place. Cette démarche a depuis été remplacée par celle décrite au chapitre 3 lors de l'instanciation de la nouvelle infrastructure de démonstration de ce projet, et suite aux améliorations du moteur Docker et de mes compétences dans son utilisation.
Ce projet a énormément changer depuis la première version de l'infrastructure de démonstration mise en place en 2018-2019 détaillée en annexe :
- Intégration les nouvelles fonctionnalités offertes par les versions récente de Docker et en particulier au niveau du mode Swarm. Ces nouveautés simplifient énormément la gestion et la mise en place du cluster.
- Utilisation d'un registre docker privé non sécurisé afin d'éviter les difficultés liées à la mise en place du certificat SSL sur le cluster. Ce registre sera de plus complété par celui fourni par Gitlab afin d'assurer la centralisation des images.
- Abandon des configurations des services via systemd au profit des options de redémarrage des services intégrées à Docker Swarm et qui permettent plus de souplesse
- Abandon du proxy frontal nginx mis en place sur une machine virtuelle séparée au profit du load balancer Haproxy déployé par SIPR
Ancienne infrastructure "LVS" de 2018-2019
Ces infrastructures utilisent l'ancien load balancer de SIPR et nécessite des machines proxy supplémentaires.
Par exemple : infrastructure de démonstration de 2018-2019 et infrastructure de développement du portail de 2019
Nouvelle infrastructure "HAProxy"
Par exemple : seconde infrastructure de démonstration dckr.sisg et nouvelles infrastructures de développement, qa et production du portail
Description de l’infrastructure
L’infrastructure générale mise en œuvre est composée des éléments suivants :
- Proxy frontend : gère la connexion sécurisée aux applications depuis l’extérieur pour donner accès aux applications et services hébergées sur le cluster. Il joue également le rôle de répartiteur de charge et de firewall du cluster en limitant l’accès à des machines ou réseaux clients et l'accès à certains ports (80 et 443).
- Le cluster Docker Swarm proprement dit, composé de
- 1 ou plusieurs « workers » qui exécutent les conteneurs des différents services du cluster, l'infrastructure de démonstration en possède 2, mais d’autres peuvent être ajoutés « à chaud »
- 1 « manager » qui fournit les fonctionnalités suivantes
- L'orchestrateur des stacks et services du cluster (Docker Swarm)
- Le registre et dépôt des images Docker custom utilisées par les services et conteneurs (Docker Registry)
- Le partage de fichiers entre le « manager » et les « workers » via un serveur NFS
- Une application web de gestion du cluster (Portainer)
- L'exécution de tâches de gestion sur les différentes machines du cluster avec l’outil Ansible
- La construction des images Docker avec l’outil Docker Compose
- D'autres applications web de gestion ou de monitoring selon les besoins qui apparaîtront lors de l'utilisation du cluster
D'autres applications externes au cluster peuvent lui être connectées, par exemple
- l'authentification SSO via l'IDP Shibboleth UCLouvain
- un cluster de base de données (MySQL, Percona, PostgreSQL…)
- un serveur Gitlab pour la récupération du code des applications…
- …
Les machines du cluster
L'infrastructure de démonstration est mise en place dans le cloud Open Nebula géré par SIPR et est composée des éléments suivants :
- 1 machine virtuelle Debian GNU/Linux1 pour le manager du cluster – qui hébergent les conteneurs des services des applications ;
- 2 machines virtuelles Debian GNU/Linux pour les workers.
Load balancing et proxys
La répartition de charge et le reverse proxy se fait à deux niveaux dans l’infrastructure :
- Un niveau « externe » situé devant le cluster qui permet de gérer les certificats SSL, les règles spéciales de droits d’accès et les noms de domaine des applications. Il est chargé de répartir la charge entre les différentes machines du cluster. Il permet également d’accéder aux applications web de gestion et de monitoring du cluster.
- Un niveau « interne » situé dans le cluster qui permet de joindre les applications déployées sur ce dernier. Celui-ci utilise le DNS interne de Docker pour joindre les services des stacks applicatives. Il permet également de limiter les ports ouverts sur les machines du cluster et évite d’exposer tous les conteneurs à l’extérieur du cluster. Ce proxy est exposé au proxy extérieur via le port 80 et aux services du cluster via un réseau interne à Docker. Pour l’utiliser, les services devant être accédés depuis l’extérieur du cluster devront s’y connecter.
Les solutions de proxy et load balancing qui ont été envisagées dans le cadre de ce brevet sont :
- NGINX : solution de serveur web, reverse proxy et répartiteur de charge open source très légère et très performante
- Caddy : un serveur web open source très léger utilisant HTTPS par défaut
- Traefik : un reverse proxy / répartiteur de charge open source conçu pour faciliter le déploiement d’application en microservices
- HAProxy : une solution de proxy et répartiteur de charge performante
Solution | Externe | Interne |
---|---|---|
NGINX | oui | oui |
HAProxy | oui | non |
Traefik | (oui) | oui |
Caddy | oui | oui |
Ayant déjà une expérience de NGINX, j’ai préféré utiliser ce dernier pour l'infrastructure de démonstration, mais les deux autres solutions pourraient être envisagées pour une future infrastructure.
Remarque :
- En production, HAProxy (en cours de mise en place pour l’infrastructure SIPR) pourrait remplacer NGINX
- Docker Swarm effectue lui-même un routage interne ainsi qu’une répartition de charge entre les conteneurs (tâches) d’un service
SCHEMA PROXY FRONT END
Utilisation de l’infrastructure de démonstration
L’infrastructure de démonstration mise en place dans le cadre de ce brevet est utilisée activement depuis 2019 pour le déploiement de stacks applicatives dans le cadre du développement du nouveau portail UCLouvain en Drupal 9.
Voici par exemple les stacks déployées afin de fournir un site Drupal ainsi que l’authentification SAML (via Shibboleth) associée :
Une stack Drupal peut-être déployée pour chaque développement ou plateforme de démonstration spécifique. La stack SAML SP est commune à l’ensemble des stacks Drupal déployées.
En plus de ces stacks destinées au développement du portail, l’infrastructure héberge aussi d’autres stacks applicatives : blog, outils de développement, gestionnaire de base de données…
Cette infrastructure a également servi de modèle à une infrastructure similaire utilisée pour la mise en place d’un cluster ElasticSearch. Ce cluster est au cœur de la nouvelle application du service bibliothèques de l'UCLouvain et qui doit être mise en production début 2022 afin de succéder à VIRTUA.
Mise en place de l’infrastructure
1. Infrastructure de machines virtuelles dans Open Nebula
Mise en place de 4 machines virtuelles Debian dans OpenNebula par SIPR : 3 pour le cluster Docker Swarm, une pour le proxy frontal.
Configuration des machines :
- manager du Swarm : 4 cœurs, 16 Go RAM, 20Go pour le système, 40 Go pour /var/lib/docker 2, 20 Go pour le partage avec les nœuds /dockerdata-ceph
- workers du cluster : 4 cœurs, 16 Go RAM, 20 Go pour le système 3
- proxy Nginx : 4 cœurs, 8 Go RAM, 20 Go pour le système
2. Le stockage partagé
Pour le partage des fichiers des applications, un volume Ceph est monté sur le nœud manager à l’emplacement /dockerdata-ceph
.
Ce point de montage est partagé avec les hôtes depuis le manager via le protocole NFS.
Remarques :
- Le partage du point de montage via NFS depuis le manager plutôt que depuis l’infrastructure NFS fournie par SIPR a été préférée pour des raisons de performances.
- La gestion des permissions sur les fichiers est mise en place en utilisant les groupes POSIX et le système d'ACL4 de Debian.
- Le nombre de nœuds hôtes du cluster pourra être doublé en production si la charge le nécessite ou pour assurer une redondance des services en cas de perte d’un data center.
2.1. Configuration de la gestion via les ACL
sudo apt install acl
#check if acl supported by filesystem
sudo tune2fs -l /dev/vdd1
# set group as "sticky"
sudo chmod g+s docker /dockerdata-ceph
# set group default acls
sudo setfacl -Rdm g:docker:rwx /dockerdata-ceph/
Dans cette configuration, le groupe docker
est appliqué automatiquement à tous les fichiers et répertoires et reçoit les droits rwx
sur ceux-ci afin que les conteneurs puissent lire et écrire sans problème sur le système de fichier partagé.
2.2. Configuration du partage NFS
Export du répertoire /dockerdata-ceph
via NFS depuis le manager /etc/exports
/dockerdata-ceph 10.1.4.64(rw,no_subtree_check,no_root_squash,async) 10.1.4.65(rw,no_subtree_check,no_root_squash,async)
Le choix du flag async
sur le serveur a été fait afin de fournir les performances nécessaires sur l'infrastructure. Cette option peut poser des problèmes lors de l’écriture des fichiers, mais ceux-ci sont mitigés par le fait qu’un seul hôte écrit à la fois sur le montage.
Montage sur les workers /etc/fstab
10.1.4.63:/dockerdata-ceph /dockerdata-ceph nfs defaults,noatime
sync
andasync
have different meanings for the two different situations.
sync
in the client context makes all writes to the file be committed to the server.async
causes all writes to the file to not be transmitted to the server immediately, usually only when the file is closed. So another host opening the same file is not going to see the changes made by the first host.Note that NFS offers "close to open" consistency meaning other clients cannot anyway assume that data in a file open by others is consistent (or frankly, that it is consistent at all if you do not use
nfslock
to add some form of synchronization between clients).
sync
in the server context (the default) causes the server to only reply to say the data was written when the storage backend actually informs it the data was written.async
in the server context gets the server to merely respond as if the file was written on the server irrespective of if it actually has written it. This is a lot faster but also very dangerous as data may have a problem being committed!In most cases,
async
on the client side andsync
on the server side. This offers a pretty consistent behaviour with regards to how NFS is supposed to work.
3. Installation et configuration de Docker et du Docker Swarm
L'installation de Docker sous Debian se fait en suivant la documentation officielle5.
Configuration de Docker dans le fichier /etc/default/docker
# Docker Upstart and SysVinit configuration file
#
# THIS FILE DOES NOT APPLY TO SYSTEMD
#
# Please see the documentation for "systemd drop-ins":
# https://docs.docker.com/engine/admin/systemd/
#
# Customize location of Docker binary (especially for development testing).
#DOCKERD="/usr/local/bin/dockerd"
# Use DOCKER_OPTS to modify the daemon startup options.
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
# If you need Docker to use an HTTP proxy, it can also be specified here.
#export http_proxy="http://127.0.0.1:3128/"
# This is also a handy place to tweak where Docker's temporary files go.
#export DOCKER_TMPDIR="/mnt/bigdrive/docker-tmp"
DOCKER_OPTS="--config-file=/etc/docker/daemon.json"
Options du daemon Docker dans /etc/docker/daemon.json
{
"storage-driver": "overlay2",
"graph": "/var/lib/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "2"
},
"debug": false
}
Installation de docker-py
sur les hôtes Docker
apt-get install python-pip
pip install docker-py
Initialisation du mode swarm sur le manager6 :
docker swarm init --advertise-addr <internal_server_ip>
Exemple :
$ docker swarm init --advertise-addr 192.168.99.100
Swarm initialized: current node (dxn1zf6l61qsb1josjja83ngz) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
192.168.99.100:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Le nom des nœuds peuvent être obtenus via :
docker node ls
Sur les workers, la commande suivante permet de rejoindre le cluster.
$ docker swarm join \
--token SWMTKN-1-49nj1cmql0jkz5s954yi3oex3nedyz0fb0xx14ie39trti4wxv-8vxv8rssmk743ojnwacrr2e7c \
192.168.99.100:2377
This node joined a swarm as a worker.
4. Portainer, Registry, Swarmpit et Docker-Compose
4.1. Installation de Portainer sur le manager
1ʳᵉ étape : instanciation du conteneur Portainer
docker volume create portainer_data
docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /opt/portainer-data:/data portainer/portainer
2ᵉ étape : lancement de Portainer via Systemd7
Configuration /etc/systemd/system/portainer.service
[Unit]
Description=Portainer
After=dkm_registry.service
Requires=docker.service
[Service]
ExecStartPre=/usr/bin/docker stop dkm_portainer
ExecStartPre=/usr/bin/docker rm dkm_portainer
ExecStart=/usr/bin/docker run -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /opt/portainer-data:/data --name dkm_portainer portainer/portainer
;ExecStart=/usr/bin/docker start dkm_portainer
ExecStop=/usr/bin/docker stop dkm_portainer
[Install]
WantedBy = multi-user.target
Activation du service
systemctl enable portainer.service
systemctl start portainer
systemctl status portainer
Remarque : cette configuration n’est plus optimale aujourd’hui, le lancement automatique du conteneur pouvant être géré directement par Docker.
Mise à jour de portainer
docker image pull portainer/portainer
docker kill dkm_portainer
docker rm dkm_portainer
docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /opt/portainer-data:/data --name dkm_portainer portainer/portainer
Configuration du proxy frontal
Une fois Portainer installé, il restera à définir un site dans le proxy frontal (voir plus loin) afin d'y avoir accès depuis l'extérieur des data center :
upstream portainer_backend {
server dkm-webapps.sipr-dc.ucl.ac.be:9000;
}
server {
listen 80;
listen [::]:80;
server_name portainer.apps.sisg.ucl.ac.be;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include /etc/nginx/uclinclude/ssl/apps-ssl.conf;
server_name portainer.apps.sisg.ucl.ac.be;
location / {
include /etc/nginx/uclinclude/access/apps.access.conf;
proxy_pass http://portainer_backend;
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 https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-NGINX-UPSTREAM dkm;
proxy_set_header Host $host;
}
}
4.2. Installation du registry
Sources :
- https://www.server-world.info/en/note?os=Debian_9&p=docker&f=6
- https://www.server-world.info/en/note?os=Debian_9&p=ssl
- https://docs.docker.com/registry/deploying/
1ʳᵉ étape : Création d’un certificat auto-signé pour les communications entre les nœuds du cluster et le registry.
Génération de la clé
root@dkm-webapps:/etc/ssl/private# openssl genrsa -aes128 -out dkm-webapps.key 2048
Generating RSA private key, 2048 bit long modulus
......................+++
........................................+++
e is 65537 (0x010001)
Enter pass phrase for dkm-webapps.key:
Verifying - Enter pass phrase for dkm-webapps.key:
Il faut ensuite retirer la pass phrase de la clé :
root@dkm-webapps:/etc/ssl/private# openssl rsa -in dkm-webapps.key -out dkm-webapps-nopass.key
Enter pass phrase for dkm-webapps.key:
writing RSA key
Génération du fichier de demande de certificat (CSR) :
root@dkm-webapps:/etc/ssl/private# openssl req -new -days 3650 -key dkm-webapps-nopass.key -out dkm-webapps-selfsigned.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:BE
State or Province Name (full name) [Some-State]:BW
Locality Name (eg, city) []:LLN
Organization Name (eg, company) [Internet Widgits Pty Ltd]:SISG
Organizational Unit Name (eg, section) []:^C
root@dkm-webapps:/etc/ssl/private# openssl req -new -days 3650 -key dkm-webapps-nopass.key -out dkm-webapps-selfsigned.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:BE
State or Province Name (full name) [Some-State]:BW
Locality Name (eg, city) []:LLN
Organization Name (eg, company) [Internet Widgits Pty Ltd]:UCLouvain
Organizational Unit Name (eg, section) []:SISG
Common Name (e.g. server FQDN or YOUR name) []:dkm-webapps.sipr-dc.ucl.ac.be
Email Address []:info-portail@uclouvain.be
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Création du certificat auto-signé :
root@dkm-webapps:/etc/ssl/private# openssl x509 -in dkm-webapps-selfsigned.csr -out dkm-webapps-selfsigned.crt -req -signkey dkm-webapps-nopass.key -days 3650
Signature ok
subject=C = BE, ST = BW, L = LLN, O = UCLouvain, OU = SISG, CN = dkm-webapps.sipr-dc.ucl.ac.be, emailAddress = info-portail@uclouvain.be
Getting Private key
Vérification :
root@dkm-webapps:/etc/ssl/private# ls
dkm-webapps.key dkm-webapps-nopass.key dkm-webapps-selfsigned.crt dkm-webapps-selfsigned.csr
2ᵉ étape : Installation du certificat
Sur le manager :
root@dkm-webapps:/etc/ssl/private# mkdir -p /etc/docker/certs.d/dkm-webapps.sipr-dc.ucl.ac.be:5000
root@dkm-webapps:/etc/ssl/private# cp /etc/ssl/private/dkm-webapps-selfsigned.crt /etc/docker/certs.d/dkm-webapps.sipr-dc.ucl.ac.be\:5000/ca.crt
Sur chacun des workers :
root@dkh-webapps[1-2]:/root# mkdir -p /etc/docker/certs.d/dkm-webapps.sipr-dc.ucl.ac.be:5000
root@dkh-webapps[1-2]:/root# cp dkm-webapps-selfsigned.crt /etc/docker/certs.d/dkm-webapps.sipr-dc.ucl.ac.be\:5000/ca.crt
3ᵉ étape : Installation du registry
Récupération de l’image :
root@dkm-webapps:/etc/ssl/private# docker pull registry:2
2: Pulling from library/registry
Digest: sha256:5a156ff125e5a12ac7fdec2b90b7e2ae5120fa249cf62248337b6d04abc574c8
Status: Image is up to date for registry:2
Création du répertoire pour le stockage des données du registry :
mkdir /var/lib/docker/registry
Lancement du registry :
root@dkm-webapps:/etc/ssl/private# docker run -d -p 5000:5000 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/dkm-webapps-selfsigned.crt -e REGISTRY_HTTP_TLS_KEY=/certs/dkm-webapps-nopass.key -v /etc/ssl/private:/certs -v /var/lib/docker/registry:/var/lib/registry registry:2
4ᵉ étape : Test du registry
Sur la machine manager :
root@dkm-webapps:/etc/ssl/private# docker pull debian:latest
root@dkm-webapps:/etc/ssl/private# docker tag debian dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg
root@dkm-webapps:/etc/ssl/private# docker push dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg
The push refers to repository [dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg]
f715ed19c28b: Pushed
latest: digest: sha256:bbb3345ed2e7548dc7a53385b724374ecfb166489a1066cc31b345d0d767df78 size: 529
root@dkm-webapps:/etc/ssl/private# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
drupal latest e4207c5bbdec 11 days ago 446MB
mariadb latest 67238b4c1da0 5 weeks ago 365MB
nginx latest dbfc48660aeb 6 weeks ago 109MB
dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg latest be2868bebaba 6 weeks ago 101MB
debian latest be2868bebaba 6 weeks ago 101MB
portainer/portainer latest 00ead811e8ae 2 months ago 58.7MB
registry 2 2e2f252f3c88 2 months ago 33.3MB
registry latest 2e2f252f3c88 2 months ago 33.3MB
root@dkm-webapps:/etc/ssl/private# curl -k https://localhost:5000/v2/_catalog
{"repositories":["debian_reg"]}
Sur les workers :
root@dkh-webapps1:~# docker pull dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg
Using default tag: latest
latest: Pulling from debian_reg
Digest: sha256:bbb3345ed2e7548dc7a53385b724374ecfb166489a1066cc31b345d0d767df78
Status: Downloaded newer image for dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg:latest
root@dkh-webapps1:~# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mariadb <none> 67238b4c1da0 5 weeks ago 365MB
nginx <none> dbfc48660aeb 6 weeks ago 109MB
dkm-webapps.sipr-dc.ucl.ac.be:5000/debian_reg latest be2868bebaba 6 weeks ago 101MB
debian <none> be2868bebaba 6 weeks ago 101MB
registry <none> 2e2f252f3c88 2 months ago 33.3MB
5ᵉ étape : Lancement du registry comme service Systemd
[Unit]
Description=Docker Registry
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/usr/bin/docker run -p 5000:5000 -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/dkm-webapps-selfsigned.crt -e REGISTRY_HTTP_TLS_KEY=/certs/dkm-webapps-nopass.key -v /etc/ssl/private:/certs -v /var/lib/docker/registry:/var/lib/registry --name dkm_registry --rm registry:2
[Install]
WantedBy = multi-user.target
Remarque : cette configuration n'est plus optimale aujourd’hui, le lancement automatique du conteneur pouvant être géré directement par Docker.
4.3. Installation du registry
Swarpit est un outil de gestion de cluster Docker Swarm fournissant un monitoring des performance du cluster. Son installation est plutôt simple et rapide grâce à un installateur :
docker run -it --rm \
--name swarmpit-installer \
--volume /var/run/docker.sock:/var/run/docker.sock \
swarmpit/install:1.9
L'installeur pose alors quelques questions permettant la personnalisation de l'outil (nom de la stack Docker Swarm, port sur lequel exposer le service, volume de stockage...). L’installation déploie également un agent sur chacun des noeuds du cluster.
Cet installeur ne permet pas de configurer le redémarrage automatique de la stack en cas de crash. Il faudra donc modifier cela via les outils en ligne de commande ou l'interface web de Portainer.
Il est également possible d'installer Swarpit manuellement en clonant son dépôt git et en adaptant le fichier yaml de description de la stack.
Une fois Swarmpit installé, il est nécessaire de configurer un site dans le proxy :
upstream swarmpit_backend {
server dkm-webapps.sipr-dc.ucl.ac.be:8088;
}
server {
listen 80;
listen [::]:80;
server_name swarmpit.apps.sisg.ucl.ac.be;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include /etc/nginx/uclinclude/ssl/apps-ssl.conf;
server_name swarmpit.apps.sisg.ucl.ac.be;
location / {
include /etc/nginx/uclinclude/access/apps.access.conf;
proxy_pass http://swarmpit_backend;
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 https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-NGINX-UPSTREAM dkm;
proxy_set_header Host $host;
}
}
4.4. Installation de docker-compose
Deux options :
- installation via Python-Pip
- installation manuelle :
sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
5. Proxy NGINX, SSL et templates
L’infrastructure possède deux niveaux de proxy. Tout d’abord, un proxy frontend est mis en place afin de gérer les connexions SSL, de diriger certaines requêtes vers une application ou ports spécifiques (par exemple applications de gestion sur le manager), de limiter l’accès à certaines applications à des plages d’IP spécifiques, de répartir la charge des applications déployées sur le Swarm entre les workers…
5.1. Proxy frontal
Le proxy frontal (frontend) est un serveur nginx qui a été mis en place en dehors du cluster afin de servir de répartiteur de charge et de point d’entrée des applications hébergées sur l'infrastructure.
Il remplit les rôles suivants :
- Répartition de charge entre les 2 workers du cluster
- Proxy vers les applications hébergées sur le manager du cluster
- Point d’entrée pour les connexions HTTPS pour les domaines *.apps.sisg.ucl.ac.be et *.portail.sisg.ucl.ac.be
- Limitation d’accès aux applications de gestion du cluster
- Isolation des machines du cluster afin d’éviter d’exposer les ports des applications directement au monde extérieur
- Gestion des noms de domaine spécifiques pour certaines applications
Ce service est hébergé sur une machine virtuelle Debian dédiée dans OpenNebula.
La répartition de charge et le failover de Nginx se base sur des pools de machines appelés upstream
. Voici la définition de l'upstream permettant l’accès aux applications via les proxies nginx des 2 workers :
# Upstream vers les 2 proxies des workers
upstream dkh_backend {
server dkh-webapps1.sipr-dc.ucl.ac.be:8080;
server dkh-webapps2.sipr-dc.ucl.ac.be:8080;
}
Voici un upstream permettant d’accéder à l’application Portainer sur le port 9000 du manager du cluster :
#
upstream portainer_backend {
server dkm-webapps.sipr-dc.ucl.ac.be:9000;
}
Certaines configurations ont été placées dans des fichiers inclus dans les définitions des sites :
/etc/nginx/uclinclude/ssl/apps-ssl.conf
: définit la configuration de la connection sécurisée SSL/TLS commune à tous les sites/etc/nginx/uclinclude/access/apps.access.conf
: définit les droits d'accès pour les applications de management (principalement du filtrage sur l'IP)
Exemples de configuration : domaine *.apps.sisg.ucl.ac.be
# generated 2020-12-14, Mozilla Guideline v5.6, nginx 1.10.3, OpenSSL 1.1.0l, intermediate configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.10.3&config=intermediate&openssl=1.1.0l&guideline=5.6
# Default server configuration
#
upstream dkh_backend {
server dkh-webapps1.sipr-dc.ucl.ac.be:8080;
server dkh-webapps2.sipr-dc.ucl.ac.be:8080;
}
server {
listen 80;
listen [::]:80;
server_name *.apps.sisg.ucl.ac.be apps.sisg.ucl.ac.be;
client_max_body_size 128M;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include /etc/nginx/uclinclude/ssl/apps-ssl.conf;
server_name *.apps.sisg.ucl.ac.be apps.sisg.ucl.ac.be;
client_max_body_size 128M;
location / {
include /etc/nginx/uclinclude/access/apps.access.conf;
include /etc/nginx/uclinclude/html/error_pages.conf;
proxy_pass http://dkh_backend;
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 https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-NGINX-UPSTREAM dkh;
proxy_set_header Host $host;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
}
Exemple de configuration : accès à Portainer
upstream portainer_backend {
server dkm-webapps.sipr-dc.ucl.ac.be:9000;
}
server {
listen 80;
listen [::]:80;
server_name portainer.apps.sisg.ucl.ac.be;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
include /etc/nginx/uclinclude/ssl/apps-ssl.conf;
server_name portainer.apps.sisg.ucl.ac.be;
location / {
include /etc/nginx/uclinclude/access/apps.access.conf;
proxy_pass http://portainer_backend;
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 https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-NGINX-UPSTREAM dkm;
proxy_set_header Host $host;
}
}
Notes :
- Pour ce proxy, il a fallu obtenir des certificats SSL avec wildcard. La méthode à utiliser pour créer le fichier CSR (Certificate Signing Request) se trouve dans les documents annexes.
- Les configurations SSL peuvent être générées facilement en se rendant sur le site https://ssl-config.mozilla.org/
- Les modèles et fichiers de configurationpour le proxy frontal se trouvent dans les documents techniques.
5.2. Proxy sur les workers
En plus du proxy frontal discuté plus haut, sur chaque nœud worker, un conteneur NGINX est déployé afin de diriger les requêtes vers le bon service et la bonne stack du Swarm.
Les services devant être accessibles depuis l’extérieur sont connectés sur un réseau docker de type overlay spécifique : UCLouvainProxy.
Cela ajoute une couche d’isolation en n’exposant vers l’extérieur que les ports nécessaires pour l'accès aux applications et pas ceux de l’ensemble des services d’une stack.
Le proxy des workers est un conteneur déployé via les commandes de gestion de Docker Swarm. Il est défini dans le fichier docker-compose-stack.yml :
version: "3.5"
services:
proxy:
image: dkm-webapps.sipr-dc.ucl.ac.be:5000/dkhproxy:latest
deploy:
replicas: 2
placement:
constraints:
- node.role!=manager
build:
context: .
cache_from:
- dkm-webapps.sipr-dc.ucl.ac.be:5000/dkhproxy:latest
env_file:
- dkh_proxy.env
ports:
- target: 80
published: 8080
protocol: tcp
mode: host
- target: 443
published: 8443
protocol: tcp
mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- type: bind
source: ./entrypoint.sh
target: /usr/local/bin/start_container.sh
- type: bind
source: ./uclouvain.d/
target: /etc/nginx/sites-enabled/
- type: bind
source: ./tpl
target: /templates
networks:
- UCLouvainProxy
networks:
UCLouvainProxy:
external: true
name: UCLouvainProxy
Afin de pouvoir automatiser le déploiement des applications, les configurations des sites accessibles via le proxy sont générés grâce à un système de templates simple basé sur la commande sed
.
Template de configuration de site :
# tpl/site.tpl
upstream ${site_name} {
server ${service_name};
}
server {
listen 80;
server_name ${site_name}.${host_name};
client_max_body_size 128M;
location / {
proxy_pass http://${site_name};
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 https;
proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-NGINX-UPSTREAM ${stack_name};
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
proxy_set_header Host $host;
}
}
Script de génération des configurations avec sed
:
#generate sites config
serverconf() {
NETWORK_NAME=UCLouvainProxy
PROXY_NETWORK_ID=$(docker network inspect -f {{.Id}} $NETWORK_NAME )
SERVICES=$(docker service ls --format {{.Name}})
EXCLUDE_SERVICE=frontend_proxy
HOST_NAME="apps.sisg.ucl.ac.be"
for SERVICE in $SERVICES
do
if [ $SERVICE != $EXCLUDE_SERVICE ]
then
NETWORK_INSPECT=$(docker service inspect $SERVICE)
if [ -n "$NETWORK_INSPECT" ]
then
NETWORKS_IDS=$( echo $NETWORK_INSPECT | jq -r '.[] | {Spec} | .[] | {TaskTemplate} | .[] | {Networks} | .[] | .[] | {Target} | .[]')
for NETWORK_ID in $NETWORKS_IDS
do
if [ $NETWORK_ID = $PROXY_NETWORK_ID ]
then
STACK_NAME=${SERVICE%"_webserver"}
sed -e "s/\${service_name}/$SERVICE/; s/\${site_name}/$STACK_NAME/; s/\${host_name}/$HOST_NAME/" ./tpl/site.tpl > ./apps.d/$SERVICE.conf
fi
done
fi
fi
done
reload
}
Les configurations de site sont générées dans le répertoire uclouvain.d
monté comme volume dans le conteneur du proxy.
Ces configurations sont chargées automatiquement par Nginx lors de son démarrage ou via la commande reload fournie par le script d’automatisation :
reload () {
echo "-- Reload Nginx Server"
stack-exec frontend_proxy service nginx reload
}
La commande stack-exec permet d’exécuter une tâche sur un conteneur sans savoir sur quelle machine du cluster il se trouve. Elle est décrite dans la partie « Évolution de l’infrastructure de démonstration »
Notes :
- Les fichiers de configuration du proxy des workers sont disponibles dans les documents techniques
6. Installation des outils d’automatisation (Ansible et Jenkins)
- installer Ansible sur le manager
- créer l'utilisateur Ansible (ajouter note sur l’échange de clé ssh pas encore fait)
- installer Jenkins + plugin Ansible puis dire qu’on ne l’utilise pas encore
Outils de monitoring
- Swarmpit : outil de management et de monitoring pour Docker Swarm
- Agrégation des logs des conteneurs avec Graylog
- Monitoring avec cadvisor, prometheus et grafana
Évolution de l’infrastructure au cours du temps
L’infrastructure a beaucoup changé depuis le développement initial du projet.
Ce qui est resté constant
- Cluster Docker Swarm de 3 hôtes
- Portainer pour la gestion via une interface web
- Un Registry docker
Ce qui a changé
L'architecture décrite ici a été testée et modifiée pour l’infrastructure de développement du futur portail en Drupal 8.
Au départ, elle se basait sur le build d’images Docker qui intégraient tous les fichiers sources des applications dans l’image et le chargement des dépendances (ici dépendances PHP via le gestionnaire de paquets Composer) devaient être intégrées soit durant le build, soit au démarrage de l’image. Le registry était utilisé pour sauvegarder les modifications successives faites dans les conteneurs et les images. Ces images étaient alors déployées via l’interface web de Portainer.
Dans un second temps, de l’automatisation a été ajoutée via l’utilisation de Ansible et de docker-py pour déployer les conteneurs. Dans cette configuration, toute modification des fichiers sources nécessitait un build des images et un redéploiement sur l’infrastructure. Cette procédure était complexe, lente, lourde et non-standard et pas tenable sur le long terme !
La solution que j’ai proposée a été de passer à l’usage de docker-compose et de monter le répertoire des applications via un volume à l’intérieur du conteneur. Cela se justifiait par le fait que c’est le standard de facto pour le déploiement des applications disponibles en ligne (sur Github et autres plateformes concurrentes).
Pour permettre le déploiement sur la stack Docker Swarm, deux fichiers de configuration docker-compose sont fournis pour chaque stack de services à déployer :
- le premier,
docker-compose.yml
, pour l’exécution en local, la préparation des images et leur publication sur le registry via les sous-commandes dedocker-compose
- le second,
docker-compose-stack.yml
, contient en plus les paramètres nécessaires (contraintes de déploiement, réseaux overlay, utilisation du registry privé…) au déploiement dans Docker Swarm via les sous-commandes dedocker stack
Cela permet de conserver la possibilité d’un déploiement local sans nécessiter, de cluster Docker Swarm, pour le test et le développement…
Une série de scripts bash sont utilisées afin de faciliter encore le déploiement et, surtout, l’exécution de commandes sur un service de la stack déployée via ssh. En effet, il n’est pas possible de savoir sur quel worker du cluster se trouve un service a priori.
Le cœur de la commande est le code ci-dessous :
stack-exec()
{
if [ -z $SERVICENAME ]; then
echo "Missing service name. Abort"
usage
exit 1
fi
SERVICE="${STACKNAME}_${SERVICENAME}"
set -e
echo "-- Execute task on service $SERVICE"
SERVICE_IDS=$(docker service ps $SERVICE --format "{{.Name}}.{{.ID}}" -q --no-trunc -f "DESIRED-STATE=running")
for SERVICE_ID in $SERVICE_IDS
do
NODE_ID=$(docker inspect -f "{{.NodeID}}" ${SERVICE_ID})
NODE_IP=$(docker inspect -f {{.Status.Addr}} ${NODE_ID})
echo "-- Execute task ${@} on container $SERVICE_ID deployed on $NODE_IP "
# here document
ssh -T ${NODE_IP} << EOSSH
docker exec $SERVICE_ID ${@}
EOSSH
done
}
Problèmes et solutions
En cas de redémarrage de l’infrastructure
Si le système de fichiers NFS n’est pas monté, le cluster n’est pas capable de relancer les services.
Après un crash de l’infrastructure :
Il faut relancer le service NFS sur la manager
sudo systemctl restart nfs-kernel-server nfs-server
Ensuite sur chaque worker, il faut remonter le système de fichiers partagé :
sudo mount /dockerdata-ceph
Puis relancer Docker :
sudo systemctl restart docker
De plus, Portainer perd les informations des workers sur son dashboard, il faut donc mettre à jour les Endpoint via l’interface web.
Il faudra automatiser cette procédure.
Configuration des conteneurs
-
timezone
- via DockerFile :
ENV TZ="Europe/Brussels"
- via DockerFile :
-
via docker-compose :
environment: - TZ="Europe/Brussels"
-
logs de nginx et php-fpm
-
config buffer nginx :
proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k;
Dépendance entre conteneurs
via docker-compose :
services:
web:
# ...
depends_on:
- "php-fpm"
- "database"
utilisation d’un script pour attendre la disponibilité d’un service :
services:
web:
# ...
command: ["./wait-for-it.sh", "php-fpm:9000", "--", "nginx", "-g", "daemon off;"]
Configurations diverses
Par défaut, certaines configurations de nginx ne sont pas suffisantes. C’est par exemple le cas de la taille maximum des requêtes acceptées par nginx via la directive client_body_max_size
et dont la valeur par défaut est de 2Mo. Cela signifie que quelles que soient les limites de taille d'upload de fichiers fixés par les applications, les proxies nginx (frontend ou dans les stacks) ainsi que les services nginx des stacks elles-mêmes, empêcheront tout upload d’un fichier de taille supérieure à 2Mo.
Il est très simple de résoudre le problème en ajoutant la directive client_body_max_size
dans les configurations des sites. Pour l'infrastructure, elle a été fixée à 128Mo pour les sites Drupal ou hébergeant un autre CMS, mais elle pourra être modifiée si nécessaire en production.
Ce qu’il reste à faire
Automatiser l’instanciation du cluster et l’ajout de nœud workers supplémentaires avec Ansible. Cette question sera traitée dans la section « Automatisation de l’instanciation du cluster avec Ansible ».
Automatiser le déploiement d’application :
- Utiliser la composition de fichiers docker-compose pour alléger la configuration des stacks
- Automatiser le déploiement des stacks avec ansible afin de pouvoir l’exécuter depuis une application distante (par exemple Jenkins, Gitlab, ou via des webhooks…)
Ces questions seront traitées dans le chapitre « Livraison continue et déploiement automatique d’applications sur le cluster ».
Gérer la perte du manager.
La perte du manager provoque actuellement un downtime de l’ensemble de l’infrastructure car
- On perd la machine qui assure la gestion du cluster.
- On perd le partage du système de fichiers NFS entre les nœuds du cluster. Si le manager tombe, les workers perdront donc l’accès à leurs données.
Cette question sera abordée plus en détail dans le chapitre des perspectives consacré à la mise en place d'une infrastructure de production.
Monter les volumes NFS directement dans les conteneneurs
Mettre en place des healthchecks spécifiques pour certains conteneurs (Drupal...).
Automatiser l'activation des applications dans les proxies avec Ansible et docker service
plutôt que sed
et la commande stack-exec
, ce qui permettrait de mettre à jour les services nginx des proxies depuis le manager et sans avoir à se connecter sur les conteneurs.
Résolution de problèmes sur l’infrastructure
En cas de problème sur l’infrastructure.
Tous les conteneurs sont instanciés sur un seul des 2 workers
Vérifier le montage NFS sur le worker qui n’a reçu aucun conteneur, si absent sudo mount -a
Un seul replica du proxy des workers a été instancié
Idem que pour le problème précédent
Aucun conteneur ne se lance à part les conteneurs système (registry, portainer...)
C’est probablement un problème avec le serveur NFS sur le manager
- relancer le serveur NFS sur dkm-webapps
systemctl restart nfs-server
- remonter système de fichiers sur dkh-webapps1-2
sudo mount -a
Le service du proxy des workers ne démarre pas
Cela est généralement dû à des configurations de site qui pointent vers des services absents et empêchent nginx
de démarrer
- supprimer toutes les configurations dans
/dockerdata-ceph/proxy/uclouvain.d
saufapps.sisg.conf
- relancer la stack frontend :
- soit faire un update du service via Portainer
- soit faire un update du service via le client ligne de commande Docker
docker service update --force frontend_proxy
- soit ré-instancier la stack
(cd /dockerdata-ceph/proxy; docker stack rm frontend; docker stack deploy -c docker-compose.yml frontend)
- une fois le service redémarré, recharger la config des sites via
(cd /dockerdata-ceph/proxy/; sudo -u ansible bin/app serverconf)
Erreur 502 persistante
Généralement due à un problème avec un des niveaux de proxy
- recharger la config
nginx
sur le proxy frontal viasystemctl reload nginx
- si ça ne suffit pas, recharger la config des sites configurés sur le proxy des workers via
(cd /dockerdata-ceph/proxy/; sudo -u ansible bin/app serverconf)
Désactiver et réactiver un service
# désactiver un service
docker service scale <service_name>=0
# activer le service
docker service scale <service_name>=<number_of_replicas>
# si un seul replica
docker service scale <service_name>=1
Le choix s’était au départ porté sur une distribution Ubuntu 18.04 LTS présentée comme préférable à Debian pour le déploiement de Docker. Néanmoins, une incompatibilité entre la gestion du DNS dans Ubuntu 18.04 et la version du Docker Engine disponible au moment de la mise en place nous à fait choisir Debian GNU/Linux à la place.
Il faudra prévoir un stockage pour /var/lib/docker plus important en production.
Il faudra prévoir un stockage supplémentaire pour /var/lib/docker en production.
« Installation de docker sous debian et ubuntu » https://docs.docker.com/install/linux/docker-ce/debian/
ACL (Access Control List) est un système de gestion fin des droits d’accès aux fichiers. Les droits d’accès y sont définis par des ACE (Access Control Entry) pour un utilisateur ou un groupe. Ce système vient compléter la gestion des permissions POSIX sur les systèmes de type UNIX.
Voir la documentation officielle pour plus de détails https://docs.docker.com/engine/swarm/
Voir https://container-solutions.com/running-docker-containers-with-systemd/