Déployer de nouvelles applications sans coupures grâce à Caddy-Docker-Proxy

Déployer de nouvelles applications sans coupures grâce à Caddy-Docker-Proxy

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 tourne en local

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

First App est disponible

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

Second App est disponible

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

Second App a bien été mis à jour

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 😁

Ça peut aussi vous intéresser