Aller au contenu

CodeDeploy bleu/vert sur EC2 + ASG — la mécanique d'attente-puis-bascule qui annule réellement

Comment CodeDeploy provisionne un ASG de remplacement, le maintient derrière l'ALB jusqu'à validation, puis démantèle l'ancien — avec le appspec.yml et la configuration du groupe de déploiement qui font fonctionner le tout.

Un ASG de quatre nœuds m6i.large se trouve derrière un ALB et sert une API interne. L’équipe effectue des mises à jour CodeDeploy progressives en place depuis deux ans, et le matin d’un déploiement de routine, l’une des quatre machines se retrouve à mi-chemin de ApplicationStart avec un cache node_modules périmé au moment où le débit de requêtes augmente. L’ALB redirige une partie du trafic vers la machine à moitié déployée; le taux d’erreur grimpe pendant quatre-vingt-dix secondes; l’ingénieur d’astreinte annule manuellement en relançant la révision précédente via CodeDeploy. La conclusion du post-mortem est juste : les déploiements en place sur un ASG n’ont pas de point de bascule atomique. La correction consiste à passer à BLUE_GREEN avec un ASG de remplacement. Les paragraphes suivants couvrent le câblage.

Ce qui se passe

CodeDeploy en place sur un ASG EC2 déploie vers les instances existantes une ou deux à la fois. L’agent CodeDeploy sur chaque instance récupère la révision, exécute les hooks, et le groupe de déploiement gère combien d’instances sont hors rotation simultanément. Le mode de défaillance dans le scénario ci-dessus est que « hors rotation » est appliqué en désinscrivant l’instance du groupe cible — mais le délai de désinscription (300 secondes par défaut) et le OverrideAlarmConfiguration du groupe de déploiement interagissent de manière à laisser une petite fenêtre où les requêtes peuvent atteindre une machine à moitié déployée.

Le bleu/vert élimine la fenêtre. CodeDeploy provisionne un tout nouvel ASG (le vert) à partir du modèle de lancement de l’existant (le bleu). Les nouvelles instances démarrent, l’agent exécute ApplicationStop → BeforeInstall → AfterInstall → ApplicationStart → ValidateService sur chaque instance de façon isolée. Ce n’est qu’après que chaque instance verte ait validé ValidateService et soit déclarée saine dans le groupe cible de l’ALB que CodeDeploy fait basculer le trafic du listener du groupe cible bleu vers le groupe cible vert. La bascule est un échange de groupe cible au niveau du listener ALB — atomique du point de vue du répartiteur de charge.

Après la bascule, CodeDeploy maintient l’ASG bleu en ligne pendant une durée configurable (le « temps d’attente avant terminaison des instances d’origine »). Si vous avez câblé l’annulation automatique à une alarme CloudWatch — taux de 5xx en moyenne cinq minutes, latence p95, ou une métrique personnalisée — et que l’alarme se déclenche dans la fenêtre d’attente, le listener rebascule vers le bleu et l’ASG vert est terminé. Si aucune alarme ne se déclenche, l’ASG bleu est terminé à la fin de la fenêtre d’attente et le déploiement est terminé.

Les trois leviers qui comptent :

  • Configuration de déploiement — CodeDeployDefault.AllAtOnce bascule 100 % en une seule fois; CodeDeployDefault.HalfAtATime bascule 50 % puis 50 % après une pause; des configurations personnalisées permettent 10/30/60/100. Pour une API interne où l’équipe surveille le taux d’erreur à une granularité de cinq minutes, AllAtOnce plus 15 minutes d’attente avant terminaison est un bon point de départ.
  • Attente avant terminaison — la fenêtre pendant laquelle l’ancien ASG est encore vivant et l’annulation est à une alarme CloudWatch près. Suffisamment longue pour rattraper les alarmes basées sur des métriques de cinq minutes avec au moins deux périodes d’évaluation. 15 minutes est le plancher; 30 est confortable.
  • Méthode de provisionnement — COPY_AUTO_SCALING_GROUP (CodeDeploy crée un tout nouvel ASG cloné de l’existant) est la valeur par défaut sûre. DISCOVER_EXISTING est pour les cas où l’ASG vert est pré-provisionné par Terraform et CodeDeploy ne fait que déployer dessus.

Comment vérifier

Trois éléments à vérifier avant de déclarer le groupe de déploiement prêt pour la production : le modèle de lancement produit une instance démarrable avec l’agent installé, le hook de validation valide réellement quelque chose, et les alarmes sont câblées correctement.

Pour le modèle de lancement, le mode de défaillance est une AMI qui démarre bien en place (parce que l’agent est déjà installé depuis le déploiement précédent) mais échoue en bleu/vert (parce que les nouvelles instances ont besoin que l’agent soit intégré dans l’AMI ou installé via user-data). Vérifiez en lançant une seule instance à partir du modèle de lancement, en vous connectant en SSH et en vérifiant l’agent :

sudo service codedeploy-agent status
sudo cat /var/log/aws/codedeploy-agent/codedeploy-agent.log | tail -50

Pour le hook de validation, le mode de défaillance est un script qui sort 0 sans condition — ce qui fait de ValidateService une no-op et laisse passer un déploiement cassé. Le hook doit atteindre le port local de l’application, vérifier un endpoint significatif et sortir non-zéro en cas d’échec :

