本番デプロイが反映されない!Lambda エイリアス + CloudFront の落とし穴と GitHub Actions で完全自動化した話

目次

デプロイ成功なのに本番が更新されない問題

Next.js を AWS Lambda コンテナでデプロイしている環境で、GitHub Actions のワークフローは成功しているのに本番サイトが一向に更新されない。こんな経験ありませんか?

実はこれ、Lambda エイリアスと CloudFront を組み合わせた構成で起こりがちな「見えない罠」です。デプロイパイプラインが Lambda の $LATEST しか更新せず、CloudFront が参照しているエイリアスは古いバージョンのままという状況が発生します。

この記事では、Lambda エイリアス構成で本番反映されない問題の原因特定から、GitHub Actions での完全自動化までを実装ベースで解説します。

問題が起きていた構成

まず、問題が発生していた環境の構成を整理します。

システム構成図

ユーザーからのリクエストは以下の経路で処理されていました:

  • Route 53(DNS)
  • CloudFront(CDN)
  • Lambda エイリアス(prd
  • Lambda バージョン(固定)

Next.js アプリケーションは Docker コンテナイメージとして Lambda にデプロイされ、CloudFront 経由でカスタムドメインで配信される構成です。

なぜエイリアス構成なのか

Lambda の Provisioned Concurrency(コールドスタート対策機能)を使うには、$LATEST ではなくバージョンを発行してエイリアスに紐づける必要があります。この構成自体は正しい設計ですが、デプロイフローがそれに対応していませんでした。

あわせて読みたい
Create an alias for a Lambda function - AWS Lambda Create a Lambda function alias to use as a pointer to a specific function version.

何が起きていたのか:二重の罠

GitHub Actions でデプロイすると update-function-code コマンドで Lambda の $LATEST が更新されます。ここまでは問題ありません。

しかし CloudFront のオリジン設定は $LATEST ではなく、エイリアス prd を参照していました。このエイリアスは特定のバージョン(例:バージョン 72)に固定されているため、$LATEST がいくら更新されてもユーザーには古いコードが返り続けます。

トラップ1:エイリアスの更新漏れ

# GitHub Actions がやっていること
CloudFront → prd エイリアス → バージョン72(古い) ← ユーザーが見ている
GitHub Actions → $LATEST(最新)                      ← ここにしかデプロイされない

GitHub Actions のワークフローには publish-versionupdate-alias も含まれていなかったため、エイリアスは古いバージョンを指したままでした。

トラップ2:CloudFront の長時間キャッシュ

さらに CloudFront のデフォルトビヘイビアで TTL が 24 時間に設定されていたため、仮にエイリアスを手動で更新しても、キャッシュが切れるまで反映されません。

前任者は手動でバージョン発行とエイリアス更新を行っていたと推測されますが、Invalidation 履歴が 0 件だったことから、TTL 切れを待つ運用をしていた可能性もあります。

原因特定までの調査フロー

実際に問題を特定するまでの調査手順を再現します。

  1. main ブランチにマージ → GitHub Actions 成功 → 本番サイトに反映されない
  2. CloudFront のキャッシュを疑う → Invalidation 実行 → 反映されない
  3. Lambda コンソールで確認 → $LATEST は最新だが、エイリアス prd がバージョン 72(古い)に固定されていた
  4. 手動でバージョン 73 を発行 → エイリアスを 73 に更新 → Invalidation → 反映された
  5. GitHub Actions のワークフローを確認 → publish-versionupdate-alias もないことを確認 → 根本原因特定

この時点で、デプロイパイプラインにバージョン発行とエイリアス更新が含まれていないことが明確になりました。

GitHub Actions での自動化実装

問題を解決するため、GitHub Actions のワークフローに以下の 3 ステップを追加しました。

ステップ1:Lambda の更新完了を待つ

update-function-code は非同期で処理されるため、完了を待たずに次のステップに進むと、更新途中の古いコードでバージョンが作られる可能性があります。

- name: Wait for Lambda update to complete
  run: |
    aws lambda wait function-updated \
      --function-name ${{ env.LAMBDA_FUNCTION_NAME }}

このコマンドは Lambda が Active 状態になるまで待機します。内部的には GetFunctionConfiguration API を使ってポーリングしているため、後述する IAM 権限の設定が重要です。

ステップ2:バージョン発行とエイリアス更新

main ブランチのデプロイ時のみ、新しいバージョンを発行してエイリアスを更新します。

- name: Publish Lambda version and update alias
  if: env.BRANCH_NAME == 'main'
  run: |
    VERSION=$(aws lambda publish-version \
      --function-name ${{ env.LAMBDA_FUNCTION_NAME }} \
      --query 'Version' --output text)
    echo "Published Lambda version: ${VERSION}"
    
    aws lambda update-alias \
      --function-name ${{ env.LAMBDA_FUNCTION_NAME }} \
      --name prd \
      --function-version $VERSION
    echo "Updated alias prd to version ${VERSION}"

publish-version で現在の $LATEST のスナップショットをバージョンとして固定し、そのバージョン番号をエイリアスに割り当てます。

ステップ3:CloudFront キャッシュ無効化

エイリアスが更新されても、CloudFront のキャッシュが残っていると古いコンテンツが返されます。すべてのパスでキャッシュを無効化します。

- name: Invalidate CloudFront cache
  if: env.BRANCH_NAME == 'main'
  run: |
    aws cloudfront create-invalidation \
      --distribution-id ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} \
      --paths "/*"

