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 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
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 --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
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 😁