Dynamic Reference を、sam deploy の度に必ず再評価させる
Lambda の環境変数に Dynamic Reference({{resolve:ssm:...}} や {{resolve:secretsmanager:...}})を使っていると、
「sam deploy のたびに常に最新値を読んでほしいのに、実際には更新されない」
という状況に遭遇することがあります。
この記事では、AWS SAM で定義した環境変数に対して、
- ビルド時刻(buildtime)をダミーの環境変数として追加
-
sam deployのたびに buildtime の値を変える
というシンプルな工夫で、毎回 Dynamic Reference を確実に評価させる方法をまとめます。
背景:Dynamic Reference と Lambda 環境変数
まず前提として、Lambda の環境変数に Dynamic Reference を書くとき、SAM テンプレートはだいたいこんなイメージになります。
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
DB_PASSWORD: '{{resolve:secretsmanager:my-app/db-password:SecretString:password}}'
ここで押さえておきたいポイントは以下の 2 つです。
-
Dynamic Reference は CloudFormation がデプロイ時に評価する
- テンプレート内の
{{resolve:...}}は スタックの作成・更新時に解決されて、具体的な値に置き換えられます。 - つまり Lambda の環境変数として保存されるのは「すでに解決された平文の値」です(もちろん KMS によって暗号化されて保存されますが、中身は“固定された値”)。
- テンプレート内の
-
CloudFormation は「テンプレート上の差分」がなければリソースを更新しない
- 同じ
template.yamlをsam deployしても、環境変数ブロックに差分がなければ Lambda Function の設定更新が起きません。 - その結果、Secrets Manager 側で値が変わっていても、「テンプレートに変化がない」場合は Lambda の環境変数は更新されません。
- 同じ
この挙動のせいで、
「Secret はローテーションされたのに、Lambda は古い値のまま」
という事象が起きることがあります。
解決したいこと
やりたいことはシンプルです。
-
DB_PASSWORDのような Dynamic Reference を含む環境変数について -
sam deployを実行するたびに、 - 毎回 CloudFormation に Lambda の環境変数ブロックを“更新対象”として認識させる
= つまり、デプロイのたびに Dynamic Reference を再評価させたい、という要件です。
方針:buildtime をダミーの環境変数として仕込む
CloudFormation が「リソースを更新するかどうか」を判定するときに見ているのは、テンプレート上のプロパティの差分です。
なので逆に言えば、
Lambda の
Environment.Variablesに 毎回変わる値 を 1 個紛れ込ませる
ことで、「毎回差分がある」状態を強制的に作ることができます。
そこで利用するのが buildtime(ビルド時刻) を表すダミーの環境変数です。
- この環境変数自体は Lambda コード側では使いません
- 「毎回値が変わる」ことだけが目的です
実装例
1. テンプレートに Parameter を追加する
まず、template.yaml にビルド時刻を受け取るための Parameter を定義します。
Parameters:
BuildTime:
Type: String
Description: "Unique value for each build/deploy to force env var update"
2. Lambda の Environment に BuildTime を含める
次に、Lambda Function の Environment にこの BuildTime をダミー変数として追加します。
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
...
Environment:
Variables:
# Dynamic Reference を使った本命の環境変数
DB_PASSWORD: '{{resolve:secretsmanager:my-app/db-password:SecretString:password}}'
# ダミー用途の buildtime 環境変数(コードからは使わない)
BUILD_TIME: !Ref BuildTime
これで、BuildTime の値が変わるたびに Environment.Variables が変化し、
Lambda Function の設定が必ず更新されるようになります。
当然そのタイミングで DB_PASSWORD の Dynamic Reference も再評価され、
Secrets Manager / SSM Parameter Store の最新値が環境変数に反映されます。
3. CodeBuild から BuildTime を渡す
最後に、実際のデプロイ時に BuildTime に毎回異なる値を渡します。
ここでは AWS CodeBuild からデプロイすること を想定し、CodeBuild が毎回ユニークな値として持っている CODEBUILD_START_TIME(例: build_start_time 環境変数にマッピング)をそのまま渡す例にします。
例えば、buildspec.yml は次のように書けます。
version: 0.2
phases:
build:
commands:
- sam build
- sam deploy \
--stack-name my-stack \
--capabilities CAPABILITY_IAM \
--parameter-overrides BuildTime=${CODEBUILD_START_TIME}
CODEBUILD_START_TIME は各ビルドごとに異なる値(ビルド開始時刻)になるため、毎回 BuildTime の値も変わります。
もし自分で build_start_time という環境変数を CodeBuild の環境変数として定義しているなら、単に ${build_start_time} に置き換えても問題ありません。
これで、
-
sam deployのたびにBUILD_TIMEが変わる -
Environment.Variablesに差分が出る - CloudFormation が Lambda Function を更新
- Dynamic Reference も再評価される
というループが完成します。
このやり方のメリット・デメリット
メリット
-
Dynamic Reference が毎回必ず評価されるので、
- Secrets Manager / SSM Parameter Store 側の値更新が Lambda に確実に反映される
-
実装は非常にシンプル
- Parameter 1 個
- Environment に変数 1 個
-
sam deployに--parameter-overridesを足すだけ
デメリット / 注意点
-
sam deployのたびに Lambda Function の 設定が毎回更新される ため、- Lambda のバージョン/エイリアス運用をしている場合は、バージョンがどんどん増える
- CloudFormation の履歴上も毎回差分が残る
-
「Dynamic Reference の再評価」が目的なので、
「本当にそこまで頻繁に更新が必要か?」は要件と相談する必要がある- 例えば Secrets のローテーションが月 1 回なら、そのタイミングだけこの仕組みを使う、という運用もあり
まとめ
-
Dynamic Reference は便利だが、「デプロイ時に一度だけ評価される」という性質がある
-
CloudFormation はテンプレートに差分がないと Lambda Function を更新してくれない
-
そこで、
- buildtime を表すダミー Parameter を用意し
- Lambda の Environment に
BUILD_TIMEを追加し -
sam deployのたびに違う値を渡す
とすることで、
sam deployのたびに Lambda の環境変数ブロックに差分が生まれ、
Dynamic Reference が毎回必ず再評価されるようになる
Dynamic Reference を SSM / Secrets Manager と組み合わせてガチガチにセキュア運用していると、
「値は変わっているはずなのに、Lambda が更新されていない」系のハマりどころはそこそこ出てきます。
同じような状況で悩んでいる方の参考になれば幸いです。