ADR 021 — Lifecycle ECR : artefacts éphémères par âge, images déployées par count + priorité de règle (2026-06-20)
Statut
Accepté et validé live le 2026-06-20. La policy à 3 règles a été appliquée
(terraform apply -target=module.ecr, stack persistent) et le lifecycle-policy-preview
de la policy appliquée confirme deployedExpiring: [] : l'image servie par ArgoCD n'est
jamais dans l'ensemble à expirer (expiring: 4 = vieilles candidate de plus de 3 jours,
nettoyage normal de la règle 2). Le risque d'ImagePullBackOff après une pause CI de plus
de 3 jours est éliminé.
Contexte
Le promote par digest (ADR 005 §, #65) ne crée pas une image « release » distincte :
il pose le tag de déploiement sur le même digest que l'image candidate déjà scannée.
Conséquence vérifiée en production (2026-06-20) : toutes les images du repo portent un
tag candidate-*, y compris celle servie par ArgoCD.
La lifecycle policy d'alors avait deux règles :
candidate-*→ expire après 3 jours (par âge)tagStatus: any→ garde 5 (par count)
ECR traite chaque image par la règle de plus haute priorité dont elle matche le filtre
de tag. Comme toute image porte candidate-, la règle 1 réclame tout et la règle 2
(any keep 5) ne s'applique jamais : seule l'âge 3 jours gouverne. Le lifecycle-policy-preview
le confirmait (expiring: 0 avec 28 images, toutes candidate de moins de 3 jours).
Le risque : l'image déployée hérite du tag candidate-*, donc elle est soumise à
l'expiration 3 jours. Tant qu'on merge plus souvent que tous les 3 jours, le tag déployé
reste frais. Mais après une pause CI > 3 jours, l'image référencée par
k8s/base/kustomization.yaml peut être purgée d'ECR → ImagePullBackOff au prochain
aws-start. Le keep 5 ne protège rien (la règle candidate réclame l'image en premier).
Décision
Donner à l'image promue un préfixe de tag distinct (deployed-), non couvert par la
règle d'âge, et une règle lifecycle dédiée de plus haute priorité qui la conserve par
count. La sémantique « la règle prioritaire gagne » devient un bouclier : un digest
portant à la fois candidate- et deployed- est traité par la règle deployed- (keep N),
donc soustrait à l'expiration 3 jours.
Lifecycle en trois règles (l'ordre = la priorité) :
| Priorité | Filtre | Action | Rôle |
|---|---|---|---|
| 1 | deployed-* |
keep last 5 (imageCountMoreThan) |
protège les images servies (courante + 4 pour rollback) |
| 2 | candidate-* |
expire après 3 jours (sinceImagePushed) |
nettoie les artefacts intermédiaires |
| 3 | any |
keep last 5 (imageCountMoreThan) |
filet pour tout tag orphelin (contrainte ECR : la règle any doit être la moins prioritaire) |
Côté pipeline (.gitlab-ci.yml) : le promote pose deployed-$CI_COMMIT_SHA (au lieu du sha
nu), et le write-back GitOps (kustomize edit set image) pointe ce tag → ArgoCD pull
:deployed-<sha>.
Conséquences
- L'image courante est toujours le
deployed-le plus récent, donc toujours protégée, quelle que soit la durée d'une pause CI. - On garde 5 images déployées : rollback possible sur les 4 versions précédentes. Au-delà de
5 déploiements, les plus anciennes
deployed-sont expirées par la règle 1 (voulu). - Les
candidate-*continuent d'expirer à 3 jours : le volume reste borné. - Transition naturelle : les anciennes images taguées en sha nu (avant ce changement)
restent capées par le filet
any/ la règle d'âge ; aucune migration manuelle.
Alternatives écartées
- Allonger l'âge candidate (3j → 14j) : palliatif, n'enlève pas le couplage de fond et accumule davantage d'images.
- Ne rien faire / documenter le risque : laisse le piège
ImagePullBackOffaprès une pause, pour un gain nul.
Validation
- Hors infra :
terraform fmt,tfsec(0 HIGH),glab ci lint,mkdocs build. - Live (2026-06-20) : pipeline
developpost-merge vert (le promote posedeployed-<sha>, write-back OK), puisterraform apply -target=module.ecr(persistent) pour activer la policy.start/get-lifecycle-policy-previewde la policy appliquée →deployedExpiring: [](l'image servie n'est pas dans l'ensemble à expirer). #104 fermée.