Le déploiement continu avec ECS

Benoît Fabre
papernest
Published in
4 min readOct 17, 2017

Dans le mode du lean et de l’extreme agilité, les fonctionnalités s’enchainent et doivent être livrées en production le plus rapidement possible afin d’en valider les hypothèses d’utilisation et d’itérer.

Dans un tel contexte, être capable de déployer du code vite et sans risque est devenu vital. Une régression en pré-production, et c’est 2 heures de perdues et des tests à re-passer, puis on est déjà vendredi, la mise en prod est décalée à la semaine prochaine ! Le PO fulmine, et notre progression est ralentie.

Alors pour aller vite, éviter les régressions, et ne plus avoir peur des mises en production, un seul remède : faciliter le process de tests et de mise en production, et le répéter le plus souvent possible. Pour cela, un seul chemin possible : le déploiement continu.

Le déploiement continu, c’est bien plus qu‘un script de mise en production automatisée. Mais la base reste quand même d’être capable de mettre à jour la production à partir d’un outil de gestion CI/CD (nous utilisons CircleCI). Du coup, je vous propose de voir comment on fait cela chez Papernest en déployant sur Amazon ECS.

Amazon ECS

Chez Papernest, on aime beaucoup Docker. Travailler dans des conteneurs nous facilite la vie à bien des égards : gestion des environnements et des dépendances, architectures micro-services, gestion des tests… Et pour assurer la gestion des clusters, la scalabilité, …, on utilise Amazon ECS.

Vous le savez certainement, ECS, ce sont 4 concepts de base :
- les définitions de tâches qui définissent la manière dont un ou plusieurs conteneurs doivent fonctionner (image à utiliser, mémoire allouée, ports ouverts, gestion des logs, etc.)
- les services, qui ont la charge de créer une ou plusieurs tâches “réelles” à partir d’une définition, et de les placer sur un ou plusieurs serveurs
- les tâches, qui sont donc un ensemble de conteneurs en action
- ECR, qui est un stockage d’images de conteneurs, prêtes à être utilisées dans les définitions de tâches (l’équivalent du Docker Store en version AWS)

Et en pratique, comment est-ce qu’on déploie du code ?

Pour modifier le code en production sur ECS, l’idéal est de créer une nouvelle définition de tâche prenant en compte le nouveau code. Le process de déploiement est donc le suivant :

  1. Une fois la nouvelle image de conteneur prête a être déployée, on la pousse sur ECR et on ajoute un tag avec le numéro du build
  2. On modifie la définition de tâche pour qu’elle référence le nouveau build poussé sur ECR
  3. On modifie le(s) service(s) qui utilisent cette définition de tâche, pour qu’ils référencent la nouvelle version de la définition
  4. ECS entre en action ! Les services se chargent seuls de créer les nouvelles tâches. Celles-ci sont référencées auprès du load balancer, qui commence à y diriger du trafic. Si tout se passe bien, les anciennes tâches sont terminées proprement, et le déploiement se fini, sans perte de trafic !
Etape du déploiement sur ECS

Maintenant que l’on est clair sur le principe, il est temps de mettre les mains dans le cambouis ! Pour l’étape 1, pousser une image sur ECR, pas de difficulté, cela se fait en 2 commandes :

login="$(aws ecr get-login --no-include-email)"
${login}
docker tag my_docker "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/my_ecr_path:
v_${BUILD_NUM}"
docker push "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/my_ecr_path:
v_${BUILD_NUM}"

Ensuite, il faut modifier chaque définition de tâche, et chaque service y faisant référence. Pour cela nous avons développé un petit script bash qui va récupérer le JSON décrivant chaque définition de tâche à l’aide de aws ecs describe-task-definition, qui modifie l‘image ECR utilisée, et qui enregistre cette nouvelle définition à l’aide de aws ecs register-task-definition. Pour les services, même punition avec list-services, describe-services et update-service.

#!/bin/bash
CLUSTER=$1
REPO=$2
TASKS=(task1 task2)

for task in ${TASKS[*]}; do
TASK_NAME=${CLUSTER}-$task
FILE_NAME=${TASK_NAME}.json

#Get task description and clean it
aws ecs describe-task-definition --task-definition ${TASK_NAME}\
| jq '.taskDefinition' \
| jq 'del(.status)' \
| jq 'del(.revision)' \
| jq 'del(.taskDefinitionArn)' \
| jq 'del(.requiresAttributes)' \
> temp_file_1.json

#Modify repository build number
sed -e "s;${REPO}/\([a-z]*\):\([a-zA-Z_0-9]*\)
;${REPO}/\1:v_${BUILD_NUM};g" \
temp_file_1.json > ${FILE_NAME}

#Register the new repository build number in the task definition
NEW_TASK_DEF=`aws ecs register-task-definition --cli-input-json
file://${FILE_NAME} | jq -r '.taskDefinition.taskDefinitionArn'`
NEW_TASK_DEF_FAMILY=`sed -e "s;\(.*\):\([0-9]*\);\1;g" <<<
${NEW_TASK_DEF}`

#Get services and update them
SERVICES=`aws ecs list-services --cluster ${CLUSTER} |
jq -r '.serviceArns[]'`
for service in ${SERVICES}; do TASK_DEF=`aws ecs describe-services --services $service
--cluster ${CLUSTER} | jq -r '.services|.[0]|.taskDefinition'`
TASK_DEF_FAMILY=`sed -e "s;\(.*\):\([0-9]*\);\1;g" <<<
${TASK_DEF}`
if [ "$NEW_TASK_DEF_FAMILY" == "$TASK_DEF_FAMILY" ]; then
aws ecs update-service --cluster ${CLUSTER} \
--service $service --task-definition ${NEW_TASK_DEF}
fi
done
done

Et voilà ! La dernière étape est complètement gérée par ECS, il n’y a plus qu’a attendre les changements.

Quelques réflexions supplémentaires

Cette méthode permet un déploiement propre, sans perte de trafic (ou “zero down-time deployment”), et sécurisé par des vérifications de santé (“health-check”) qui permettent de ne pas basculer le trafic sur la nouvelle version tant qu’elle ne répond pas correctement.

Le gros plus de cette méthode est que vous allez déployer une image Docker. Ainsi, vous pouvez construire votre pipeline de déploiement continu afin que cette image soit construite, puis testée, avant d’être déployée. L’avantage est que vous déployez exactement l’image que vous venez de tester ! Vous avez donc une garantie sur le code qui arrive en production.

Enfin, vous bénéficiez de tous les services AWS associés. Songez par exemple que vous souhaitez évoluer vers un véritable déploiement Blue/Green dans lequel la nouvelle production est déployée sur une URL spécifique et validée manuellement avant de modifier le routing du trafic : aucun problème. Il vous suffit d’adapter la situation précédente à deux services (un bleu, un vert) et de configurer votre équilibreur de charge en conséquence. Bref, vous profitez pleinement de toute la puissance de ECS !

--

--