logo hsb.horse
← Retour au blog

Blog

Un script de déploiement pour servir une SPA avec CloudFront + S3

Un script de déploiement concret pour servir une SPA hébergée sur S3 derrière CloudFront. Cet article récapitule les réglages de cache-control, une structure de déploiement de type blue-green et la procédure de rollback.

Publié:

Voici un script de déploiement pragmatique pour servir une SPA avec CloudFront + S3.

Hypothèses

  • Le cloud utilisé est AWS
  • Les artefacts de build frontend sont stockés dans S3 et servis via CloudFront

Architecture de l’infrastructure

S3

  • Versioning du bucket : désactivé
  • Chiffrement : SSE-S3
  • Block Public Access : activé
  • Hébergement statique de site web : désactivé

CloudFront

  • Classe tarifaire : toutes les edge locations
  • Versions HTTP supportées : HTTP/3, 2, 1.1, 1.0

Origin

  • Nom de l’origin : S3-frontend-app
  • Domaine d’origine : <bucket_name>.s3.ap-northeast-1.amazonaws.com
  • Chemin d’origine : /current
  • Accès à l’origine : Origin access control settings

Behavior

Valeur par défaut (*)

  • Origin : S3-frontend-app
  • Viewer protocol policy : HTTP to HTTPS
  • Cache policy : Managed-CacheOptimized
  • Viewer request : CloudFront Functions

Script de déploiement

Je laisse de côté la partie credentials AWS, car elle dépend du contexte d’exécution.

Terminal window
# global vars
readonly S3_BUCKET=""
readonly DIST_DIR=""
function main() {
# BUILD_ID peut aussi être un hash de commit Git
local -r build_id=$(date -Iseconds)
local -r base_uri="s3://$S3_BUCKET"
local -r deploy_uri="$base_uri/builds/$build_id"
local -r previous_uri="$base_uri/previous"
local -r current_uri="$base_uri/current"
# Déployer sous builds/
# js,css
aws s3 sync "$DIST_DIR" "$deploy_uri" \
--exclude "*" \
--include "*.js" \
--include "*.css" \
--metadata-directive "REPLACE"
--cache-control "public,max-age=31536000,immutable"
# sauf js,css,html
aws s3 sync "$DIST_DIR" "$deploy_uri" \
--exclude "*.js" \
--exclude "*.css" \
--exclude "*.html" \
--metadata-directive "REPLACE" \
--cache-control "public,max-age=1,stale-while-revalidate=604800"
# html
aws s3 sync "$DIST_DIR" "$deploy_uri" \
--exclude "*" \
--include "*.html" \
--metadata-directive "REPLACE" \
--cache-control "no-cache"
# Sauvegarder les assets actuellement servis de current vers previous
aws s3 sync "$current_uri" "$previous_uri" --delete --exact-timestamp
# Copier ce build vers current et le publier
aws s3 sync "$deploy_uri" "$current_uri" --delete --exact-timestamp
}

Pourquoi définir Cache-Control dans cet ordre

Dans l’idéal, j’aimerais définir le cache-control à la toute dernière étape, aws s3 sync "$deploy_uri" "$current_uri", mais lors d’une synchronisation S3 vers S3 avec --metadata-directive "REPLACE", AWS cesse d’inférer le Content-Type, et tout finit en binary/octet-stream.

Lors d’une synchronisation depuis le local vers S3, l’inférence du Content-Type est conservée. Cette organisation est donc un compromis pratique.

Script de rollback

En cas de problème, il suffit de restaurer previous vers current.

Terminal window
# global vars
readonly S3_BUCKET=""
function revert() {
local -r base_uri="s3://$S3_BUCKET"
local -r previous_uri="$base_uri/previous"
local -r current_uri="$base_uri/current"
# copy previous to current
aws s3 sync "$previous_uri" "$current_uri" --delete --exact-timestamp
}

Résumé

Pour servir une SPA avec CloudFront + S3, combiner des réglages de cache-control adaptés avec une structure de déploiement de type blue-green permet un flux de déploiement plus sûr et plus efficace.

En conservant l’historique sous builds et en gérant la version active et la précédente avec current et previous, on peut revenir en arrière rapidement en cas de problème.