Skip to content

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 :

  1. candidate-* → expire après 3 jours (par âge)
  2. 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'ECRImagePullBackOff 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 ImagePullBackOff aprè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 develop post-merge vert (le promote pose deployed-<sha>, write-back OK), puis terraform apply -target=module.ecr (persistent) pour activer la policy. start/get-lifecycle-policy-preview de la policy appliquée → deployedExpiring: [] (l'image servie n'est pas dans l'ensemble à expirer). #104 fermée.