Mettre à jour le contenu CloudFront : créer une invalidation pour vider le cache edge immédiatement

Vous venez de remplacer un fichier dans S3, mais CloudFront continue de servir l'ancienne version à vos utilisateurs — c'est l'un des pièges les plus fréquents en production, et il arrive systématiquement au pire moment : après un correctif urgent ou une mise à jour critique d'asset. Comprendre pourquoi CloudFront ignore votre nouveau fichier, et comment forcer une invalidation CloudFront pour vider le cache edge, est une compétence opérationnelle fondamentale.

TL;DR — Invalidation CloudFront en bref

SituationAction recommandéeDélai effectif
Fichier unique modifié dans S3Invalidation sur le chemin exact (/images/logo.png)Généralement quelques secondes à quelques minutes
Plusieurs fichiers d'un répertoireInvalidation avec wildcard (/assets/*Idem, propagation vers tous les PoP
Déploiement completInvalidation /* ou versionnage des fichiersIdem — coût à surveiller
Stratégie long termeVersionnage des noms de fichiers (cache busting)Immédiat, sans invalidation

Pricing et quotas d'invalidation : toujours vérifier la documentation officielle AWS — les 1 000 premiers chemins par mois sont gratuits, au-delà la facturation s'applique.

Comment fonctionne le cache CloudFront — ce que l'invalidation doit contourner

CloudFront est un CDN à plusieurs couches. Quand un utilisateur demande https://d1234abcd.cloudfront.net/images/logo.png, la requête arrive sur le Point of Presence (PoP) le plus proche. Si l'objet est en cache et que son TTL n'est pas expiré, le PoP répond directement depuis son cache local — il ne consulte jamais S3. C'est exactement pour ça que remplacer le fichier dans S3 ne change rien immédiatement : le PoP ne sait pas que la source a changé.

graph LR User["Utilisateur"] --> PoP["PoP CloudFront
(cache local)"] PoP -->|"Cache HIT
(TTL valide)"| User PoP -->|"Cache MISS
ou TTL expiré"| S3["Origine S3"] S3 -->|"Nouvel objet"| PoP Invalidation["Invalidation
créée"] -->|"Marque objet expiré
sur tous les PoP"| PoP style Invalidation fill:#ff9900,color:#fff style S3 fill:#3f8624,color:#fff style PoP fill:#1a73e8,color:#fff
  1. Requête utilisateur : arrive sur le PoP CloudFront le plus proche géographiquement.
  2. Cache HIT : si l'objet est en cache et le TTL valide, le PoP sert l'ancienne version directement — S3 n'est jamais consulté.
  3. Cache MISS ou TTL expiré : le PoP remonte vers l'origine S3 (origin fetch), récupère le nouvel objet, le met en cache localement.
  4. Après invalidation : CloudFront marque l'objet comme expiré sur tous les PoP concernés. Le prochain accès déclenche un origin fetch systématique, quelle que soit la valeur du TTL résiduel.
Penser à CloudFront comme à un réseau de bibliothèques locales : chaque PoP garde sa propre copie du livre. Remplacer l'exemplaire à la bibliothèque centrale (S3) ne met pas à jour automatiquement les copies locales. L'invalidation envoie un message à toutes les bibliothèques : 'Jetez votre copie, récupérez la nouvelle à la prochaine demande.'

Un détail important que la documentation ne met pas toujours en avant : CloudFront ne propage pas l'invalidation de manière synchrone vers tous les PoP simultanément. La propagation est asynchrone — certains PoP peuvent encore servir l'ancienne version pendant quelques secondes après le démarrage de l'invalidation. L'API retourne un statut InProgress puis Completed quand tous les PoP sont synchronisés.

Créer une invalidation CloudFront — étape par étape

Étape 1 : Identifier l'ID de votre distribution CloudFront

Avant toute invalidation, vous avez besoin de l'identifiant de distribution. Si vous ne l'avez pas sous la main, listez vos distributions pour le retrouver — c'est aussi l'occasion de confirmer que vous ciblez la bonne distribution, surtout en environnement multi-distribution.

aws cloudfront list-distributions \
  --query 'DistributionList.Items[*].{ID:Id,Domain:DomainName,Comment:Comment}' \
  --output table

Notez l'ID de distribution (format E1ABCDEF2GHIJK) correspondant à votre domaine.

Étape 2 : Créer l'invalidation via AWS CLI

L'invalidation cible des chemins tels qu'ils apparaissent dans l'URL CloudFront, pas des clés S3. Si votre fichier S3 est images/logo.png et que votre distribution sert ce chemin directement, le chemin d'invalidation est /images/logo.png — avec le slash initial obligatoire.

aws cloudfront create-invalidation \
  --distribution-id E1ABCDEF2GHIJK \
  --paths '/images/logo.png'

Pour invalider plusieurs fichiers d'un répertoire :

aws cloudfront create-invalidation \
  --distribution-id E1ABCDEF2GHIJK \
  --paths '/assets/*' '/index.html'

Pour invalider l'intégralité du cache (à utiliser avec discernement — voir la note sur les coûts) :

aws cloudfront create-invalidation \
  --distribution-id E1ABCDEF2GHIJK \
  --paths '/*'

La réponse inclut un InvalidationId et un statut initial InProgress.

Étape 3 : Surveiller la progression de l'invalidation

Une invalidation en cours ne garantit pas que tous les PoP sont déjà mis à jour. Pour les déploiements critiques, vérifiez le statut avant de considérer l'opération terminée — un statut InProgress prolongé peut indiquer un problème de propagation.

aws cloudfront get-invalidation \
  --distribution-id E1ABCDEF2GHIJK \
  --id INVALIDATION_ID

Attendez le statut Completed avant de valider que le contenu mis à jour est bien servi. Vous pouvez aussi lister toutes les invalidations récentes pour auditer l'historique :

aws cloudfront list-invalidations \
  --distribution-id E1ABCDEF2GHIJK \
  --query 'InvalidationList.Items[*].{ID:Id,Status:Status,CreateTime:CreateTime}' \
  --output table

Étape 4 : Créer l'invalidation via la console AWS (alternative)

Si vous préférez l'interface graphique : Console AWS → CloudFront → Distributions → [votre distribution] → Invalidations → Créer une invalidation. Entrez les chemins dans le champ texte, un par ligne. Le comportement est identique à la CLI.

Invalidation CloudFront via l'API — pour les pipelines CI/CD

En production, les invalidations manuelles sont un anti-pattern. Intégrez-les dans votre pipeline de déploiement pour qu'elles se déclenchent automatiquement après chaque mise à jour d'assets.

Voici la politique IAM minimale requise pour qu'un rôle de déploiement puisse créer des invalidations :

🔽 Politique IAM — permission d'invalidation CloudFront
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudfrontInvalidation",
      "Effect": "Allow",
      "Action": [
        "cloudfront:CreateInvalidation",
        "cloudfront:GetInvalidation",
        "cloudfront:ListInvalidations"
      ],
      "Resource": "arn:aws:cloudfront::123456789012:distribution/E1ABCDEF2GHIJK"
    }
  ]
}

Notez que l'ARN CloudFront est global (pas de région dans l'ARN) : arn:aws:cloudfront::<account-id>:distribution/<distribution-id>. C'est une source d'erreur fréquente quand on construit la politique à la main.

Exemple d'intégration dans un script de déploiement shell :

🔽 Script de déploiement avec invalidation automatique
#!/bin/bash
set -e

DISTRIBUTION_ID="E1ABCDEF2GHIJK"
S3_BUCKET="mon-bucket-assets"
LOCAL_ASSETS_DIR="./dist"

# Synchronisation des assets vers S3
aws s3 sync "${LOCAL_ASSETS_DIR}" "s3://${S3_BUCKET}/" \
  --delete \
  --cache-control 'max-age=31536000,immutable'

# Invalidation du cache CloudFront après le déploiement
INVALIDATION_ID=$(aws cloudfront create-invalidation \
  --distribution-id "${DISTRIBUTION_ID}" \
  --paths '/*' \
  --query 'Invalidation.Id' \
  --output text)

echo "Invalidation démarrée : ${INVALIDATION_ID}"

# Attendre la complétion (optionnel pour les pipelines bloquants)
aws cloudfront wait invalidation-completed \
  --distribution-id "${DISTRIBUTION_ID}" \
  --id "${INVALIDATION_ID}"

echo "Invalidation terminée — contenu mis à jour sur tous les PoP."

Diagnostic : 'J'ai créé l'invalidation mais CloudFront sert toujours l'ancienne version'

C'est le scénario classique qui fait perdre du temps. L'invalidation est marquée Completed, vous videz le cache navigateur, et pourtant l'ancienne version persiste. Voici le chemin de diagnostic réel.

graph TD Start["Invalidation Completed
mais ancienne version visible"] --> Q1{"curl -I montre
X-Cache: Hit ?"} Q1 -->|"Oui"| Q2{"Chemin d'invalidation
correct ?"} Q1 -->|"Non — Miss"| BrowserCache["Cache navigateur
— vider le cache local"] Q2 -->|"Non"| FixPath["Corriger le chemin
(slash initial, Origin Path)"] Q2 -->|"Oui"| Q3{"Origin Path configuré
sur la distribution ?"} Q3 -->|"Oui"| OriginPath["Ne pas inclure Origin Path
dans le chemin d'invalidation"] Q3 -->|"Non"| Q4{"Query strings dans
la Cache Policy ?"} Q4 -->|"Oui"| Wildcard["Utiliser wildcard
/fichier* pour invalider
toutes les variantes"] Q4 -->|"Non"| Wait["Attendre propagation
complète — vérifier statut"] style Start fill:#d32f2f,color:#fff style BrowserCache fill:#1a73e8,color:#fff style FixPath fill:#ff9900,color:#fff style OriginPath fill:#ff9900,color:#fff style Wildcard fill:#ff9900,color:#fff style Wait fill:#3f8624,color:#fff

Erreur de chemin d'invalidation — la cause la plus fréquente. Le chemin d'invalidation doit correspondre exactement à l'URL CloudFront, pas à la clé S3. Si votre distribution a un Origin Path configuré (par exemple /prod), le chemin d'invalidation ne doit PAS inclure ce préfixe. CloudFront applique l'Origin Path en interne lors du fetch vers S3 — l'invalidation se fait sur le chemin tel que l'utilisateur le voit.

# Vérifier la configuration Origin Path de votre distribution
aws cloudfront get-distribution \
  --id E1ABCDEF2GHIJK \
  --query 'Distribution.DistributionConfig.Origins.Items[*].{DomainName:DomainName,OriginPath:OriginPath}' \
  --output table

Cache navigateur indépendant de CloudFront — le navigateur a sa propre couche de cache, contrôlée par les en-têtes Cache-Control et Expires que CloudFront transmet depuis S3. Une invalidation CloudFront ne vide pas le cache navigateur. Testez toujours avec curl pour isoler les deux couches :

# Tester directement depuis CloudFront, sans cache navigateur
curl -I 'https://d1234abcd.cloudfront.net/images/logo.png'

Regardez les en-têtes de réponse : X-Cache: Hit from cloudfront indique un cache HIT (invalidation pas encore propagée ou chemin incorrect). X-Cache: Miss from cloudfront ou RefreshHit from cloudfront indique que CloudFront a bien récupéré le nouvel objet depuis S3.

Comportement de cache CloudFront avec Query Strings — si votre distribution est configurée pour inclure les query strings dans la clé de cache, une URL comme /script.js?v=1 et /script.js?v=2 sont traitées comme deux objets distincts. Une invalidation sur /script.js invalide toutes les variantes si vous utilisez un wildcard, mais pas si vous spécifiez le chemin exact sans query string. Vérifiez votre Cache Policy :

aws cloudfront get-distribution \
  --id E1ABCDEF2GHIJK \
  --query 'Distribution.DistributionConfig.DefaultCacheBehavior.CachePolicyId' \
  --output text

Stratégie long terme : le versionnage de fichiers évite les invalidations

Les invalidations ont un coût opérationnel et financier. Au-delà des 1 000 chemins gratuits par mois, chaque chemin supplémentaire est facturé. Mais surtout, une invalidation /* sur une distribution à fort trafic génère un pic d'origin fetches simultanés vers S3 — tous les PoP remontent vers l'origine en même temps pour le prochain accès, ce qui peut saturer votre origine si le trafic est élevé.

La solution structurelle est le cache busting par versionnage de nom de fichier : au lieu de app.js, déployez app.a3f9c2d.js (hash du contenu). Chaque déploiement génère un nouveau nom de fichier — CloudFront le traite comme un nouvel objet, aucune invalidation nécessaire. Seul index.html (qui référence le nouveau nom) doit être invalidé, ou servi avec un TTL court.

graph LR subgraph Deploy["Déploiement"] Build["Build
app.[hash].js"] --> S3Upload["Upload S3"] S3Upload --> InvalidateHTML["Invalider
/index.html uniquement"] end subgraph Cache["Stratégie cache"] Assets["app.[hash].js
max-age=31536000"] -->|"Jamais invalidé
nouveau nom = nouvel objet"| CF["CloudFront"] HTML["index.html
no-cache"] -->|"Invalidé à chaque deploy"| CF end style Assets fill:#3f8624,color:#fff style HTML fill:#ff9900,color:#fff style CF fill:#1a73e8,color:#fff
  1. Assets avec hash (app.[hash].js) : TTL long (max-age=31536000, immutable), jamais invalidés — le nom change à chaque déploiement.
  2. index.html : TTL court ou no-cache, invalidé à chaque déploiement. Un seul chemin à invalider.
  3. Cette approche élimine le risque de pic d'origin fetch et réduit les coûts d'invalidation à presque zéro.

Wrap-up — Mettre à jour le contenu CloudFront sans se faire piéger

Pour forcer la mise à jour du contenu CloudFront après un remplacement de fichier dans S3, créez une invalidation CloudFront ciblant le chemin exact tel qu'il apparaît dans l'URL — pas la clé S3. Vérifiez le statut Completed avant de valider, et utilisez curl -I pour inspecter les en-têtes X-Cache et isoler CloudFront du cache navigateur. Pour les déploiements fréquents, intégrez l'invalidation dans votre pipeline CI/CD et adoptez le versionnage de fichiers pour réduire la dépendance aux invalidations.

Ressources officielles :

Glossaire

TermeDéfinition
InvalidationOpération CloudFront qui marque un ou plusieurs objets en cache comme expirés sur tous les PoP, forçant un origin fetch au prochain accès.
PoP (Point of Presence)Nœud edge du réseau CloudFront qui stocke les copies en cache et répond aux requêtes utilisateurs.
TTL (Time To Live)Durée pendant laquelle un objet est considéré valide dans le cache CloudFront avant qu'un origin fetch soit déclenché.
Origin FetchRequête émise par un PoP CloudFront vers l'origine (S3) pour récupérer une version fraîche d'un objet.
Cache BustingTechnique consistant à inclure un hash ou un numéro de version dans le nom de fichier pour forcer le navigateur et le CDN à traiter chaque déploiement comme un nouvel objet.

Commentaires

Posts les plus consultés de ce blog

Groupes IAM AWS : Pourquoi Attacher les Politiques aux Groupes Plutôt qu'aux Utilisateurs

LSI vs GSI dans DynamoDB : choisir le bon index secondaire

NAT Gateway vs NAT Instance : Quelle solution choisir pour vos instances privées ?