はじめに
これまではSAMのテンプレートをYMLでがりがり書いていましたけど、定義したいリソースが増えてくるとYMLファイルが長くなり、作業効率がよくありません。作業効率を向上させるために、AWS CDKを使ってテンプレートを書こうと思い、CDKからSAMテンプレートを出力し、デプロイできることを確認してみました。
環境について
本記事は下記環境で実行しています。
- AWS SAM CLI version 0.39.0
- AWS CDK 1.76.0 (build c207717)
- typescript Version 4.1.2
- VSCode バージョン 1.51.1
- Docker Desktop 2.1.0.5
- macOS Catalina バージョン 10.15.7
手順
それでは、CDKからSAMテンプレートを作成する手順です
CDK
CDKのプロジェクトを作成します
% mkdir adventcalander2020; cd adventcalander2020
% cdk init --language=typescript
-
cdk init
を実行する時にディレクトリが空じゃないと怒られます
CDKのバージョンが古い時は最新版にしておきます
これはやってもやらなくてもよいですが、CDKは頻繁にバージョンがアップしますので、新しいプロジェクトの時は最新を使ったほうがよいと思います。最新にする時にはnpm-check-updatesを使っています。
% ncu -u
(出力は割愛)
% npm install
(出力は割愛)
% which cdk
./node_modules/.bin/cdk <-- ローカルの.binが先になるようにパスをきっています
% cdk --version
1.76.0 (build c207717) <-- この記事を書いている時の最新版
Lambda 関数を用意します
今回はAPIGatewayからのリクエストに対してレスポンスを返すLambda関数を用意しました。なので、超シンプルです
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'isBase64Encoded': False,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'message': 'Hello World.',
}),
}
CDKを使ってSAMのテンプレートを記述します
コーディング中に必要となるモジュールはインストールします
npm install -s @aws-cdk/aws-sam
CDKのstackはこちらです。CDKのコードを書き終えたらcdk lsで認識されていることを確認して、cdk synthで出力します.
import * as cdk from '@aws-cdk/core';
import * as sam from '@aws-cdk/aws-sam';
import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam';
export class Adventcalander2020Stack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const environment = this.node.tryGetContext('env')
// The code that defines your stack goes here
const lambdaRole = new Role(
this,
'lambdaRole',{
assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
],
path: "/lambda/",
}
);
const lambdaFunctionHelloWorld = new sam.CfnFunction(
this,
'lambdaFunctionHelloWorld',{
codeUri: "lambda/HelloWorld/src",
functionName: `${environment}-HelloWorld`,
handler: 'app.lambda_handler',
runtime: 'python3.8',
role: lambdaRole.roleArn,
autoPublishAlias:`${environment}`,
events:{
GetMethod:{
type: "Api",
properties:{
path: "/helloworld",
method: "GET",
}
}
}
}
);
}
}
% cdk ls
Adventcalander2020Stack
% cdk synth > template.yml
(出力は省略)
これでSAMテンプレートが出力されます。
SAM
テンプレートが用意できたらsamの出番です。
Lambdaから応答があるかどうかの確認をします。事前にDockerを起動しておきます。
% sam local start-api
Mounting lambdaFunctionHelloWorld at http://127.0.0.1:3000/helloworld [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-12-05 06:15:03 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
これで起動できたので、curl
やPostman
でAPIをコールします。
% curl http://127.0.0.1:3000/helloworld
{"message": "Hello World."}%
Lambdaが動作していることが確認できたのでdeployします。
sam deploy --guided
初回のdeployは--guided
を付けて実行すると設定がsamconfig.toml
というファイルに保存されるのでよいと思います。
無事にdeployできたらAWSコンソールでURLを調べて、呼び出してみます。
curl https://******.execute-api.ap-northeast-1.amazonaws.com/Prod/helloworld
{"message": "Hello World."}%
はい、無事に呼び出せませたね。
パラメータはCDKとSAMのどちらで吸収するのがいいのか?
実運用すると、環境は異なるけど1つのテンプレートで運用することが多いと思います。その場合、環境別のパラメーターをどうするのか?という問題が出てくると思います。
CDKの場合
いろんな方法があると思いますが、私の場合はcdk.jsonに環境別のパラメータを記述し、それを読み込む方法をよく使います。
{
"app": "npx ts-node bin/cdk.ts",
"context": {
"stg":{
"BucketName":"stgbucket"
},
"prod":{
"BucketName":"prodbucket"
}
}
}
const app = new cdk.App();
const environment: string = app.node.tryGetContext('env')
const params = app.node.tryGetContext(environment)
このようにparamsで読み込んで、コンストラクターに渡してあげて、下のように使います。
const contentBucket = new s3.Bucket(this, "ContentBucket", {
bucketName: `${props.BucketName}`,
})
cdkのコマンドを実行する時には、cdk synth -c env=prodとすれば、cdk.jsonの内容を読み込んでくれます。
SAMの場合
SAMでパラメータを渡したい場合は、CDKではパラメータは読み込まず、CloudFormationの組み込み関数である、Fn::RefやFn::Subのままテンプレートを出力できるようにします。
CloudFormationパラメータをCDKで以下のように宣言します。
const environment = new cdk.CfnParameter(this, 'environment',{
type: 'String',
});
宣言をされたパラメータは、以下のように使います。
functionName: cdk.Fn.sub(`\${environment}-HelloWorld`),
handler: 'app.lambda_handler',
runtime: 'python3.8',
role: lambdaRole.roleArn,
autoPublishAlias: cdk.Fn.ref('environment'),
これで出力してみると、CloudFormationの組み込む関数のままテンプレートを出力できています。
Parameters:
environment:
Type: String
(省略)
AutoPublishAlias:
Ref: environment
FunctionName:
Fn::Sub: ${environment}-HelloWorld
どっちがいいの?
どっちでも結果は同じなので、どっちを選択してもよいですが、実際の開発になると複数人で作業することになるし、CI/CDをどうするのかという話も絡んでくるので、チームの中でやりやすい方法でよいかと思います。
最後に
SAMのテンプレートもCDKで出力できることを確認できました。
SAMテンプレートの中で、Lambda関数を複数定義したい時には、TypeScriptのループ処理、関数やクラスを使えばとんでもなく長くなるCloudFormationのYAMLから開放されます。また、VSCodeのコード補完が使えるメリットがあります。
CDKのSAMには、High-level Constructsは無く、Low-level Constructsしかありませんが、メリットだけでデメリットは無いと思いますので、がんがんCDKを使ってSAMテンプレートを出力していきたいと思います。
参考にしたほうがよいサイト
CDKやSAMを使ったことがない場合は、以下のサイトを参考にするといいと思います。
-
AWS サービス別資料
- このページにある、AWS Serverless Application Modelと*AWS Cloud Development Kit (CDK)*はまず最初に読みましょう。
-
CDK workshop
- はじめてCDKを触るときにはこのworkshopをやるとよいと思います。
-
aws-samples/aws-cdk-examples
- CDKのサンプルがたくさんありますので参考になります。