diff --git a/README.md b/README.md index cb63e56b7b96cf465f726a3ced0605464e67808e..6e9a912c0f18867ec785a39c4b2e97feada82959 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,4 @@ Lire [la documentation de mise à jour des services](./doc/update_and_test.md). ### Mettre en place un nouveau service -Lire [la documentation de versionnage d'un nouveau service](./new_service.md) et [les bonnes pratiques pour Docker](./doc/guide_bonnes_pratiques.md). +Lire [la documentation de versionnage d'un nouveau service](./doc/new_service.md) et [les bonnes pratiques pour Docker](./doc/guide_bonnes_pratiques.md). diff --git a/doc/guide_bonnes_pratiques.md b/doc/guide_bonnes_pratiques.md index a2dc03edfe86eb985c50a2f2c5ad49fda7552716..1dfe9f06312f27f177447af8dbd00be2a7d2b7d3 100644 --- a/doc/guide_bonnes_pratiques.md +++ b/doc/guide_bonnes_pratiques.md @@ -2,7 +2,7 @@ Ce mini-guide vous donne des pistes pour créer de nouveaux services, tout en restant harmonieux par rapport à l'infrastructure existante. -Pour une plongée plus complète dans les différents concepts abordés ici, on pourra se référer aux documentations officielles. Il n'est pas nécessaire de les lire en entier pour travailler sur ce dépôt : ils seront surtout utiles pour comprendre l'existant. +Pour une plongée plus complète dans les différents concepts abordés ici, on pourra se référer aux documentations officielles. Il n'est pas nécessaire de les lire en entier pour travailler sur ce dépôt : elles seront surtout utiles pour comprendre l'existant. * [Une introduction à Docker : pourquoi et comment](https://docs.docker.com/engine/docker-overview/) * [Référence pour l'écriture d'un Dockerfile](https://docs.docker.com/engine/reference/builder/) @@ -12,17 +12,17 @@ Pour une plongée plus complète dans les différents concepts abordés ici, on ## Se baser sur un autre Dockerfile ? -Tous les services tournant sur l'infrastructure de Picasoft sont voués à être pris en charge par ce dépôt. Cela permet notamment d'assurer leur présence sur notre registre Docker privé (`registry.picasoft.net`), ce qui permet notamment de réduire le temps de téléchargement des images, d'assurer leur intégrité et de décentraliser le stockage des images du seul Hub Docker officiel. Enfin, cela nous permet de tester la sécurité des images en amont. +Tous les services tournant sur l'infrastructure de Picasoft sont voués à être pris en charge par ce dépôt. Il y a plusieurs cas. ### Je veux faire tourner un service existant, tel quel -Si un service existant a une image Docker officielle, référencée sur le Docker Hub, et qu'elle nous suffit, on s'en servira tel quel dans le `docker-compose.yml`. +Si un service existant a une image Docker officielle, référencée sur le Docker Hub, et qu'elle nous suffit, on s'en servira telle quel dans le `docker-compose.yml`. ### Je veux customiser un service existant -Supposons que l'image Docker officielle ne nous convienne pas, que l'on soit obligés de la modifier pour régler des problèmes de sécurité, ou tout simplement qu'on veuille l'étendre, il faut créer un Dockerfile plus complet. Il y a deux solutions : +Supposons que l'image Docker officielle ne nous convienne pas, que l'on soit obligé de la modifier pour régler des problèmes de sécurité, ou tout simplement qu'on veuille l'étendre, il faut créer un Dockerfile plus complet. Il y a deux solutions : * Soit on part de l'image officielle, avec un `FROM`, et on travaille dessus en rajoutant des fichiers, en enlevant des paquets... Cette solution a l'inconvénient de multiplier les *layers* inutiles, et d'augmenter la taille de l'image. * Soit on copie le Dockerfile de l'image officielle (c'est le cas pour [Mattermost](../pica-mattermost)), et on fait nos modifications. Cette solution a pour inconvénient de devoir se synchroniser avec les modifications du Dockerfile officiel à chaque mise à jour, s'il contient des améliorations ou corrections importantes. @@ -51,7 +51,7 @@ L'idée derrière un build "reproductible", c'est que si je me rends sur un anci Souvent, un Dockerfile va récupérer du code sur un dépôt Git, ou encore un binaire sur un site de téléchargement de releases. Il est fortement déconseillé de faire un `git clone` ou un `wget` de la dernière version (`latest`, `master`...), ce qui rend le build non reproductible et dépendant de cette dernière version. -Il est donc important de gérer la version du service en question, par exemple avec une variable `ENV SERVICE_VERSION=1.0.1` qui sera ré-utilisée dans l'URL de téléchargement. +Il est donc important de gérer la version du service en question, par exemple avec une variable `ARG SERVICE_VERSION=1.0.1` qui sera ré-utilisée dans l'URL de téléchargement. En outre il existe deux solutions pour récupérer du code existant, versionné sur un dépôt Git distant : * Installer Git dans le Dockerfile, utiliser un `git clone` puis un `git checkout <tag>` sur la version souhaitée et copier le code dans l'image. @@ -74,25 +74,29 @@ Si le fichier à monter est versionné sur ce dépôt, on utilisera un chemin re Exemple : ```yaml +[...] services: - volumes: - # Dossier contenant les certificats sur les machines de production : utilisation du chemin absolu - - /DATA/docker/certs/example.picasoft.net:/certs - # Fichier de configuration versionné dans le même dossier : utilisation du chemin relatif - - ./config.json:/etc/config.json + exemple: + [...] + volumes: + # Dossier contenant les certificats sur les machines de production : utilisation du chemin absolu + - /DATA/docker/certs/example.picasoft.net:/certs + # Fichier de configuration versionné dans le même dossier : utilisation du chemin relatif + - ./config.json:/etc/config.json ``` ### Volumes Docker -Si on veut indiquer qu'un des dossiers du conteneur doit persister au fil des recréations, alors on utilise des volumes Docker. C'est typiquement le cas pour le dossier `/var/lib/postgresql/data` d'une base Postgres, qui ne doit pas supprimer les données à chaque recréation du conteneur. +Si on veut indiquer qu'un des dossiers du conteneur doit persister au fil des re-créations, alors on utilise des volumes Docker. C'est typiquement le cas pour le dossier `/var/lib/postgresql/data` d'une base PostgreSQL, qui ne doit pas supprimer les données à chaque re-création du conteneur. -Exemple à reprendre : +Exemple : ```yaml +[...] volumes: # Nom du volume Docker db: - # On force le nom utilisé par Docker + # On spécifie le nom exact name: db services: @@ -103,8 +107,8 @@ volumes: ``` Il est suggéré d'éviter les volumes déclarés `external` : -* Un volume créé en dehors de Docker Compose peut être utilisé sans être déclaré `external` ; -* En revanche un volume non-créé et déclaré `external` fera échouer Docker Compose. +* Un volume créé en dehors de Compose peut être utilisé sans être déclaré `external` ; +* En revanche un volume non-créé et déclaré `external` fera échouer les commandes Compose. ## Reverse-proxy @@ -125,6 +129,13 @@ services: exemple: networks: - docker_default + labels: + # Traefik va prendre ce conteneur en compte + traefik.enable: true + # Il redirigera vers ce port, exposé par le conteneur + traefik.port: <port> + # Lorsque l'utilisateur consulte cette URL + traefik.frontend.rule: <exemple>.picasoft.net ``` ## Système init @@ -132,31 +143,37 @@ services: Tous les systèmes Linux ont un système dit `init`, correspondant au processus avec le premier PID (1). Ce processus est le parent de tous les autres, et doit transmettre les signaux qu'il reçoit à ses enfants (par exemple, un signal de terminaison). Quand vous lancez un conteneur avec un script Shell ou Bash comme entrypoint, ce script a le PID 1. -S'il démarre ensuite l'application, il ne transmettra pas le signal de terminaison à ses enfants. +S'il démarre ensuite l'application, il ne transmettra pas le signal de terminaison à ses enfants (fonctionnement normal des scripts shell). Le souci, c'est qu'un `docker stop` enverra un signal `SIGTERM` au script d'entrypoint, mais il ne sera pas transmis au service en lui-même, qui se terminera brutalement par un `SIGKILL` après expiration du timeout. -Docker Compose, depuis la version 3.7, adresse ce problème avec une directive très simple : +Compose, depuis la version 3.7, adresse ce problème avec une directive très simple : ``` services: exemple: init: true ``` -[Plus d'informations sur ce lien](https://hynek.me/articles/docker-signals/) et [sur la documentation de Compose](https://docs.docker.com/compose/compose-file/#init) +[Plus d'informations sur ce lien](https://hynek.me/articles/docker-signals/) et [sur la documentation de Compose](https://docs.docker.com/compose/compose-file/#init). ## Réseaux L'idée est de mettre dans des réseaux séparés les services n'ayant pas besoin de communiquer entre eux, pour améliorer la sécurité de l'infrastructure. -Imaginons un service web et sa base de données. Le service web a besoin d'être exposé sur Internet, via Traefik, mais sa base de données n'a pas besoin ! D'autant que si on a la rajoutait au réseau `docker_default`, elle serait également accessible des autres conteneurs du réseau. +Ainsi, la compromission d'un service dans un réseau particulier ne permet pas d'accéder aux autres services qui ne seraient pas exposés sur internet. + +Imaginons un service web et sa base de données. Le service web a besoin d'être exposé sur Internet, via Traefik, mais sa base de données n'a pas besoin. Ce qui nous donnerait quelque chose comme : ```yaml networks: + # C'est le réseau dans lequel se trouve + # Traefik sur toutes les machines. docker_default: external: true + # Ce réseau est créé uniquement pour + # ce fichier Compose. db: services: @@ -167,6 +184,8 @@ services: networks: - docker_default - db + # La base de donnée n'est que dans son réseau, et n'est donc + # pas accessible depuis Internet. exemple_db: networks: - db @@ -174,10 +193,18 @@ services: ## Mise en place de TLS -Si le service est un service **TLS**, mais n'est pas un service web, alors il a besoin de certificats. Ces certificats peuvent être générés par Traefik en lui "faisant croire" que c'est un service web, mais on ne peut pas passer par lui pour servir les requêtes. C'est le cas pour le LDAP, le serveur mail... on utilisera dans ce cas l'outil [TLS Certs Monitor](./pica-tls-certs-monitor). En pratique, cela revient à ajouter des labels dans le Docker Compose du dépôt. +Si le service est un service **TLS**, mais n'est pas un service web, alors il a besoin de certificats. + +Par exemple, pour pouvoir se connecter au serveur LDAP de manière sécurisée, on utilise LDAPS, mais on ne passe pas par Traefik, qui ne gère que les services web. -Si plusieurs conteneurs doivent partager un même volume, on accordera les noms dans les fichiers Docker Compose du dépôt. +Ces certificats peuvent être générés par Traefik en lui "faisant croire" que c'est un service web, mais on ne peut pas passer par lui pour servir les requêtes. C'est le cas pour le LDAP, le serveur mail... on utilisera dans ce cas l'outil [TLS Certs Monitor](./pica-tls-certs-monitor). En pratique, cela revient à ajouter des labels dans le fichier Compose du dépôt. ## Divers +On utilisera une version récente dans les fichiers `docker-compose.yml`, et au minimum la 3.7 pour profiter des fonctionnalités présentes dans ce guide. + +```yaml +version: '3.7' +``` + On préférera utiliser la politique `restart: unless-stopped` pour les services. Ceci évite qu'un service arrêté explicitement ne se relance tout seul au démarrage de la machine. diff --git a/doc/launch_service.md b/doc/launch_service.md index 8bca9b78fbc1d889a20ddeec129e48fb3b8f594a..b27a69826b115cf462e41af09e831c4f8e4b9a89 100644 --- a/doc/launch_service.md +++ b/doc/launch_service.md @@ -56,7 +56,7 @@ Attention : un conteneur noté `Unhealthy` à cause d'un mauvais `HEALTHCHECK` s ### Lancement d'un nouveau service Lorsque vous lancez un nouveau service, il y a parfois quelques opérations à faire avant de lancer les conteneurs. -En effet, bien qu'en théorie un `docker-compose up -d` suffise, il y a quelques cas particulier. +En effet, bien qu'en théorie un `docker-compose up -d` suffise, il y a quelques cas particuliers. #### Secrets