Page d'accueil Mes articles

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

Dans cette dernière partie, nous allons voir comment déployer de nouvelles applications sans coupures grâce à Caddy-Docker-Proxy

Publié en November 2022.

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.

# Dockerfile
FROM php:8.1-rc-fpm-alpine as app_php

WORKDIR /srv/app

COPY public /srv/app/public
COPY src /srv/app/src
COPY vendor /srv/app/vendor
COPY composer.json /srv/app/composer.json
COPY docker-entrypoint.sh .

RUN chmod -R ugo+rx public
RUN chmod -R ugo+rx src
RUN 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/public
php_fastcgi php_fpm:9000
file_server
# .env
CONTAINER_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.yaml
version: '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
      <<: *app-cache-from
    image: ${CONTAINER_REGISTRY_BASE}/php_fpm

  caddy:
    build:
      context: ./
      target: app_caddy
      <<: *app-cache-from
    image: ${CONTAINER_REGISTRY_BASE}/caddy
    depends_on:
      - php_fpm

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.yaml
version: '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 --pull
docker-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.yaml
version: '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.yaml
version: "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 --pull
docker-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 😁

Cet article vous a été utile ? Faites le connaître sur les réseaux sociaux Twitter twitter LinkedIn linkedin

Un commentaire ?

codeur
Ou connectez-vous avec GitHub