콘텐츠 캐시 제어와 업데이트 무효화

AWSBeginner
지금 연습하기

소개

새 릴리스가 S3에 올바르게 저장되어도 배포는 이전 캐시 사본을 제공할 수 있습니다. 캐시 유효 기간을 설정하고 재사용과 만료를 관찰한 뒤, 업데이트한 페이지의 경로만 무효화합니다. 두 번째 객체의 캐시와 오리진의 비공개 상태는 유지합니다.

먼저 비공개 S3 콘텐츠 배포 실습을 완료하세요. 이 독립 VM에는 새로운 비공개 버킷, OAC, 배포 및 일치하는 버킷 정책이 준비되어 있습니다. 이전 VM의 리소스는 재사용하지 않습니다. 처음에는 캐시가 비활성화되어 있고 뷰어 요청과 무효화 요청이 없습니다. 준비된 페이지 파일로 캐시 동작에 집중할 수 있습니다. 위쪽 AWS View에서 실제 상태를 보고 아래쪽 Terminal에서 명령을 실행하세요. 개인 AWS 계정이나 공개 도메인은 필요하지 않습니다.

자격증 학습 목표

자격증 시험 과제 실습 내용
Solutions Architect – Associate (SAA-C03) 과제 3.4 CloudFront 엣지 콘텐츠 배포와 캐시 유효 기간이 오리진 읽기에 미치는 영향을 실습합니다.

실습 개요

개념도: 캐시된 릴리스를 재사용한 다음 정확한 경로만 무효화하여 최신 오리진 콘텐츠를 가져오면서 다른 객체의 캐시를 유지합니다.

캐시 재사용과 만료 관찰

이 단계에서는 짧은 유효 기간을 설정하여 캐시 적중과 오리진을 다시 읽는 요청을 구별합니다.

캐시는 이후 요청에 사용할 사본을 저장합니다. TTL은 초 단위의 유효 기간입니다. 객체의 Cache-Control: max-age가 기간을 제공하고, 배포의 최소 및 최대 TTL이 범위를 제한합니다. 오리진이 기간을 지정하지 않으면 기본 TTL을 사용합니다. 여기서는 일반 배포 설정을 사용하며 별도 캐시 정책을 만들지 않습니다.

준비된 배포를 찾으세요. --query는 실습 주석이 있는 배포를 선택하고, $(...)는 ID나 도메인을 셸 변수에 저장합니다. Terminal을 열어 두세요. 무관한 참조 버킷을 확인하되 변경하지 마세요.

cd /home/labex/project
DIST_ID=$(aws cloudfront list-distributions \
  --query "DistributionList.Items[?Comment=='labex-n03:private-content'].Id | [0]" \
  --output text)
DIST_DOMAIN=$(aws cloudfront get-distribution \
  --id "$DIST_ID" \
  --query Distribution.DomainName \
  --output text)
aws s3api list-buckets \
  --query 'Buckets[].Name'

현재 구성과 ETag를 읽습니다. ETag는 안전한 업데이트에 필요한 버전 식별자입니다. >는 출력을 파일에 저장합니다. sed는 준비된 구성에서 값이 0인 두 TTL 속성만 변경하며 최소 TTL은 0으로 유지됩니다. 새 최대값은 이후 더 긴 기간을 허용합니다.

aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query DistributionConfig > current-config.json
ETAG=$(aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query ETag \
  --output text)
sed -e 's/"DefaultTTL": 0/"DefaultTTL": 6/'   -e 's/"MaxTTL": 0/"MaxTTL": 3600/'   current-config.json > cached-config.json
aws cloudfront update-distribution \
  --id "$DIST_ID" \
  --if-match "$ETAG" \
  --distribution-config file://cached-config.json
aws cloudfront wait distribution-deployed \
  --id "$DIST_ID"

준비된 첫 릴리스를 업로드하고 유효 기간을 6초로 설정합니다. 콘텐츠 유형 text/html을 유지하세요.

aws s3api put-object \
  --bucket labex-n03-content \
  --key index.html \
  --body index.html \
  --content-type text/html \
  --cache-control 'max-age=6'

curl --include는 응답 헤더와 본문을 표시합니다. --resolve는 실제 배포 이름을 이 VM의 뷰어 엔드포인트에 연결하며 시스템 DNS를 변경하지 않습니다. 두 요청을 연달아 실행하여 두 번째가 6초 안에 도착하도록 합니다.

curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"
curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"

첫 번째는 X-Cache: Miss from cloudfront, 두 번째는 Hit from cloudfront이며 모두 Release one을 반환합니다. 적중은 저장된 바이트를 재사용하며 오리진을 다시 읽지 않습니다. Age는 사본의 나이를 나타내므로 즉시 실행한 요청은 모두 0초일 수 있습니다.

