はじめに
この記事では、k8s CronJobとLambdaを組み合わせた監視バッチを実装したときに発生した、Slack通知先がずれる問題を整理します。
Lambda単体で動かすと通知されるSlackチャネルと、CronJobで動かすと通知されるSlackチャネルが異なるという現象でした。
原因を調べると、SNSトピックが実行経路ごとに分岐していたことが根本にありました。
同じ構成を検討している方の参考になれば幸いです。
先に結論だけ書くと次の3点です。
- 問題は、
.env.devがcronjob.yamlのAWS_SNS_TOPICを上書きしていたことでした。 - 本質は、SNSトピックが「イベント種別」ではなく「実行経路」で分岐していた設計にありました。
- SNSトピックを統一し、CronJob実行時の環境変数名を
.env.devと衝突しない名前に変更することで解消しました。
構成の概要
対象はディスク使用率を定期監視してSlackへ通知する仕組みです。
設定管理は次のように分けていました。
| 設定内容 | 管理場所 |
|---|---|
| ディスク使用率の閾値 | AWS Parameter Store |
| 監視対象ホストの接続情報 | AWS Secrets Manager |
| Slack Webhook URL | AWS Secrets Manager |
| Slackチャネル名・バッチ名 | Lambda 環境変数 |
発生した問題
Lambda単体で動作させたときと、k8s CronJobで実行したときで、通知されるSlackチャネルが異なっていました。
Lambda自体の実装は変えていないため、最初は原因が分かりませんでした。
原因
調査の結果、.env.dev に残っていた AWS_SNS_TOPIC が cronjob.yaml の設定を上書きしていたことが原因でした。
cronjob.yaml 側で AWS_SNS_TOPIC を設定していたため、それが使われると思っていました。
しかし実際にはアプリケーションの起動時に .env.dev が後から読み込まれており、cronjob.yaml の値が上書きされていました。
【意図していた動き】
cronjob.yaml の AWS_SNS_TOPIC=disk-usage-alert-dev
→ SNS: disk-usage-alert-dev → Lambda → 正しいチャネルへ通知
【実際の動き】
cronjob.yaml の AWS_SNS_TOPIC=disk-usage-alert-dev
→ .env.dev の AWS_SNS_TOPIC=disk-alert-dev で上書き
→ SNS: disk-alert-dev → 別の Lambda → 別チャネルへ通知
.env.dev には AWS_SNS_TOPIC を意図的に書いたつもりはなく、以前の設定が残り続けていたものでした。
CronJob の環境変数で管理できていると思っていた値が、.env.dev によって静かに上書きされていた形です。
変更前後の比較
今回の差分を実行経路で比較すると次のとおりです。
| 観点 | 変更前 | 変更後 |
|---|---|---|
| SNSトピックの決まり方 | 実行経路ごとに別名 | ドメインイベントで固定 |
| 設定の扱い |
.env.dev と cronjob.yaml の同名環境変数が衝突 |
CronJob実行時の環境変数名を変更して衝突を回避 |
| 起動されるLambda | 実行経路によって変わる | どの経路でも同じLambda |
| 通知先Slackチャネル | 経路依存でずれる可能性あり | 経路に依存せず一貫 |
【変更前】
ローカル実行 -> SNS: disk-alert-dev -> Lambda A -> Slack channel A
CronJob実行 -> SNS: disk-usage-alert-dev -> Lambda B -> Slack channel B
【変更後】
ローカル実行 -> SNS: disk-high-alert-dev -> Lambda Notify -> Slack channel Notify
CronJob実行 -> SNS: disk-high-alert-dev -> Lambda Notify -> Slack channel Notify
本質的な問題:SNSトピックが実行経路の都合で決まっていた
設定ファイルが2つあったことは直接の原因ですが、より根本的な問題は別にあります。
SNSトピックの名前が「何が起きたか」ではなく「どこから実行するか」で決まっていたことです。
悪い例(実行経路で分岐)
disk-alert-dev ← ローカル実行用
disk-usage-alert-dev ← CronJob実行用
良い例(ドメインイベントで固定)
disk-high-alert ← 「使用率が閾値を超えた」というイベント
今回のようなイベント通知では、SNSトピックは「何が起きたか」というイベントの種別に寄せた方が扱いやすいです。
「どこから実行するか」によって変わる設計にすると、実行経路ごとに別のLambdaが起動し、通知先まで変わりやすくなります。
本来の責務の分担は次のとおりです。
| レイヤ | 責務 |
|---|---|
| SNSトピック | 何が起きたかを伝える(イベント種別) |
| Lambda | どこへどのように通知するかを決める |
この分担が崩れると、同じイベントのつもりでも実行経路によって通知先が変わる不安定な挙動になりやすいです。
なぜ上書きが起きていたか
.env.dev はローカル開発・手動テスト用、cronjob.yaml はk8s上での実運用用と、用途が異なるため両方持っていました。
| ファイル | 用途 |
|---|---|
.env.dev |
ローカル開発・手動テスト時に使用 |
k8s-cronjobs/dev/cronjob.yaml |
k8s上でのdev環境の実運用で使用 |
アプリケーションは起動時に cronjob.yaml の環境変数を受け取った後、.env.dev を読み込む順序になっていました。
後から読まれる .env.dev の値が優先されるため、cronjob.yaml 側で正しい値を設定していても効かない状態でした。
.env.dev に AWS_SNS_TOPIC が残っていることに気づいていなかったため、CronJob の設定で管理できていると誤認していました。
.env と CronJob の両方で持つこと自体が危険だった
今回の問題は、.env.dev に古い値が残っていたことだけが本質ではありません。
同じ意味の設定値を .env と CronJob の両方で持っていたこと自体が、ズレを生みやすい構造でした。
特に次のような値を二重管理すると危険です。
- SNSトピック ARN
- 接続先 URL
- キュー名
- 実行対象リソース
これらは実行経路そのものを変えてしまう値です。
ローカル実行では .env、k8s では CronJob というように正本が分かれると、同じアプリを動かしているつもりでも実際には別の経路に流れます。
.env 自体が悪いわけではありません。
ローカル確認専用のダミー値やスタブ接続先を持つ用途では便利です。
ただし、実運用の挙動を決める値を .env と CronJob の両方で常用すると、今回のように静かに壊れます。
今回のケースでは、少なくとも AWS_SNS_TOPIC のような汎用的な名前は避け、CronJob実行時の値が .env.dev と衝突しないようにするべきでした。
対処
2つの問題を同時に解決しました。
まずSNSトピックをイベント種別で統一しました。
実行経路に関わらず同じトピックを使うことで、どこから実行しても同じLambdaが起動します。
次に、CronJob実行時の環境変数名を .env.dev と衝突しない形に整理しました。
| 用途 | 方法 |
|---|---|
| 通常のローカル検証 | LocalStackを使った自動テストに寄せる |
| 手動での動作確認が必要な場合のみ |
launch.json に環境変数を記載して実行 |
| dev環境での実運用 |
k8s-cronjobs/dev/cronjob.yaml の環境変数を参照 |
手動確認に launch.json を使う理由は、.env.dev と性質が異なるためです。
.env.dev はアプリ起動時に自動で読み込まれるため、意図せず有効になります。
launch.json はデバッガから実行するときにだけ明示的に適用されるので、「気づかず読まれる」が起きません。
また、CronJob側では AWS_SNS_TOPIC のような汎用名ではなく、アプリ固有の環境変数名を使うようにしました。
これにより .env.dev に以前の AWS_SNS_TOPIC が残っていても、CronJob実行時の設定と衝突しなくなります。
振り返り
この問題から得た教訓は2つあります。
1つ目は、環境変数には汎用的な名前を避けるということです。
AWS_SNS_TOPIC のような名前はスコープが広く、どのアプリ・どの環境の変数なのか判断しにくいです。
複数の設定ファイルを持つ構成では、同名の変数が別のファイルに残っていても気づきにくくなります。
DISK_MONITOR_SNS_TOPIC_ARN のようにアプリ固有のプレフィックスをつけると、衝突しにくくなり、.env に古い値が残っていても発見しやすくなります。
2つ目は、Lambda単体での動作確認はCronJob実行の保証にならないという点です。
環境変数の読み込み元が実行経路によって変わる場合、Lambda単体で動いていてもCronJob経由では別の値が使われることがあります。
実行経路ごとに検証しておかないと、Lambda単体テストが通っていても本番の動作がずれる可能性があります。
再発防止チェックリスト
公開後に同じ事故を防ぐため、変更時に次を確認します。
- SNSトピック名は実行経路ではなくイベント種別で設計されているか
-
AWS_SNS_TOPICのような経路を変える設定値に正本が1つだけ定義されているか -
.envとCronJobに同じ意味の設定を常用していないか - 環境変数名にアプリ固有プレフィックスを付け、衝突しにくくしているか
- Lambda単体テストに加え、CronJob経由の実行経路でも検証しているか