CloudFormation Custom Resources:IaCの限界を突破する「最後の切り札」を使いこなせ
多くのエンジニアが「Infrastructure as Code (IaC)」を語るとき、彼らは既存のAWSリソースの定義に終始する。しかし、真のSREは知っている。「AWSが提供するネイティブなリソース定義だけでは、システムの心臓部は決して完結しない」という冷徹な事実を。
CloudFormation Custom Resourcesは、単なる機能ではない。それは、AWSのAPIが直接サポートしていないリソースや、複雑なビジネスロジックを伴うセットアップを、宣言的なIaCのサイクルの中に強制的にねじ込むための「究極のフック」だ。
本稿では、この深淵に踏み込み、単なる「Lambdaを動かす」段階を超えた、本番環境で生き残るためのカスタムリソース実装術を解き明かす。
—
1. カスタムリソースの本質:IaCの抽象化レイヤーを破壊する
カスタムリソースとは、CloudFormationのスタック作成・更新・削除のイベントをLambdaに投げ込み、その応答を待つことでリソースのライフサイクルを制御する仕組みだ。
これが必要になるのは、以下のような「AWSのAPI外」にある制御が必要なケースである。
- 動的な外部システムとの同期: Auth0などの外部IDプロバイダーへの設定、あるいはオンプレミス側のスイッチング。
- 初期化を要するリソース: RDS作成後のテーブル生成、あるいはElasticsearch(OpenSearch)のインデックスマッピング適用。
- 高度な条件判定: 特定のタグ付けルールに基づいたリソースの動的な再配置や、クロスアカウントの複雑な権限設定。
これらを「手動のスクリプト」で実行しているならば、それはIaCの敗北である。カスタムリソースを使えば、これら全てを`CREATE/UPDATE/DELETE`という冪等なサイクルに収束させることができる。
—
2. 実装の極致:Lambda連携とライフサイクル制御
カスタムリソースのLambda実装において、最も避けるべきは「適当な実装」だ。CloudFormationは`cfn-response`モジュールを推奨するが、プロダクション環境では自前のレスポンスロジックを実装せよ。
なぜか?`cfn-response`はエラー時にスタックをハングさせるリスクがあり、デバッグ情報が極めて乏しいからだ。
堅牢なLambda実装のアーキテクチャ
import json
import logging
ロガーの初期化は必須。CloudWatchでの追跡効率を最大化する
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def handler(event, context):
try:
request_type = event[‘RequestType’]
# リソースのユニークID(PhysicalResourceId)を適切に管理せよ
# 削除時に必要となるキーをResourcePropertiesから抽出する
if request_type == ‘Create’:
return on_create(event)
elif request_type == ‘Update’:
return on_update(event)
elif request_type == ‘Delete’:
return on_delete(event)
except Exception as e:
logger.error(f”Execution failed: {str(e)}”)
# 失敗時は必ずStatus: FAILEDを返すこと。さもなくばスタックが1時間ハングする
send_response(event, context, ‘FAILED’)
def send_response(event, context, status, reason=None):
# ここでPre-signed URLに対してHTTP PUTを行う自前のロジックを実装
# タイムアウトとリトライ戦略をここで定義するのがプロの仕事
pass
—
3. 実践:データベース初期化とAPI呼び出しのハック
例えば、Auroraのテーブル初期化をカスタムリソースで行う場合、単にクエリを投げるだけではいけない。「冪等性の保証」と「接続プールの最適化」が鍵となる。
- 冪等性の担保: `CREATE TABLE IF NOT EXISTS`を必ず使い、実行前にスキーマのバージョンテーブルをチェックせよ。
- メモリとタイムアウト: 複雑なDDLを流す場合、Lambdaのメモリ割り当ては最低でも512MB以上を確保せよ。DB接続のハンドシェイク中にメモリ不足でOOM Killerに殺されるのは、最も恥ずべき障害の一つだ。
- 疎結合な設計: DBのパスワードは直接渡さず、SSM Parameter StoreまたはSecrets Managerから取得する設計をLambda内で完結させること。
—
4. エラーハンドリングとタイムアウトの深淵
カスタムリソースの最大の敵は「スタックのスタック(スタックロック)」である。Lambdaがタイムアウトした際、CloudFormationは状態を確定できず、スタックが「UPDATE_ROLLBACK_FAILED」などの地獄に陥る。
これを防ぐための「鉄則」:
1. タイムアウトは二重管理せよ:
- Lambdaのタイムアウト値(例: 2分)よりも短い時間(例: 1分45秒)で、プログラム内部のタイムアウト処理を走らせ、適切に`FAILED`を返す。
2. 冪等な削除処理:
- `Delete`イベントは、「対象が存在しなくてもエラーにしない」設計にせよ。既に削除されているリソースに対して削除コマンドを投げても、それは成功とみなすべきだ。
3. PhysicalResourceIdの固定:
- カスタムリソースの更新時、`PhysicalResourceId`を変更するとリソースが再作成(削除→作成)される。これが望ましくない場合は、リソースのIDを固定し、更新処理のみを走らせる設計を徹底せよ。
—
結びに代えて:IaCの先にある世界
カスタムリソースを極めることは、AWSの制約を「拡張可能なAPI」に変えることと同義だ。
あなたが構築すべきは、単なるリソースの羅列ではない。インフラの変更がコードによって完全に管理され、たとえ標準外の制御が必要であっても、一つのボタンで全環境が整合性を保ってデプロイされる「自己修復するシステム」だ。
技術を単なるツールとして使うな。インフラそのものをコードで制御し尽くすという強い意志を持って、CloudFormationの限界を塗り替えていけ。その先には、エンジニアとして唯一無二の景色が待っている。