はじめに
新しいリリースが 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 は初期設定でゼロになっている二つの TTL 属性だけを変更し、最小 TTL はゼロのままです。新しい最大値は後で長い有効期間を使えるようにします。
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"
用意された最初のリリースをアップロードし、有効期間を六秒にします。コンテンツタイプ 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 は変更しません。二つのリクエストを続けて実行し、二回目が六秒以内に届くようにします。
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 はコピーの経過時間です。直後のリクエストでは両方ゼロ秒でも構いません。
sleep 7 で六秒のコピーを期限切れにしてから再度リクエストします。
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 でアップロードします。ディストリビューションの最大 TTL 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'
オリジンのメタデータを変更しても、保存済みのレスポンスには遡って反映されません。六秒のコピーが期限切れになるまで待ち、両方のパスをリクエストしてキャッシュに入れます。
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 が付きます。次の二つのステップは、この十五分の有効期間内に完了してください。
用意された 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 です。無効化はキャッシュを変更し、オリジン権限は変更しません。対象限定の更新検証を実行します。

自分の配信リソースだけを削除する
このステップでは、まずディストリビューションを無効にして削除し、次に 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 を設定し、ヒットではオリジン読み取りが増えないことと、短いコピーの期限切れを観察しました。オリジンへの新しいアップロードは、有効なビューアーのコピーを置き換えません。正確なパスの無効化で新しいページを取得し、別のキャッシュと非公開オリジンを維持してから、自分のリソースだけを削除しました。頻繁なリリースではバージョン付きオブジェクト名で新しい内容を選ぶ方法もあります。このラボでは既存パスの更新を実践しました。