sleep 7로 6초 사본을 만료시킨 뒤 다시 요청합니다.

sleep 7
curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"

다시 미스가 발생하고 같은 페이지를 반환해야 합니다. 만료는 다음 요청이 오리진을 다시 읽게 하며 S3 객체를 삭제하지 않습니다. AWS View에서 실제 요청과 오리진 읽기 횟수를 확인하고 검증을 실행한 뒤 진행하세요.

만료 후 예시: 실제 페이지 요청은 미스, 적중, 미스이며 오리진을 두 번 읽었습니다.

캐시 사본 뒤의 업데이트 관찰

이 단계에서는 새 릴리스를 업로드하고 기존 캐시가 이전 콘텐츠를 반환하는 이유를 관찰합니다.

더 긴 기간으로 현상을 쉽게 관찰합니다. 첫 페이지를 max-age=900으로 다시 업로드하고 독립 객체 stable.txt를 max-age=3600으로 업로드하세요. 배포의 최대값 3600은 두 값을 모두 허용합니다.

aws s3api put-object \
  --bucket labex-n03-content \
  --key index.html \
  --body index.html \
  --content-type text/html \
  --cache-control 'max-age=900'
aws s3api put-object \
  --bucket labex-n03-content \
  --key stable.txt \
  --body stable.txt \
  --content-type text/plain \
  --cache-control 'max-age=3600'

오리진 메타데이터를 변경해도 이미 저장된 응답에 소급 적용되지 않습니다. 이전 6초 사본이 만료되기를 기다린 뒤 두 경로를 요청하여 캐시를 채웁니다.

sleep 7
curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"
curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/stable.txt"

두 요청 모두 미스이며 현재 오리진 객체를 읽습니다. 이제 페이지는 Cache-Control: max-age=900을 포함합니다. 이 15분 유효 기간 안에 다음 두 단계를 완료하세요.

준비된 release-two.html에는 수정된 페이지가 있습니다. 같은 객체 키로 업로드하고 유형과 기간을 유지하세요. 인증된 S3 CLI로 객체를 origin-release.html에 읽어 실제 바이트를 확인합니다.

cat release-two.html
aws s3api put-object \
  --bucket labex-n03-content \
  --key index.html \
  --body release-two.html \
  --content-type text/html \
  --cache-control 'max-age=900'
aws s3api get-object \
  --bucket labex-n03-content \
  --key index.html origin-release.html
cat origin-release.html

오리진에는 Release two가 있습니다. 뷰어에서 같은 경로를 요청합니다.

curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"

적중하면서 Release one을 반환해야 합니다. 업로드는 성공했지만 뷰어 사본은 자체 TTL에 따라 여전히 유효합니다. 이것은 오리진 권한 오류와 다릅니다. 이전 사본 검증을 실행하세요.

무효화 전 예시: 가장 최근 페이지 요청은 적중이며, 독립 객체는 캐시되어 오리진을 한 번만 읽었습니다.

업데이트한 페이지만 무효화

이 단계에서는 독립 객체의 캐시를 지우지 않고 만료 전에 새 릴리스를 표시합니다.