#!/usr/bin/env bash
set -euo pipefail
for i in $(seq 1 12); do
  if curl -fsS http://127.0.0.1:8080/healthz | grep -q '"status":"ok"'; then
    exit 0
  fi
  sleep 5
done
exit 1

Ce hook attend jusqu’à 60 secondes que le service démarre, atteint /healthz, et fait échouer le déploiement si le endpoint est absent ou retourne une charge utile malsaine. Une vérification de surface « le port 8080 écoute-t-il » ne suffit pas — systemd continuera de relancer un processus en boucle de plantage et le port apparaîtra à l’écoute entre les redémarrages.

Pour les alarmes, listez le alarmConfiguration du groupe de déploiement :

aws deploy get-deployment-group \
  --application-name internal-api \
  --deployment-group-name internal-api-prod \
  --query 'deploymentGroupInfo.alarmConfiguration'

Si la réponse est {"enabled": false, "ignorePollAlarmFailure": false, "alarms": []}, l’annulation automatique n’est câblée à rien. Le champ alarms doit contenir au moins un nom d’alarme CloudWatch — typiquement une alarme de taux 5xx sur le groupe cible et une alarme de latence p95 sur le listener.

La correction

La configuration complète du groupe de déploiement pour BLUE_GREEN avec COPY_AUTO_SCALING_GROUP, bascule de trafic AllAtOnce, et 20 minutes d’attente avant terminaison :

aws deploy create-deployment-group \
  --application-name internal-api \
  --deployment-group-name internal-api-prod \
  --service-role-arn arn:aws:iam::111122223333:role/CodeDeployServiceRole \
  --auto-scaling-groups internal-api-prod-asg \
  --deployment-style deploymentType=BLUE_GREEN,deploymentOption=WITH_TRAFFIC_CONTROL \
  --blue-green-deployment-configuration '{
    "terminateBlueInstancesOnDeploymentSuccess": {
      "action": "TERMINATE",
      "terminationWaitTimeInMinutes": 20
    },
    "deploymentReadyOption": {
      "actionOnTimeout": "STOP_DEPLOYMENT",
      "waitTimeInMinutes": 30
    },
    "greenFleetProvisioningOption": {
      "action": "COPY_AUTO_SCALING_GROUP"
    }
  }' \
  --load-balancer-info '{
    "targetGroupInfoList": [{"name": "internal-api-tg"}]
  }' \
  --auto-rollback-configuration '{
    "enabled": true,
    "events": ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM"]
  }' \
  --alarm-configuration '{
    "enabled": true,
    "alarms": [
      {"name": "internal-api-5xx-rate"},
      {"name": "internal-api-p95-latency"}
    ]
  }'

Le appspec.yml correspondant se trouve à la racine de la révision et pointe vers l’artéfact de build et les hooks :

version: 0.0
os: linux
files:
  - source: /
    destination: /opt/internal-api
file_exists_behavior: OVERWRITE
permissions:
  - object: /opt/internal-api
    owner: app
    group: app
    mode: 0755
hooks:
  ApplicationStop:
    - location: scripts/stop.sh
      timeout: 60
      runas: root
  BeforeInstall:
    - location: scripts/before_install.sh
      timeout: 120
      runas: root
  AfterInstall:
    - location: scripts/after_install.sh
      timeout: 180
      runas: app
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: root
  ValidateService:
    - location: scripts/validate.sh
      timeout: 90
      runas: app

Trois règles que nous tenons pour le bleu/vert sur EC2 + ASG :

Ne jamais régler l’attente avant terminaison à zéro. L’attente est tout le filet de sécurité — sans elle, le bleu disparaît au moment où le vert est sain, et l’annulation signifie un nouveau cycle de provisionnement au lieu d’un basculement de listener. Vingt minutes est une valeur par défaut raisonnable pour une API interne; trente pour tout ce qui est exposé au client.

Valider contre un vrai endpoint, pas une vérification TCP. Un hook ValidateService qui prouve que l’application sert des réponses correctes attrape plus de régressions de déploiement qu’un hook qui prouve que le port est ouvert.

Pré-chauffer l’ASG vert si l’application a un démarrage lent. Un service JVM qui prend 90 secondes pour préchauffer les caches échouera ValidateService si le timeout du hook est de 60 secondes, même quand le déploiement réussirait autrement. Soit augmenter le timeout, soit faire bloquer le hook jusqu’à ce que le signal de préchauffage soit donné.

Le modèle bleu/vert comporte plus de pièces mobiles que l’en-place — un deuxième ASG, un échange de groupe cible, l’attente avant terminaison — mais c’est la seule forme de déploiement EC2 qui permet d’annuler une régression en moins d’une minute. Référez-vous au guide de référence appspec pour les détails d’ordonnancement des hooks et au runbook d’annulation automatique pour les alarmes.


Stack Harbor opère CodeDeploy + CodePipeline sur les flottes EC2 / ECS / Lambda de production dans le cadre de Gestion AWS et des Environnements infogérés en continu. Si vos déploiements demandent encore une fenêtre de maintenance, commencez par une consultation d’infrastructure.