feat: intégration Fabio/Consul/Registrator + TLS acme.sh
docker-compose.business.yml: - Suppression version: obsolete et ports: (Fabio gère le routing) - Ajout labels SERVICE_3000_* pour Registrator/Consul/Fabio - Ajout LETSENCRYPT_HOST pour sonic-acme-1 - Ajout réseau sonic (externe, partagé avec la stack) .woodpecker.yml: - Ajout write-env (RADAR_DOMAIN depuis secret, séparé car from_secret + volumes incompatibles) - Ajout TLS acme.sh dans deploy (idempotent, exit 2 = skip) - Healthcheck sur https://RADAR_DOMAIN (100s max) Secret Woodpecker à créer : radar_domain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,17 +22,48 @@ steps:
|
|||||||
- npm run build
|
- npm run build
|
||||||
- ls -la build/ | head -10
|
- ls -la build/ | head -10
|
||||||
|
|
||||||
# Etape 2 : Deploiement sur sonic via Docker socket
|
# Etape 2a : Ecriture du .env depuis les secrets
|
||||||
# NOTE: from_secret et volumes: incompatibles dans le meme step (bug Woodpecker next)
|
# NOTE: from_secret et volumes: incompatibles dans le meme step (bug Woodpecker next)
|
||||||
# Aucun secret ici — les variables de build sont dans docker-compose.business.yml
|
- name: write-env
|
||||||
|
image: alpine:3.20
|
||||||
|
environment:
|
||||||
|
RADAR_DOMAIN:
|
||||||
|
from_secret: radar_domain
|
||||||
|
commands:
|
||||||
|
- env | grep -E "^(RADAR_DOMAIN)=" > .env.deploy
|
||||||
|
- echo "Fichier .env.deploy cree ($(wc -c < .env.deploy) octets)"
|
||||||
|
|
||||||
|
# Etape 2b : Deploiement sur sonic via Docker socket
|
||||||
|
# NOTE: from_secret et volumes: incompatibles — pas de secrets ici
|
||||||
- name: deploy
|
- name: deploy
|
||||||
image: docker:27-cli
|
image: docker:27-cli
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
commands:
|
commands:
|
||||||
|
- export $(cat .env.deploy | xargs)
|
||||||
- docker compose -f docker-compose.business.yml build --no-cache
|
- docker compose -f docker-compose.business.yml build --no-cache
|
||||||
- docker compose -f docker-compose.business.yml up -d --remove-orphans
|
- docker compose -f docker-compose.business.yml up -d --remove-orphans
|
||||||
- docker compose -f docker-compose.business.yml ps
|
- docker compose -f docker-compose.business.yml ps
|
||||||
|
- |
|
||||||
|
DOMAIN=$(grep '^RADAR_DOMAIN=' .env.deploy | cut -d= -f2)
|
||||||
|
|
||||||
|
# --- Certificat TLS (acme.sh via sonic-acme-1) ---
|
||||||
|
# acme.sh est idempotent : skip si cert valide, renouvelle si proche expiration
|
||||||
|
# exit 0 = emis/renouvele, exit 2 = skip (domaine inchange), autres = erreur
|
||||||
|
ACME_EXIT=0
|
||||||
|
docker exec sonic-acme-1 /app/acme.sh \
|
||||||
|
--home /etc/acme.sh \
|
||||||
|
--issue -d "$DOMAIN" \
|
||||||
|
--webroot /usr/share/nginx/html \
|
||||||
|
--server letsencrypt \
|
||||||
|
--accountemail support+acme@asycn.io || ACME_EXIT=$?
|
||||||
|
if [ "$ACME_EXIT" -ne 0 ] && [ "$ACME_EXIT" -ne 2 ]; then
|
||||||
|
echo "ERREUR: acme.sh a echoue (exit $ACME_EXIT)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/fullchain.cer /host/certs/$DOMAIN-cert.pem
|
||||||
|
docker exec sonic-acme-1 cp /etc/acme.sh/$DOMAIN/$DOMAIN.key /host/certs/$DOMAIN-key.pem
|
||||||
|
echo "Cert TLS: /host/certs/$DOMAIN-cert.pem OK (acme exit $ACME_EXIT)"
|
||||||
|
|
||||||
# Etape 3 : Healthcheck post-deploiement
|
# Etape 3 : Healthcheck post-deploiement
|
||||||
- name: healthcheck
|
- name: healthcheck
|
||||||
@@ -40,19 +71,26 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- apk add --no-cache --quiet curl
|
- apk add --no-cache --quiet curl
|
||||||
- |
|
- |
|
||||||
|
DOMAIN=$(grep '^RADAR_DOMAIN=' .env.deploy | cut -d= -f2)
|
||||||
|
if [ -z "$DOMAIN" ]; then
|
||||||
|
echo "ERREUR: RADAR_DOMAIN non defini dans .env.deploy"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
TARGET="https://$DOMAIN"
|
||||||
|
echo "Healthcheck sur $TARGET (max 100s)..."
|
||||||
MAX=20
|
MAX=20
|
||||||
i=0
|
i=0
|
||||||
until [ $i -ge $MAX ]; do
|
until [ $i -ge $MAX ]; do
|
||||||
CODE=$(curl -sSo /dev/null -w "%{http_code}" http://localhost:3006/ 2>/dev/null)
|
CODE=$(curl -sSo /dev/null -w "%{http_code}" "$TARGET" 2>/dev/null)
|
||||||
echo "Tentative $((i+1))/$MAX - HTTP $CODE"
|
echo "Tentative $((i+1))/$MAX - HTTP $CODE"
|
||||||
if [ "$CODE" = "200" ]; then
|
if [ "$CODE" = "200" ] || [ "$CODE" = "301" ] || [ "$CODE" = "302" ]; then
|
||||||
echo "Radar repond sur http://localhost:3006/"
|
echo "Radar repond sur $TARGET"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
echo "ERREUR: Radar ne repond pas apres $(($MAX * 5)) secondes"
|
echo "ERREUR: Radar ne repond pas apres 100 secondes"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
# Notification en cas d'echec
|
# Notification en cas d'echec
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
radar-business:
|
radar-business:
|
||||||
container_name: laplank-radar-technolologique
|
container_name: laplank-radar-technolologique
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.business
|
dockerfile: Dockerfile.business
|
||||||
pull: true # Force le pull de l'image de base pour éviter le cache
|
pull: true
|
||||||
args:
|
args:
|
||||||
BUILD_DATE: "${BUILD_DATE:-$(date +%s)}"
|
BUILD_DATE: "${BUILD_DATE:-$(date +%s)}"
|
||||||
BUILD_VERSION: "${BUILD_VERSION:-dev}"
|
BUILD_VERSION: "${BUILD_VERSION:-dev}"
|
||||||
CACHE_BUST: "${CACHE_BUST:-$(date +%s%N)}" # Nanosecondes pour garantir l'unicité et forcer l'invalidation
|
CACHE_BUST: "${CACHE_BUST:-$(date +%s%N)}"
|
||||||
# Note: no_cache n'est pas supporté dans docker-compose
|
|
||||||
# Pour forcer le rebuild sans cache dans Portainer, utilisez l'option "Rebuild" avec "No cache" dans l'interface
|
|
||||||
# Si vous utilisez une image pré-bâtie, décommentez image et commentez build
|
|
||||||
# image: votre-registre/laplank-radar-business:latest
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
|
||||||
- "3006:3000" # Mappe le port 3006 de l'hôte vers le port 3000 du conteneur
|
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
# Optionnel : Persistance des logs si nécessaire
|
labels:
|
||||||
# volumes:
|
# Registrator lit l'IP du conteneur depuis le reseau "sonic" (-useIpFromNetwork sonic)
|
||||||
# - ./logs:/app/logs
|
# et enregistre le service dans Consul avec le tag urlprefix- -> Fabio route vers ce service
|
||||||
|
- SERVICE_3000_NAME=laplank-radar-business
|
||||||
|
- SERVICE_3000_TAGS=urlprefix-${RADAR_DOMAIN}/
|
||||||
|
- SERVICE_3000_CHECK_TCP=true
|
||||||
|
# sonic-acme-1 (acme-companion) emet le cert TLS et le copie dans /host/certs/
|
||||||
|
# Fabio le detecte automatiquement par SNI pour HTTPS
|
||||||
|
- LETSENCRYPT_HOST=${RADAR_DOMAIN}
|
||||||
|
networks:
|
||||||
|
- sonic
|
||||||
|
|
||||||
|
networks:
|
||||||
|
sonic:
|
||||||
|
# Reseau externe existant sur le serveur (partage avec Registrator/Consul/Fabio)
|
||||||
|
external: true
|
||||||
|
|||||||
Reference in New Issue
Block a user