Déploiement facile de Docker Compose sans temps d'arrêt

Pendant longtemps, Hyvor Talk fonctionnait directement sur des serveurs VPS. Toutes les dépendances étaient installées directement (PHP, extensions PHP, Node, etc.) sur le serveur. Cependant, cela est devenu assez lourd. La mise à jour était difficile. L'application était fragile en raison des incohérences entre les environnements sur lesquels elle fonctionnait. Il existe des solutions comme Ansible pour automatiser cela, mais nous avons décidé d'utiliser Docker pour conteneuriser l'application. J'écrirai un autre tutoriel sur la façon dont j'ai conteneurisé notre application Laravel. Ici, je vais me concentrer sur un problème spécifique : les déploiements de docker-compose sans temps d'arrêt.

Après avoir dockerisé l'application, j'ai cherché des solutions pour la déployer. La plus simple était docker-compose, qui est fourni avec Docker. Elle a fait fonctionner notre application sans problème... jusqu'à ce que nous ayons besoin de déployer une nouvelle version.

Le problème majeur de docker-compose est que lorsque nous mettons à jour un conteneur, le premier est arrêté, puis le second est démarré. Cela provoque un temps d'arrêt - dans notre cas, un temps d'arrêt de 10 à 20 secondes entre les déploiements.

Peut-être est-il temps d'utiliser un outil d'orchestration de conteneurs comme Docker Swarm ou Kubernetes? Je les ai gardés en dernier recours et j'ai commencé à trouver un moyen de faire fonctionner le tout avec seulement docker-compose. Je ne voulais pas non plus écrire un script shell de 100 lignes pour que cela fonctionne.

Google et ChatGPT ont donné des solutions très complexes, et la plupart d'entre elles n'ont pas fonctionné. J'ai trouvé une meilleure solution sur Hackernews - plus précisément sur hn.algolia.com.

Il s'agit de docker-rollout, un plugin docker pour les déploiements de docker-compose sans interruption de service.

Il est assez facile à installer sur votre serveur (consultez le README du paquet pour des instructions à jour).

1# Create directory for Docker cli plugins
2mkdir -p ~/.docker/cli-plugins
3
4# Download docker-rollout script to Docker cli plugins directory
5curl https://raw.githubusercontent.com/wowu/docker-rollout/master/docker-rollout -o ~/.docker/cli-plugins/docker-rollout
6
7# Make the script executable
8chmod +x ~/.docker/cli-plugins/docker-rollout

Maintenant, vous pouvez lancer la commande docker rollout <service> pour démarrer une nouvelle version du service sans temps d'arrêt.

Pour que cela fonctionne, vous avez besoin d'un serveur proxy comme NGINX ou Traefik.

Je n'ai pas réussi à faire fonctionner le proxy NGINX la première fois, alors j'ai essayé Traefik, ayant envie d'apprendre un nouvel outil.

Avant le déploiement de docker, l'application était exposée sur le port 80. Ainsi, tout le trafic HTTP allait directement au contianer d'application. Maintenant, nous lions le port 80 au service Traefik. Il déterminera le conteneur à utiliser (docker-rollout s'en occupe).

Mise en place de Traefik

Créez un nouveau docker-compose.traefik.yaml

1version: "3.7"
2
3services:
4 traefik:
5 image: traefik:v2.9
6 container_name: traefik
7 command:
8 - "--api.insecure=true"
9 - "--providers.docker=true"
10 - "--providers.docker.exposedbydefault=false"
11 - "--entrypoints.web.address=:80"
12 ports:
13 - "80:80"
14 # - "8080:8080"
15 volumes:
16 - "/var/run/docker.sock:/var/run/docker.sock:ro"

J'ai emprunté le code de docker-compose.traefik.yaml à cet exemple dans le dépôt docker-rollout.

  • --api.insecure=true - Je n'ai aucune idée de la raison pour laquelle cela est nécessaire

  • --providers.docker=true - Définit le fournisseur à Docker. Vous pouvez en savoir plus sur le fournisseur Docker ici.

  • --providers.docker.exposedbydefault=false - Les services ne sont exposés que s'ils ont le label traefik.enable=true (que nous allons ajouter à notre service d'application).

  • --entrypoints.web.address=:80 - Définit l'adresse web

Pour les tests, vous pouvez également exposer le port 8080, qui sert le tableau de bord Traefik.

Ensuite, lancez le service Traefik.

1docker-compose -f docker-compose.traefik.yaml up -d

Mise à jour de votre service

Ensuite, mettez à jour le fichier docker-compose de votre service :

1version: "3.9"
2
3services:
4 myservice:
5 image: mycompany/myservice:latest
6 labels:
7 - "traefik.enable=true"
8 - "traefik.http.routers..entrypoints=web"
9 - "traefik.http.routers..rule=PathPrefix(`/`)"
10 deploy:
11 update_config:
12 order: start-first
13 failure_action: rollback
14 delay: 5s
15 healthcheck:
16 test: 'curl -f http://localhost || exit 1'
  • Remplacez <service> par le nom de votre service.

  • healthcheck.test est très important. C'est ainsi que Docker sait si votre service est en ligne et en bonne santé.

  • traefik.enable=true - Active Traefik pour ce service. Ceci est nécessaire car nous avons ajouté exposedbydefault=false à la configuration de Traefik

  • J'ai passé plus d'une heure à trouver la meilleure valeur de traefik.http.routers.<service>.rule. Par défaut, le fournisseur Docker dans Traefik définit la règle par défaut à un Host(). Ainsi, Traefik n'achemine les requêtes vers ce service que si l'en-tête Host correspond à cette valeur. Dans mon cas, je voulais acheminer tout le trafic. J'ai essayé Host(*), HostRegexp({domaine : .*}) - rien ne fonctionnait. Je n'ai pas non plus trouvé de moyen de désactiver la règle par défaut. Finalement, PathPrefix(`/`) a fait le travail pour correspondre à tout. En fonction de votre cas d'utilisation, vous devrez choisir la meilleure option ici.

Déploiements sans interruption de service

Enfin, utilisez docker rollout

1docker rollout myservice

Ce qui se passe ici, c'est qu'au lieu d'arrêter et de démarrer un nouveau conteneur, docker rollout fait passer le conteneur à 2, et attend que le nouveau service soit sain. Ensuite, il met à jour Traefik pour acheminer tout le trafic vers le nouveau service. Enfin, l'ancien conteneur sera arrêté.

N'hésitez pas à laisser un commentaire ci-dessous 👋