手動作成AWS LambdaのCDK化
本記事は『AWS CDK Advent Calendar 2024』の2日目の記事です。
要旨
「手動作成リソースをCDK化したい場合、cdk importを使わずにリソースを作り直す(スクラップアンドビルド)ことも選択肢の一つとして入れよう」
手動作成されていたAWSリソースに対してはじめての cdk import
でIaC化を試みましたが、結果的にリソースを作り直す判断にした実体験を紹介します。
対応前の課題
今回のCDK化の対象は、Lambda関数とLambda関数に付帯するトリガー(EventBridgeルール、Kinesis Data Streams、DynamoDB Streams、etc)です。
これらは手動作成されたもので複数あり、運用・管理は次のようにしていました。
- 最初は手動作成
- 関数コード本体はGitHubでコード管理されており、CI/CDパイプラインが組まれている
- 関数ごとにそれぞれ独立したNode.jsプロジェクトになっている
長く運用していると、次のような課題を感じるようになりました。
1️⃣ ランタイムやライブラリの更新作業が大変
Lambdaでは1年に1回のペースでNode.jsの新しいランタイムがサポートされますが、最新バージョンに追いついていくための工数が無視できないようになってきました。
(つい先日Node.js 22へのサポートされましたね)
原因は、1つ1つが別々のNode.jsプロジェクトで管理されていることでした。
使っているLambdaプロジェクトごとにLint, フォーマッタ, パッケージマネージャ(npm/yarn/pnpm)などが統一されておらず、メンテナンス時にキャッチアップの負荷が高くなっていました。
古いランタイムでそのまま動かし続けることも可能ですが、いざコード修正が必要になった時にあたふたしないためにも、継続的にランタイムの更新はしておきたいと考えています。ライブラリの脆弱性や警告にも、無視せずに継続的に対応しておきたいところです。
2️⃣ 設定が正しいことを管理できない
運用していくと環境変数、メモリ、タイムアウトなどの設定値を見直すことがあります。
手動作成していたために設定変更も手動で行われており、以下のような管理がしにくくなっていきました。
- 本当にこの設定でよいのか?
- パラメータシートとズレているがどちらが正しいのか?
- いつ、誰が、なんのために変更したのか?
また、ステージング環境と本番環境での設定のズレなどがあっても気づくのが難しく、本番リリース時のリスクとなっていました。
3️⃣ 汎用のIAMロールを割り当ててしまっている
Lambdaの実行ロールとして権限の強いロールを共用しており、「最小権限の原則」のベストプラクティスに反していました。
方針
上記の課題を解決するために、手動作成のLambda関数群を整理し、単一のCDKプロジェクトとして管理することにしました。
リポジトリのファイル・フォルダ構成は次のようなイメージです。
+ bin/
+ lib/
- foo-stack.ts
- bar-stack.ts
- baz-stack.ts
- ...
+ src/ ※ 関数のコード
+ foo/
+ bar/
+ baz/
+ ...
- package.json
- package-lock.json
- cdk.json
- jest.config.js
- tsconfig.json
1️⃣については、単一のプロジェクトであればツールチェーンの統一が強制できます。
2️⃣については、IaCの仕組みにより、コードと実際のリソースの差がないことを担保でき、設定の変更についてもバージョン管理に乗せることができます。
3️⃣については、CDKを使うことでLambda関数ごとに適切な権限の付与を高い抽象化でできると考えました。
はじめてのcdk import
CDKはこれまでも使っていましたが、既存リソースのインポートは初めてでした。既存リソースのインポートにまつわるツールとして、IaCジェネレータ、cdk import
、cdk migrate
が存在しており、それらの関係性を整理しました。
🔴 IaCジェネレータ = 既存リソースをスキャンし、CloudFormationテンプレートを出力するもの
🔵 cdk migrate
= CloudFormationテンプレートをCDKプロジェクトに変換するもの
🟢 cdk import
= 既存リソースとAWS CDKのリソースを結びつけ、CloudFormationへインポートするもの
流れ
cdk import
を使って既存のリソースをCDK管理するまでの流れは次のようになります。
-
cdk init
でCDKプロジェクトを作成する - 空のCDKスタックをデプロイする
- 既存リソースと一致するようにCDKスタックを記述する
-
cdk import
でインポートを実行する - ドリフトを検出し、ドリフトがなくなるようにCDKのコードを修正するか、既存リソースを変更し、スタックの更新を実施する。
-
cdk deploy
でデプロイを実行する - CDKのコードをリファクタリングする(L1 → L2/L3等)
注意点など
大事なのは上記の流れの3と5に関する部分で「cdk import
はCDKのコードを作成する機能ではないので、スタックのコードはスクラッチで書くか、IaCジェネレータ+cdk migrate
等でセルフサービスで行う必要がある」ということです。そしてその際に「ドリフトがないように気をつける必要がある」ことです。
実験してみて、いくつか反省点・注意点をまとめました。
インポートする前の時点では既存リソースをL1 Constructで再現(記述)することに徹底する
インポート後にドリフトが発生していた場合、解消する必要がありますが、L2 Constructで記述したスタックのコードでドリフトを解消するのは大変だと感じました。まずはL1 Constructで実直に既存リソースを記述するようにしましょう。
CDK化はあくまで手段なので、CDK化するためのドリフト解消に時間を費やすのは本末転倒です。
権限まわり(ロール)の見直し、CDKのリファクタリング(L1→L2/L3等)、関数本体のリファクタリング等々はすべてデプロイが成功してから行うようにしましょう。
cdk import
実行時のプロンプト上の入力を間違えない
cdk import
すると、既存リソースとの結びつきとインポートのために、プロンプト上での入力が必要になる場合があります。
$ npx cdk import
...
SampleStack/SampleFunction/Resource (AWS::Lambda::Function): import with FunctionName=sample-function (yes/no) [default: yes]?
SampleStack/SampleSchedule/Resource (AWS::Events::Rule): enter Arn (empty to skip): ←
SampleStack/SampleSchedule/AllowEventRuleSampleStackSampleFunctionABCDEFG (AWS::Lambda::Permission): enter FunctionName (empty to skip): ←
SampleStack/SampleSchedule/AllowEventRuleSampleStackSampleFunctionABCDEFG (AWS::Lambda::Permission): enter Id (empty to skip): ←
ここの入力を間違うと期待する設定でインポートがされなくなってしまいます。
1回インポートが成功すると当然ながらCDK(CFn)管理になってしまうため、ここの入力は実質一回勝負なので注意しましょう。
対応できないリソースが存在している
(cdk import
というよりCloudFormationのインポートの制限ですが)インポートに対応していないリソースが存在しています。
Lambdaのようにメジャーなリソースであれば大丈夫だと思いますが、こちらも事前にチェックしておきましょう。
cdk importではなく「作り直す」
色々と試行錯誤しながら検証を進めていったのですが、中途半端な状態でスタックにインポートされてしまっていたり、ドリフト解消に手こずっていたりするうちに、結局のところ作り直した方が早いという結論になりました。
今回の対象はLambdaを中心としたステートレスなリソースばかりなので、作り直してもデータ消失など懸念がなかったのも要因です。
作り直す場合のメリット・デメリットは次のようになります。
- メリット
- L2/L3 Constructを使ったCDKコードが最初から書ける
- デメリット
- ステートフルなリソースの場合、状態・データが消える
- ものによっては削除した時にダウンタイムが存在する
作り直しに方針を切り替えてからは、cdk deploy
やその後の検証・リファクタリングまでとてもスムーズに進められました。
「既存リソースをCloudFormation/CDK管理に乗せたい」と考えた時に、インポート機能ばかりに目が行きがちでしたが、ゴールから逆算すると近道できた、という反省です。cdk import
に入門して知見は溜まったので、今後活かしていきたいと思います。
おわりに
今回はじめて cdk import
に触れましたが、その感触をつかむことができました。
今後もタイパを考えながら、cdk import
を1つの選択肢としつつ、ベストなIaC運用を探っていきたいと思います!
Discussion