First App, création d'une stack
J'ai fait évoluer ma stack first_app en y ajoutant caddy webserver. Ici, pas de https, seulement une exposition du port 80.
# DockerfileFROM php:8.1-rc-fpm-alpine as app_php WORKDIR /srv/app COPY public /srv/app/publicCOPY src /srv/app/srcCOPY vendor /srv/app/vendorCOPY composer.json /srv/app/composer.jsonCOPY docker-entrypoint.sh . RUN chmod -R ugo+rx publicRUN chmod -R ugo+rx srcRUN chmod -R ugo+rx vendor EXPOSE 9000 ENTRYPOINT [ "./docker-entrypoint.sh" ] CMD php-fpm FROM caddy:2-alpine AS app_caddy WORKDIR /srv/app COPY public /srv/app/public COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile
# docker/caddy/Caddyfile{ auto_https off debug} :80 root * /srv/app/publicphp_fastcgi php_fpm:9000file_server
# .envCONTAINER_REGISTRY_BASE=registry.gitlab.com/yoann.chocteau/first-app
Mon projet first-app possède alors deux images. php_fpm et caddy.
# docker-compose-build.yamlversion: '3.7' x-cache-from: - &app-cache-from cache_from: - ${CONTAINER_REGISTRY_BASE}/php_fpm - ${CONTAINER_REGISTRY_BASE}/caddy services: php_fpm: build: context: ./ target: app_php
Avant d'aller plus loin et pour vérifier que tout fonctionne comme attendu, je vous conseille de créer un docker-compose-prod.yaml pour "simuler" votre production.
# docker-compose-prod.yamlversion: '3.7' services: php_fpm: image: ${CONTAINER_REGISTRY_BASE}/php_fpm:latest caddy: image: ${CONTAINER_REGISTRY_BASE}/caddy:latest ports: - 80:80
export $(cat .env | xargs)docker-compose -f docker-compose-build.yaml build --pulldocker-compose -f docker-compose-build.yaml push
Je vois alors le résultat sur http://127.0.0.1 !

Mon application fonctionne, je passe maintenant à la création de la stack pour swarm.
# docker-compose.yamlversion: '3.7' services: php_fpm: image: ${CONTAINER_REGISTRY_BASE}/php_fpm:latest networks: - first_app deploy: mode: replicated replicas: 2 resources: limits: cpus: '0.5' memory: 500M first_app_caddy: image: ${CONTAINER_REGISTRY_BASE}/caddy:latest networks: - caddy - first_app deploy: mode: replicated replicas: 2 resources: limits: cpus: '0.5' memory: 500M labels: caddy: first-app.chocteau.dev caddy.reverse_proxy: "{{upstreams}}" caddy.tls.ca: https://acme-staging-v02.api.letsencrypt.org/directory networks: first_app: external: false caddy: external: true
Comme vous pouvez le voir, je nomme mon serveur de façon unique. first_app_caddy : le nom du projet suivi de caddy. C'est important puisque ce service sera présent sur le même réseau caddy que mes autres applications. Donc si vous le nommez simplement caddy et que vous vous retrouvez avec plusieurs services caddy sur le network caddy ... Et bien docker-caddy-proxy sera perdu dans ses labels !
Caddy
Pour commencer, je vais créer un réseau dans lequel mon proxy caddy sera capable de gérer dynamiquement mes domaines en fonction des labels définis.
docker network create -d overlay --attachable caddy
# docker-compose-proxy.yamlversion: "3.7"services: caddy: image: lucaslorentz/caddy-docker-proxy:ci-alpine ports: - 80:80 - 443:443 environment: - CADDY_INGRESS_NETWORKS=caddy networks: - caddy volumes: - /var/run/docker.sock:/var/run/docker.sock - caddy_data:/data restart: unless-stopped networks: caddy: external: true volumes: caddy_data: {}
lucaslorentz/caddy-docker-proxy doit avoir un montage de volume avec docker afin de piloter l'adressage DNS de nos services. En fait, ce que va faire cette image, c'est récupérer les labels de mes services et venir modifier la configuration de caddy dynamiquement. Comme nous avons pu le voir dans l'article précédent, on a la possibilité avec l'API de Caddy de modifier la configuration sans interruptions de service.
Par contre, là, pas de question d'utiliser Swarm. Il faut impérativement que notre proxy soit présent sur le manager. Car si vous le déployer sur swarm, et que votre service se retrouve sur un worker, il n'aura pas les droits pour consulter les labels affectés à vos services.
eval $(docker-machine env demo-swarm-1)docker-compose -f docker-compose-proxy.yaml up -d
Et maintenant, je n'ai plus qu'à déployer mon service first_app. Je retourne dans le répertoire de mon projet first_app, et j'exécute les commandes suivantes
eval $(docker-machine env demo-swarm-1)export $(cat .env | xargs)docker stack deploy --with-registry-auth -c docker-compose.yaml first_app

Et voilà ! Mon premier service est disponible. Pour bien voir le côté dynamique, je créé un projet second_app ( qui n'est qu'un copié collé de first_app ). De la même manière que pour first_app, je build et je push ses images sur un container registry.
Une fois cela fait, je déploie ce nouveau service sur mon cluster.
version: '3.7' services: php_fpm: image: ${CONTAINER_REGISTRY_BASE}/php_fpm:latest networks: - second_app deploy: mode: replicated replicas: 2 resources: limits: cpus: '0.5' memory: 500M second_app_caddy: image: ${CONTAINER_REGISTRY_BASE}/caddy:latest networks: - caddy - second_app deploy: mode: replicated replicas: 2 resources: limits: cpus: '0.5' memory: 500M labels: caddy: second-app.chocteau.dev caddy.reverse_proxy: "{{upstreams}}" caddy.tls.ca: https://acme-staging-v02.api.letsencrypt.org/directory networks: second_app: external: false caddy: external: true
eval $(docker-machine env demo-swarm-1)export $(cat .env | xargs)docker stack deploy --with-registry-auth -c docker-compose.yaml second_app

Parfait ! J'ai bien deux applications qui tournent sur mon cluster, et j'ai pu ajouter la nouvelle sans que cela ne perturbe la première.
Je décide de mettre à jour le code de l'application "second_app" avec ajoutant quelques smileys. 🎉🎊
Je build et je push.
export $(cat .env | xargs)docker-compose -f docker-compose-build.yaml build --pulldocker-compose -f docker-compose-build.yaml push
Puis je mets à jour mon service.
eval $(docker-machine env demo-swarm-1)docker service update --with-registry-auth --image registry.gitlab.com/yoann.chocteau/second-app/php_fpm:latest second_app_php_fpm --force

Et voilà 🎉
Comme vous avez pu le voir dans les clés deploy de mes docker-compose, je définis le replicas à 2. En fait, c'est le minimum lorsque vous travaillez avec Swarm et que vous ne voulez pas d'interruption de service au déploiement. Et si demain, vous envisagez un passage télé, vous pouvez très bien ajouter des noeuds à votre cluster swarm et repliquer vos services avec la commande
docker service scale second_app_php_fpm=10
Et ça, franchement, c'est cool 😍
Bien sûr, le plus important pour un passage télé est de bien gérer en amont vos requêtes SQL, la gestion du cache à l'aide d'un reserve proxy comme varnish, le mise en place d'un système de queue... et j'en passe.
Car nos serveurs consomment de l'énergie, et notre résponsabilité en tant que développeur est de faire en sorte d'en consommer le strict nécessaire. 🌱
J'espère que cette série d'article vous a plu, n'hésitez pas à me partager vos retours dans les commentaires 😁