**무효화(invalidation)**는 일치하는 객체를 배포 캐시에서 제거합니다. 경로는 /로 시작하는 뷰어 경로이며 버킷 이름이나 로컬 파일 경로가 아닙니다. 정확히 /index.html을 사용하세요. /*는 다른 객체도 불필요하게 무효화합니다.

요청을 생성합니다. CLI의 --paths 단축 형식으로 배치를 제공하고 쿼리로 생성된 ID를 변수에 저장합니다.

INVALIDATION_ID=$(aws cloudfront create-invalidation \
  --distribution-id "$DIST_ID" \
  --paths '/index.html' \
  --query Invalidation.Id \
  --output text)
aws cloudfront wait invalidation-completed \
  --distribution-id "$DIST_ID" \
  --id "$INVALIDATION_ID"
aws cloudfront get-invalidation \
  --distribution-id "$DIST_ID" \
  --id "$INVALIDATION_ID" \
  --query 'Invalidation.{Status:Status,Paths:InvalidationBatch.Paths.Items}'

Completed와 /index.html을 확인합니다. 배치 완료만으로 사용자에게 전달된 바이트를 입증할 수 없습니다. 페이지를 두 번 요청하여 새 콘텐츠와 재사용을 확인합니다.

curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"
curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/index.html"

첫 번째는 미스와 Release two, 다음은 적중과 같은 새 페이지여야 합니다. 첫 요청은 현재 오리진 바이트를 가져오고 두 번째는 새 사본을 재사용합니다.

독립 객체를 요청하고 익명 오리진 직접 접근 테스트를 반복합니다.

curl --fail --include --noproxy '*'   --resolve "${DIST_DOMAIN}:8082:127.0.0.1"   "http://${DIST_DOMAIN}:8082/stable.txt"
curl --noproxy '*' --output /dev/null   --write-out 'Anonymous origin: HTTP %{http_code}\n'   http://127.0.0.1:5000/labex-n03-content/index.html

stable.txt는 원래 콘텐츠로 계속 적중하며 오리진 읽기가 추가되지 않아야 합니다. 익명 S3 접근도 계속 HTTP 403이어야 합니다. 무효화는 캐시를 변경하며 오리진 권한을 변경하지 않습니다. 대상 업데이트 검증을 실행하세요.

정확한 무효화 후 예시: 페이지는 미스 다음 적중이며, stable.txt는 적중을 유지하고 오리진 읽기는 한 번뿐입니다.

자신의 배포 리소스만 제거

이 단계에서는 먼저 배포를 비활성화하고 삭제한 다음 OAC와 S3 콘텐츠를 삭제합니다. 참조 버킷은 유지하세요.

CloudFront는 ETag를 구성의 버전 토큰으로 사용합니다. 현재 구성과 ETag를 가져오고 토큰을 추측하지 마세요.

배포를 삭제하기 전에 OAC ID를 읽습니다. 이 변수는 정리할 정확한 제어를 보관합니다.

OAC_ID=$(aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query 'DistributionConfig.Origins.Items[0].OriginAccessControlId' \
  --output text)

이제 현재 구성을 저장하고 ETag를 가져옵니다.

aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query DistributionConfig \
  --output json > distribution-current.json
DIST_ETAG=$(aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query ETag \
  --output text)

이 실습의 현재 구성에서 배포의 Enabled는 true인 유일한 동일 이름 속성입니다. 일반적인 sed 치환으로 비활성 사본을 만들면서 오리진 설정을 유지합니다.

sed 's/"Enabled": true/"Enabled": false/' distribution-current.json > distribution-disabled.json
aws cloudfront update-distribution \
  --id "$DIST_ID" \
  --if-match "$DIST_ETAG" \
  --distribution-config file://distribution-disabled.json

비활성 구성이 배포되기를 기다립니다. AWS의 구성 전파에는 시간이 걸릴 수 있습니다. 이 실습은 전 세계 배포 지연을 측정하지 않습니다.

aws cloudfront wait distribution-deployed \
  --id "$DIST_ID"

업데이트하면 ETag가 변경됩니다. 비활성 배포를 삭제하기 전에 최신 토큰을 읽습니다.

DIST_ETAG=$(aws cloudfront get-distribution-config \
  --id "$DIST_ID" \
  --query ETag \
  --output text)
aws cloudfront delete-distribution \
  --id "$DIST_ID" \
  --if-match "$DIST_ETAG"

OAC 자체의 ETag로 삭제합니다. 배포 ID와 OAC ID는 서로 다른 리소스를 나타냅니다.

OAC_ETAG=$(aws cloudfront get-origin-access-control \
  --id "$OAC_ID" \
  --query ETag \
  --output text)
aws cloudfront delete-origin-access-control \
  --id "$OAC_ID" \
  --if-match "$OAC_ETAG"

실습의 두 객체와 버킷만 삭제합니다.

aws s3api delete-object \
  --bucket labex-n03-content \
  --key index.html
aws s3api delete-object \
  --bucket labex-n03-content \
  --key stable.txt
aws s3api delete-bucket \
  --bucket labex-n03-content
aws s3api list-buckets \
  --query 'Buckets[].Name'

참조 버킷은 남고 콘텐츠 버킷은 없어야 합니다. AWS View에는 콘텐츠 배포가 없고 참조 객체만 표시되어야 합니다. 정리 검증을 실행하세요. 실패한 API 요청은 삭제의 증거가 아닙니다.

정리 후 예시: 무관한 참조만 남으며 이전 요청과 오리진 읽기 기록은 계속 표시됩니다.

요약

배포 TTL 범위와 객체 Cache-Control을 설정하고, 적중이 오리진 읽기를 늘리지 않는 것을 관찰하며 짧은 사본을 만료시켰습니다. 오리진에 새 버전을 업로드해도 아직 유효한 뷰어 사본은 대체되지 않습니다. 정확한 경로 무효화로 새 페이지를 가져오면서 다른 객체의 캐시와 비공개 오리진을 유지한 뒤 자신의 리소스만 삭제했습니다. 자주 릴리스할 때는 버전이 포함된 객체 이름으로 새 콘텐츠를 선택할 수도 있습니다. 여기서는 기존 경로 업데이트를 실습했습니다.