CLOUDFRONT_DISTRIBUTION_ID は GitHub の Environment Variables(prd 環境)に設定しています。Secrets ではなく Variables を使っているのは、Distribution ID は秘密情報ではなくログに表示されても問題ないためです。

IAM 権限の追加

GitHub Actions の IAM ロールに以下の権限を追加する必要があります。

権限 用途
lambda:PublishVersion バージョン発行
lambda:UpdateAlias エイリアス更新
lambda:GetFunction wait コマンド用
lambda:GetFunctionConfiguration wait コマンド用(後述の通り、これがないとエラーになる)
cloudfront:CreateInvalidation キャッシュ無効化

Lambda 系の権限は既存ポリシー(AllowLambdaUpdateFunction)に追加し、CloudFront は AllowCloudFrontInvalidation として新規ポリシーを作成しました。サービスごとにポリシーを分けることで、権限の管理が容易になります。

あわせて読みたい
Identity-based IAM policies for Lambda - AWS Lambda Learn how to use identity-based policies to grant users access to your Lambda resources.

実装時のハマりどころ

wait コマンドで権限エラー(exit code 255)

最初のデプロイで wait function-updated が exit code 255 で失敗しました。原因は lambda:GetFunctionConfiguration の権限不足です。

wait コマンドは内部的に GetFunctionConfiguration API をポーリングして Lambda の状態を監視しています。AWS CLI のドキュメントには GetFunction が必要とだけ書かれていますが、実際には GetFunctionConfiguration も必要です。最初は GetFunction だけ追加して失敗し、ログを確認して GetFunctionConfiguration を追加したところ解決しました。

あわせて読みたい
function-updated — AWS CLI 2.34.19 Command Reference Use the AWS CLI 2.34.19 to run the lambda wait function-updated command.

dev 環境との違いに注意

dev 環境は CloudFront を経由せず Lambda Function URL に直接アクセスする構成だったため、この問題は発生していませんでした。if: env.BRANCH_NAME == 'main' で本番のみ実行するようにしています。

CloudFront キャッシュ TTL の見直し

デフォルトビヘイビアの TTL が 24 時間に設定されていたため、Invalidation で都度クリアする運用にしています。根本的には SSR ページのキャッシュポリシーを CachingDisabled に変更するか、Next.js 側で適切な Cache-Control ヘッダーを返す対応が別途必要です。

自動化後のデプロイフロー

修正後、本番デプロイは以下のように自動化されました:

  1. main ブランチにマージ
  2. GitHub Actions が Docker イメージをビルド
  3. Lambda の $LATEST を更新
  4. Lambda の更新完了を待機
  5. 新しいバージョンを発行(例:バージョン 74)
  6. エイリアス prd をバージョン 74 に更新
  7. CloudFront のキャッシュを無効化
  8. 本番サイトに即座に反映

この一連の流れが完全自動化され、「マージしたら本番に反映される」が実現しました。

まとめ:エイリアス構成でのデプロイ自動化

Lambda エイリアスを使う構成では、デプロイパイプラインに publish-versionupdate-alias が必須です。update-function-code だけでは $LATEST しか更新されず、エイリアス経由のリクエストには反映されません。

さらに CloudFront を前段に置いている場合は Invalidation も忘れずに実行する必要があります。この 2 つが揃って初めて、CI/CD パイプラインが完結します。

Provisioned Concurrency を使う場合はエイリアス構成が必須ですが、デプロイフローもそれに合わせて設計することで、手動オペレーションをゼロにできます。

AWS Lambda と CI/CD をさらに強化する関連記事

Lambda のデプロイ自動化を習得したら、開発効率化やセキュリティ強化も合わせて実装しましょう:

セキュリティ・認証

開発ツール・効率化

設計・アーキテクチャ

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次