logo hsb.horse
← Voltar para o índice do blog

Blog

Um script de deploy para servir uma SPA com CloudFront + S3

Um script de deploy prático para servir uma SPA hospedada em S3 atrás do CloudFront. Este artigo organiza as configurações de cache-control, uma estrutura de deploy no estilo blue-green e o procedimento de rollback.

Publicado:

Este é um script de deploy prático para servir uma SPA com CloudFront + S3.

Premissas

  • O provedor de nuvem é a AWS
  • Os artefatos de build do frontend ficam no S3 e são servidos pelo CloudFront

Estrutura da infraestrutura

S3

  • Versionamento do bucket: desativado
  • Criptografia: SSE-S3
  • Block Public Access: ativado
  • Static website hosting: desativado

CloudFront

  • Classe de preço: usar todas as edge locations
  • Versões HTTP suportadas: HTTP/3, 2, 1.1, 1.0

Origin

  • Nome do origin: S3-frontend-app
  • Domínio do origin: <bucket_name>.s3.ap-northeast-1.amazonaws.com
  • Caminho do origin: /current
  • Acesso ao origin: Origin access control settings

Behavior

Padrão (*)

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

Script de deploy

Estou omitindo a parte de credenciais da AWS porque isso varia de acordo com o ambiente de execução.

Terminal window
# global vars
readonly S3_BUCKET=""
readonly DIST_DIR=""
function main() {
# BUILD_ID também pode ser um 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"
# Fazer deploy dentro de 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"
# tudo exceto 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"
# Fazer backup dos assets atualmente servidos de current para previous
aws s3 sync "$current_uri" "$previous_uri" --delete --exact-timestamp
# Copiar este build para current e publicá-lo
aws s3 sync "$deploy_uri" "$current_uri" --delete --exact-timestamp
}

Por que definir o Cache-Control nessa ordem

Idealmente, eu gostaria de definir o cache-control na última etapa, aws s3 sync "$deploy_uri" "$current_uri", mas quando você faz sync de S3 para S3 com --metadata-directive "REPLACE", a AWS deixa de inferir o Content-Type, e tudo acaba tratado como binary/octet-stream.

Quando o sync vai do ambiente local para o S3, a inferência de Content-Type é preservada. Por isso, essa organização acaba sendo um compromisso prático.

Script de rollback

Se algo der errado, restaure previous em 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
}

Resumo

Ao servir uma SPA com CloudFront + S3, combinar regras adequadas de cache-control com uma estrutura de deploy no estilo blue-green resulta em um fluxo mais seguro e eficiente.

Mantendo o histórico em builds e gerenciando a versão atual e a anterior com current e previous, fica fácil fazer rollback rapidamente quando necessário.