logo hsb.horse
← Zur Blog-Übersicht

Blog

Ein Deployment-Skript für eine SPA mit CloudFront + S3

Ein praxistaugliches Deployment-Skript für eine SPA, die aus S3 hinter CloudFront ausgeliefert wird. Dieser Artikel fasst Cache-Control-Einstellungen, eine Blue-Green-ähnliche Deployment-Struktur und den Rollback-Ablauf zusammen.

Veröffentlicht:

Hier ist ein praxistaugliches Deployment-Skript für die Auslieferung einer SPA über CloudFront + S3.

Voraussetzungen

  • Als Cloud-Anbieter wird AWS verwendet
  • Die Frontend-Build-Artefakte liegen in S3 und werden über CloudFront ausgeliefert

Infrastruktur

S3

  • Bucket-Versionierung: deaktiviert
  • Verschlüsselung: SSE-S3
  • Block Public Access: aktiviert
  • Statisches Website-Hosting: deaktiviert

CloudFront

  • Preis-Klasse: alle Edge-Standorte verwenden
  • Unterstützte HTTP-Versionen: HTTP/3, 2, 1.1, 1.0

Origin

  • Origin-Name: S3-frontend-app
  • Origin-Domain: <bucket_name>.s3.ap-northeast-1.amazonaws.com
  • Origin-Pfad: /current
  • Origin-Zugriff: Origin access control settings

Behavior

Standard (*)

  • Origin: S3-frontend-app
  • Viewer Protocol Policy: HTTP to HTTPS
  • Cache Policy: Managed-CacheOptimized
  • Viewer Request: CloudFront Functions

Deployment-Skript

Die AWS-Credentials lasse ich aus, weil sie vom jeweiligen Ausführungskontext abhängen.

Terminal window
# global vars
readonly S3_BUCKET=""
readonly DIST_DIR=""
function main() {
# BUILD_ID kann auch ein Git-Commit-Hash sein
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"
# Deployment unter 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"
# alles außer 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"
# Aktuell ausgelieferte Assets von current nach previous sichern
aws s3 sync "$current_uri" "$previous_uri" --delete --exact-timestamp
# Diesen Build nach current kopieren und veröffentlichen
aws s3 sync "$deploy_uri" "$current_uri" --delete --exact-timestamp
}

Warum Cache-Control in dieser Reihenfolge gesetzt wird

Eigentlich würde ich Cache-Control gern im letzten Schritt setzen, also bei aws s3 sync "$deploy_uri" "$current_uri". Wenn man aber bei einem S3-zu-S3-Sync --metadata-directive "REPLACE" verwendet, hört AWS auf, den Content-Type abzuleiten, und alles landet als binary/octet-stream.

Bei einem Sync vom lokalen Dateisystem nach S3 bleibt die Content-Type-Erkennung erhalten. Daher ist diese Reihenfolge ein praktikabler Kompromiss.

Rollback-Skript

Wenn ein Problem auftritt, wird previous nach current zurückkopiert.

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
}

Zusammenfassung

Wenn man eine SPA mit CloudFront + S3 ausliefert, ergibt die Kombination aus passenden Cache-Control-Einstellungen und einer Blue-Green-ähnlichen Deployment-Struktur einen sicheren und effizienten Deployment-Ablauf.

Mit Build-Historie unter builds sowie current und previous für aktive und vorherige Versionen lässt sich bei Problemen schnell zurückrollen.