AWS CDK で YAML を書かずに AWS SAM を利用! ~良いとこどりな IaC の実現を目指す~
🗓 AWS CDK Advent Calendar 2022 の 25 日目 🗓
🔰 はじめに
対象
AWS Cloud Development Kit (CDK) と AWS Serverless Application Model (SAM) を知っている方
やること
CDK の aws-cdk-lib.aws_sam
で SAM テンプレートの定義・出力ができることを利用して
- 全ての AWS リソース定義は CDK で統一する
- CDK ではテンプレートファイルの出力までを任せる
- Lambda 関数のパッケージング・デバッグ、そして AWS リソースのデプロイは SAM で実施する
という手順で、コードによるリソース定義ができる CDK と Lambda 関数のパッケージングが便利な SAM のそれぞれ得意な部分を活かしながら AWS リソースを管理をします!
※ こんなこともできるよという使い方で、動作を保証するものではないです
背景
AWS でサーバーレスアプリケーションを構築する場合、SAM を使用することで Lambda 関数のビルド・デプロイ・デバッグや対応サービスのリソース定義が簡潔にできて便利ですが、こんなことを考えて SAM を使用せず CDK を採用することもあるかなと思います!
- SAM に対応してないリソースも定義したい場合は AWS CloudFormation の定義を使うことになるので、IDE のコード補完や L2 コンストラクタが使えて、たくさんの YAML を書かなくても良い CDK が良さそう
- すでに Amazon VPC や Application Load Balancer (ALB) など他のリソース定義に CDK を使用しているので、リソース定義は CDK で統一したい
しかし同時に、現時点では CDK で定義した Lambda 関数の依存関係のパッケージングが必要な場合はこの辺りも気になると思います。
- AWS CDK による AWS Lambda コードの管理 | Amazon Web Services ブログ にいくつか方法はあるけど、どれを選択すれば良いか迷う
- CDK の Lambda 関数には言語固有のモジュールもあるが、Node.js もしくは Preview 段階の Go, Python しか用意されていない
そのため、この部分に関しては CDK より考えることや作業量が少なくなる sam build
& sam deploy
コマンドが使える SAM もやっぱり魅力的です!
そこで今回は CDK と SAM のそれぞれの良いとこどりができるかも?な併用方法を考えて試してみました。
環境
### AWS CDK
cdk --version
2.56.1 (build 9329a73)
### AWS SAM
sam --version
SAM CLI, version 1.67.0
🔖 要約
サンプルコード
手順
-
aws-cdk-lib.aws_sam
を使用して CDK でコードを書く - CDK が使用するメタデータなどをテンプレートに出力しないように、
./cdk.json
の編集とcdk synth
実行時にオプションを追加する - SAM 固有の
Glabals
セクション を使用できるように./sam-globals.yaml
ファイルの作成と実行時オプションの--build='cat sam-globals.yaml'
を組み合わせる-
aws-cdk-lib.aws_sam
は現時点でGlabals
セクションに対応していないため
-
-
cdk synth --no-staging --no-version-reporting --no-path-metadata --build='cat sam-globals.yaml' > template.yaml
で SAM テンプレートを出力- さらに
package.json
を修正してnpm run cdk2sam
で実施できるようにしておく
- さらに
-
sam build
&sam deploy
でリソースをデプロイする- もちろん
sam local
でのテストやsam sync
によるデプロイも可能
- もちろん
メリット
- CDK でコード補完を利用したリソース定義ができる
- SAM で簡潔に定義できる部分と、CDK の L2 コンストラクタを使用しつつ、コードは CDK で統一できる
- Lambda 関数の依存関係のパッケージングや、ローカルでのテスト、開発時の 変更セットをバイパスしたデプロイ など SAM の機能を活用できる
- 今回の使い方ではなくても、CDK で定義したリソースを SAM CLI でローカルテストする機能 は提供されています
デメリット
- CDK と SAM 両方の使い方を知っている必要がある
- デプロイまでの手順は CDK と SAM 両方のコマンドを使用するため一手間増える
- 本番環境へのデプロイは CI/CD で自動化する仕組みがあればそんなに問題はないかも
- (個人的に) 差分の出力が見やすい
cdk diff
は使えない-
sam deploy --no-execute-changeset
で変更セットの確認はできる
-
🧑🏫 工夫した点と解説
サンプルコード内の主要なファイルはこちら
.
├── bin
│ └── sam-written-by-cdk.ts # CDK の App クラス
├── cdk.json # cdk synth の出力制御などオプション設定
├── functions # Lambda 関数のコードを配置するディレクトリ
│ └── index # 関数ごとに区切ったディレクトリ
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── lib
│ └── sam-written-by-cdk-stack.ts # SAM リソースやその他 AWS リソース定義
├── package-lock.json
├── package.json
├── sam-globals.yaml # Globals セクションの定義
└── template.yaml # cdk synth で出力した SAM テンプレート
aws-cdk-lib.aws_sam
を使用した CDK のコード
🌟 いくつか aws-cdk-lib.aws_sam
使用のポイントを挙げていきます。
import 文の定義
-
import * as sam from 'aws-cdk-lib/aws-sam'
としておけば、sam.XXX
でモジュールが使用できるようになる
import { Stack, StackProps } from 'aws-cdk-lib';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
+import * as sam from 'aws-cdk-lib/aws-sam';
import { Construct } from 'constructs';
:
SAM リソースの定義
- 現在は
Type: AWS::Serverless::XXX
を定義する L1 コンストラクタのみが用意されており、new sam.CfnXXX
のprops
引数に SAM のリソースごとのProperties
を設定する - 例えば
AWS::Serverless::Api
を定義したい場合はCfnApi
コンストラクタがあり、props
は こちら に記載されている内容がCfnApiProps
として型定義されています!
const restApi = new sam.CfnApi(this, 'RestApi', {
stageName: 'dev',
name: 'cdk2sam-api',
});
Lambda 関数の定義
- Lambda 関数の定義も SAM と同様に
codeUri
でローカルのパスを指定する - Amazon API Gateway の ID や、Amazon DynamoDB のテーブル名も変数のプロパティで取得でき、CDK の機能でテンプレート出力時に
Ref
などを自動で変換してくれる - SAM の ポリシーテンプレート も使用できるので、Lambda 関数の許可の範囲をアプリケーションが使用するリソースに絞り込みが楽!
new sam.CfnFunction(this, 'IndexFunction', {
functionName: 'cdk2sam-index',
codeUri: 'functions/index/',
handler: 'app.lambda_handler',
runtime: 'python3.9',
events: {
api: {
type: 'Api',
properties: {
restApiId: restApi.ref,
method: 'GET',
path: '/',
}
}
},
policies: [
{
dynamoDbWritePolicy: {
tableName: ddb.tableName,
}
}
]
});
:
コード補完
CDK なのでコード補完も利用可能なので快適!
VS Code を利用
テンプレートの出力
cdk synth
を実行すると、Transform: AWS::Serverless-2016-10-31
や Type
セクションが AWS::Serverless::XXX
のリソース定義を含んだ SAM テンプレートが出力されます。
Transform:
- AWS::Serverless-2016-10-31
Resources:
RestApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
Name: cdk2sam-api
:
cdk synth
で CDK が使用するメタデータ出力の制御
🌟 cdk synth
では YAML 形式のテンプレートを出力できますが、そのままだとテンプレートに CDK が利用するメタデータを出力したり、cdk.out/
ディレクトリへ JSON 形式の CloudFormation テンプレートとして <Stack Name>.template.json
を書き出したりなど、sam deploy
実行時には不要な処理もあるのでこれらを抑制してみます。
ちなみにテンプレートに出力される CDK のメタデータを確認するため ↓ のようなコードを用意して、CDK のデフォルト設定のまま cdk synth
すると…
const ddb = new dynamodb.Table(this, 'BookTable', {
partitionKey: {
name: 'id',
type: dynamodb.AttributeType.STRING
}
});
↓ の YAML ファイルが出力されますが、+
で示した CDK が使用するメタデータがたくさんあります。
Resources:
BookTable:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: id
KeyType: HASH
:
+ Metadata:
+ aws:cdk:path: xxxx/BookTable/Resource
+ CDKMetadata:
+ Type: AWS::CDK::Metadata
+ Properties:
+ Analytics: v2:deflate64:xxxxxx
+ Metadata:
+ aws:cdk:path: xxxx/CDKMetadata/Default
+ Condition: CDKMetadataAvailable
+Conditions:
+ CDKMetadataAvailable:
+ Fn::Or:
+ - Fn::Or:
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - af-south-1
+ - Fn::Equals:
+ - Ref: AWS::Region
+ - ap-east-1
+ - Fn::Equals:
:
+Parameters:
+ BootstrapVersion:
+ Type: AWS::SSM::Parameter::Value<String>
+ Default: /cdk-bootstrap/xxxxxx/version
+ +escription: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
+Rules:
+ CheckBootstrapVersion:
+ Assertions:
+ - Assert:
+ Fn::Not:
+ - Fn::Contains:
+ - - "1"
+ - "2"
+ - "3"
+ - "4"
+ - "5"
+ - Ref: BootstrapVersion
+ AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.
大まかに挙げるとこの辺りは SAM テンプレート使用時には不要です。
抑制したいもの |
---|
Parameters セクションの BootstrapVersion
|
Resources セクションごとの Metadata->aws:cdk:path
|
Type: AWS::CDK::Metadata のリソースと、そこから利用される Contitions セクションの CDKMetadataAvailable
|
これらの抑制手順を以下にまとめました。
cdk.json
の設定で抑制できるもの
cdk.json の設定方法 |
抑制できるもの |
---|---|
"context" プロパティに、"@aws-cdk/core:newStyleStackSynthesis": false を追記 |
Parameters セクションの BootstrapVersion
|
設定例はこちら。
"context": {
+ "@aws-cdk/core:newStyleStackSynthesis": false,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
cdk synth
の実行時オプションで抑制できるもの
cdk コマンドの実行時オプション |
抑制できるもの |
---|---|
--no-version-reporting |
Resources セクションごとの Metadata->aws:cdk:path
|
--no-path-metadata |
Type: AWS::CDK::Metadata のリソースと、そこから利用される Contitions セクションの CDKMetadataAvailable
|
--no-staging |
./cdk.out ディレクトリに作成される <Stack Name>.template.json ファイル |
※ cdk
コマンドの 実行時オプション 一覧
実行例はこちら。
cdk synth --no-staging --no-version-reporting --no-path-metadata > template.yaml
これで SAM テンプレートがかなりすっきりしました!
Glabals
セクションの使用
🌟 SAM 固有の aws-cdk-lib.aws_sam
を使用しても、CDK では現在 SAM の Globals
セクションは定義できないのですが、複数の Lambda に一括で同じ設定をしたい時などに便利なので (無理矢理) 追加できる方法を考えてみました!
やりたいことは cdk synth
の標準出力を template.yaml
に書き出す時に合わせて Globals
セクションも書き出せれば良いので、echo
や cat
の標準出力を組み合わせると目的は達成できます。
# echo で愚直に出力したした後に cdk synth
echo "Globals:\n Function:\n Timeout: 10\n..."; cdk synth > template.yaml
もしくは
# sam-globals.yaml に書き出したい内容を定義しておいた上で
cat sam-globals.yaml
Globals:
Function:
Timeout: 10
:
# cat した後に cdk synth
cat sam-globals.yaml; cdk synth > template.yaml
さすがに echo
を使うとコマンドが長くなりやすく、改行コードを考えたりするのが手間になるので、あらかじめファイルを用意した上で cat
を使う方が扱いやすいです。
また cdk synth
の実行時オプションには、--build
というテンプレート出力前に任意のコマンドを実行できるものがあるので、cdk
のコマンドのみで完結できるようにしてみました (そこまで実行の手間は変わりませんが) 。
# sam-globals.yaml に書き出したい内容を定義しておいた上で
cdk synth --build='cat sam-globals.yaml' > template.yaml
これで Globals
セクションを含んだ SAM テンプレートを CDK でも出力できました!
+Globals:
+ Function:
+ Timeout: 10
+ Tracing: Active
+ Api:
+ TracingEnabled: true
+ EndpointConfiguration:
+ Type: REGIONAL
+ OpenApiVersion: 3.0.3
Transform: AWS::Serverless-2016-10-31
Resources:
BookTable:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: id
KeyType: HASH
:
※ Transform
セクションより上にあるのがスッキリしない方もいるかもしれませんが、これでも動くのでひとまず ^^;
ちなみに cdk.json
の "build"
プロパティを追記すれば、コマンド実行時に毎回 --build
オプションを使わずに同じことはできますので、お好みでどうぞ。
+ "build": "cat sam-globals.yaml",
"context": {
もしくは echo
でがんばる方はこちら。
+ "build": "echo \"Globals:\n Function:\n Timeout: 10\n Tracing: Active\n Api:\n TracingEnabled: true\n EndpointConfiguration:\n Type: REGIONAL\n OpenApiVersion: 3.0.3\"",
"context": {
🌟 SAM テンプレート出力コマンドの簡略化
ここまでの内容を全て盛り込んで SAM テンプレートを作成する場合、コマンドが長くなりすぎて毎回実行するのが面倒になってきました…
# CDK で今回使用しない処理を抑制して、SAM テンプレートを出力するコマンド
cdk synth --no-staging --no-version-reporting --no-path-metadata --build='cat sam-globals.yaml' > template.yaml
そこで package.json
の "scripts"
プロパティを設定することで、npm run <stage>
のように独自コマンドを定義できる機能を使用して、SAM テンプレート出力のためのコマンドをより簡単に実行できるようにしてみます!
例えば "cdk2sam": "cdk synth --no-staging --no-version-reporting --no-path-metadata --build='cat sam-globals.yaml'> template.yaml"
と追加すると…
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk",
+ "cdk2sam": "cdk synth --no-staging --no-version-reporting --no-path-metadata --build='cat sam-globals.yaml'> template.yaml"
},
長いオプションを入力せずに npm run cdk2sam
で出力の工夫をした SAM テンプレートが作成されました 😉
# template.yaml に SAM テンプレートの内容を書き出す
npm run cdk2sam
✅ まとめ
SAM テンプレートが作成できれば、あとは SAM CLI でビルド、テスト、デプロイを実施していくだけです!よく使うコマンドとオプションを自分用にまとめておきます φ(•ᴗ•๑)
コマンド | 使いどき |
---|---|
sam build --cached --use-container |
キャッシュを利用したビルド、コンテナランタイムがあれば Lambda 関数の言語ランタイムを気にせずビルドできる。 |
sam local invoke --debug -e event.json [FUNCTION_LOGICAL_ID] |
Lambda 関数にイベント引数を渡してローカル実行。 |
sam local start-api --debug -p 3000 |
API Gateway のエンドポイントをローカルで起動、ポート番号も選べる。 |
sam local start-lambda && aws lambda invoke --function-name "[FUNCTION_NAME]" --endpoint-url "http://127.0.0.1:3001" --no-verify-ssl out.txt |
SAM で定義した Lambda 関数を、別のコードのテストのため呼び出したい場合などに役立つ。詳細は こちら 。 |
sam deploy --no-execute-changeset |
変更セットの実行はせず、変更内容の確認のみ。 |
sam sync --stack-name [STACK_NAME] |
SAM 以外のリソースも変更セットを作成せずにそのままデプロイできるので、開発環境で手早くデプロイしたい時に便利。 |
今後 CDK と SAM のより便利な連携が提供されるかもしれませんが、CDK と SAM の使い分けで悩んでる方の参考になれば!
Discussion