IaCジェネレーターを使って、既存のAWS Lambda関数をAWS CDKでデプロイしてみた。
Topicに CloudFormation
とつける日は来ようとは・・・(実の所、CloudFormationはあまり使えていない人)
先日、発表されたIaCジェネレーターを試してみました。(色々やっているうちにだいぶ経ってしまった。)
自分の検証用の環境で試して、その後、業務で使っている環境でも試してみてます。※即実戦投入は結構珍しい。
手順自体はブログとかユーザーガイドに載っていますので、詳しくは書きません。
業務で使うにあたって、料金等発生するのかな?と思って、ブログとか料金ページを見てみましたが、
特に明記がなかったので、無料で使えるようですね。
試している時に、裏でAWS Lambda的なものが動いて、その中でSDKで情報集めているのかな・・・って妄想していました。
Scan
CloudFormationの左ペインに、IaCジェネレーター
というのが出てきているので、こちらをクリックして、スキャンというエリアにある「新しいスキャンを開始」をクリックします。
途中で止められないみたいなので、終わるまで気長に待ちます。
説明書きには 1,000 個のリソースのスキャンには最大 10 分かかることがあります。
とありますが、
自分の検証環境の東京リージョンで試したところ、3,000ちょいありました(そんなにリソースないだろうと思ってたら、そこそこあった)が、10分ほどで終わっています。
別アカウントで、そこも10,000リソースほどありましたが(こっちはもっとあると思って、朝には終わってるかな・・・と思ってScan実行した人)、12分ほどで終わりました。結構早いですね。
終わると、有効日数とスキャンされたリソース数が表示されます。
Scan自体は、リージョン毎に実施しないとダメみたいなので、複数リージョンで色々作っている場合は、各リージョンでScanする必要がありそうです。
(自分の場合は、北バージニアとオレゴンリージョンにもそこそこリソースあるので、この辺りのリージョンでも実施しないとなぁ・・・と思っています。)
Scanの有効期限は30日とのことなので、定期的にアップデートする場合には、
AWS CLIとかAWS SDKを組み込んだLambda関数を作った方がいいのかな?と思ったりします。
テンプレート生成
以前、以下の記事で使ったリソースをテンプレート化しようと思います。
IaCジェネレーター下部にある、「テンプレートの生成」をクリックします。
テンプレート名等を入力し、次へ。
スクリーンショットでは、削除ポリシー
と置換ポリシーを更新
を変更しています。
テンプレートに含めたいリソースを探すのですが、
リソースを検索
という検索フィールド、ここで名前の検索できるといいんですが、できないみたいなので、できるようになると探しやすいなぁ・・・と思いました。
今後のアップデートに期待したいです。
タグでの検索はできるようなので、タグを付けてという対応はできそうです。
※ただ、Lambdaだとタグ付けしないんですよね・・・
すでにAWS CDK等でデプロイしているリソースは選択できないようになっています。
管理下にあるものを他のテンプレートでも使うというケースには使えないのかなと思います。
選んだら、次に進みます。
関連するリソースを表示してくれます。
Roleまで探すと面倒なので、持ってきてくれるのは嬉しいですね。
確認して、問題なければそのまま生成します。
ちょっと待つと完成します。
そのままStackを作ることもできます。
今回は、CDK化するのが目的なので、AWS CDK
のタブに移って、YAMLのテンプレートファイルをダウンロードします。
CDK Migrate
気づいていなかったのですが、IaCジェネレーターと同じ時期に出たんですね。
実行コマンドは、テンプレート画面のAWS CDK
のタブに出ている内容をそのまま使えます。
今回の場合こんな感じでした。
cdk migrate --stack-name csv-to-parquet-template --from-path ./csv-to-parquet-template-1708964869276.yaml --language typescript
注意点としては、cdk migrate
コマンドは、テンプレートを生成した同じリージョンをデフォルトリージョンにしないとダメなようですね。
(つまりは、別リージョンで実施した人です)
元とするリソースがあるリージョンで実行しないとダメというのはわかります。
※デプロイ自体は別リージョンにするのは可能です。
実行が完了すると、CDK環境が構築されます。
L2化
migration実行すると、以下のような stack.tsファイルができます。
作成されたStackファイルを見ると、L1 Constrctで定義されています。
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
export interface CsvToParquetStackProps extends cdk.StackProps {
/**
* An Amazon S3 bucket in the same AWS-Region as your function. The bucket can be in a different AWS-account.
* This property can be replaced with other exclusive properties
*/
readonly lambdaFunction00csvtoparquetarm6400KfnpuCodeS3BucketOneOflKi5g: string;
/**
* The Amazon S3 key of the deployment package.
* This property can be replaced with other exclusive properties
*/
readonly lambdaFunction00csvtoparquetx866400buz6XCodeS3KeyOneOf4UOzg: string;
/**
* The Amazon S3 key of the deployment package.
* This property can be replaced with other exclusive properties
*/
readonly lambdaFunction00csvtoparquetarm6400KfnpuCodeS3KeyOneOfMSjzZ: string;
/**
* An Amazon S3 bucket in the same AWS-Region as your function. The bucket can be in a different AWS-account.
* This property can be replaced with other exclusive properties
*/
readonly lambdaFunction00csvtoparquetx866400buz6XCodeS3BucketOneOfqYp8d: string;
}
export class CsvToParquetStack extends cdk.Stack {
public constructor(scope: cdk.App, id: string, props: CsvToParquetStackProps) {
super(scope, id, props);
// Resources
const iamRole00csvtoparquetarm64rolernwx829v00Qavjr = new iam.CfnRole(this, 'IAMRole00csvtoparquetarm64rolernwx829v00Qavjr', {
path: '/service-role/',
managedPolicyArns: [
'arn:aws:iam::aws:policy/AmazonS3FullAccess',
'arn:aws:iam::123456789012:policy/service-role/AWSLambdaBasicExecutionRole-3c5486e3-e00d-45d4-a59d-09f34b662a62',
],
maxSessionDuration: 3600,
roleName: 'csv-to-parquet-arm64-role-rnwx829v',
assumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'lambda.amazonaws.com',
},
},
],
},
});
iamRole00csvtoparquetarm64rolernwx829v00Qavjr.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const iamRole00csvtoparquetx8664rolel0xh7iad00lXoHe = new iam.CfnRole(this, 'IAMRole00csvtoparquetx8664rolel0xh7iad00lXoHe', {
path: '/service-role/',
managedPolicyArns: [
'arn:aws:iam::aws:policy/AmazonS3FullAccess',
'arn:aws:iam::123456789012:policy/service-role/AWSLambdaBasicExecutionRole-39a1af64-fb11-42a6-8b29-f509eff9a188',
],
maxSessionDuration: 3600,
roleName: 'csv-to-parquet-x86_64-role-l0xh7iad',
assumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'lambda.amazonaws.com',
},
},
],
},
});
iamRole00csvtoparquetx8664rolel0xh7iad00lXoHe.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const lambdaFunction00csvtoparquetarm6400Kfnpu = new lambda.CfnFunction(this, 'LambdaFunction00csvtoparquetarm6400Kfnpu', {
memorySize: 1024,
description: 'CSV To Parquet For arm64',
tracingConfig: {
mode: 'PassThrough',
},
timeout: 300,
runtimeManagementConfig: {
updateRuntimeOn: 'Auto',
},
handler: 'lambda_function.lambda_handler',
code: {
s3Bucket: props.lambdaFunction00csvtoparquetarm6400KfnpuCodeS3BucketOneOflKi5g!,
s3Key: props.lambdaFunction00csvtoparquetarm6400KfnpuCodeS3KeyOneOfMSjzZ!,
},
role: iamRole00csvtoparquetarm64rolernwx829v00Qavjr.attrArn,
fileSystemConfigs: [
],
functionName: 'csv-to-parquet-arm64',
runtime: 'python3.9',
packageType: 'Zip',
loggingConfig: {
logFormat: 'Text',
logGroup: '/aws/lambda/csv-to-parquet-arm64',
},
ephemeralStorage: {
size: 512,
},
layers: [
'arn:aws:lambda:ap-northeast-1:123456789012:layer:pyarrow_arm64:5',
],
architectures: [
'arm64',
],
});
lambdaFunction00csvtoparquetarm6400Kfnpu.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const lambdaFunction00csvtoparquetx866400buz6X = new lambda.CfnFunction(this, 'LambdaFunction00csvtoparquetx866400buz6X', {
memorySize: 1024,
description: 'CSV To Parquet For x86_64',
tracingConfig: {
mode: 'PassThrough',
},
timeout: 300,
runtimeManagementConfig: {
updateRuntimeOn: 'Auto',
},
handler: 'lambda_function.lambda_handler',
code: {
s3Bucket: props.lambdaFunction00csvtoparquetx866400buz6XCodeS3BucketOneOfqYp8d!,
s3Key: props.lambdaFunction00csvtoparquetx866400buz6XCodeS3KeyOneOf4UOzg!,
},
role: iamRole00csvtoparquetx8664rolel0xh7iad00lXoHe.attrArn,
fileSystemConfigs: [
],
functionName: 'csv-to-parquet-x86_64',
runtime: 'python3.9',
packageType: 'Zip',
loggingConfig: {
logFormat: 'Text',
logGroup: '/aws/lambda/csv-to-parquet-x86_64',
},
ephemeralStorage: {
size: 512,
},
layers: [
'arn:aws:lambda:ap-northeast-1:123456789012:layer:pyarrow_x86_64:7',
],
architectures: [
'x86_64',
],
});
lambdaFunction00csvtoparquetx866400buz6X.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const s3Bucket00csvparquesttest2021122300RmKwd = new s3.CfnBucket(this, 'S3Bucket00csvparquesttest2021122300RMKwd', {
notificationConfiguration: {
queueConfigurations: [
],
topicConfigurations: [
],
lambdaConfigurations: [
{
function: lambdaFunction00csvtoparquetx866400buz6X.attrArn,
filter: {
s3Key: {
rules: [
{
value: 'sourcefile/x86_64/',
name: 'Prefix',
},
{
value: '.csv',
name: 'Suffix',
},
],
},
},
event: 's3:ObjectCreated:*',
},
{
function: lambdaFunction00csvtoparquetarm6400Kfnpu.attrArn,
filter: {
s3Key: {
rules: [
{
value: 'sourcefile/arm64/',
name: 'Prefix',
},
{
value: '.csv',
name: 'Suffix',
},
],
},
},
event: 's3:ObjectCreated:*',
},
],
},
publicAccessBlockConfiguration: {
restrictPublicBuckets: true,
ignorePublicAcls: true,
blockPublicPolicy: true,
blockPublicAcls: true,
},
bucketName: 'csv-parquest-test-20211223',
ownershipControls: {
rules: [
{
objectOwnership: 'BucketOwnerEnforced',
},
],
},
bucketEncryption: {
serverSideEncryptionConfiguration: [
{
bucketKeyEnabled: false,
serverSideEncryptionByDefault: {
sseAlgorithm: 'AES256',
},
},
],
},
});
s3Bucket00csvparquesttest2021122300RmKwd.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const lambdaPermission00functioncsvtoparquetarm6400cMWpY = new lambda.CfnPermission(this, 'LambdaPermission00functioncsvtoparquetarm6400cMWpY', {
functionName: lambdaFunction00csvtoparquetarm6400Kfnpu.attrArn,
action: 'lambda:InvokeFunction',
sourceArn: s3Bucket00csvparquesttest2021122300RmKwd.attrArn,
principal: 's3.amazonaws.com',
sourceAccount: '123456789012',
});
lambdaPermission00functioncsvtoparquetarm6400cMWpY.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
const lambdaPermission00functioncsvtoparquetx866400sjsDk = new lambda.CfnPermission(this, 'LambdaPermission00functioncsvtoparquetx866400sjsDk', {
functionName: lambdaFunction00csvtoparquetx866400buz6X.attrArn,
action: 'lambda:InvokeFunction',
sourceArn: s3Bucket00csvparquesttest2021122300RmKwd.attrArn,
principal: 's3.amazonaws.com',
sourceAccount: '123456789012',
});
lambdaPermission00functioncsvtoparquetx866400sjsDk.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.DELETE;
}
}
このままでも、デプロイできると思いますが、管理等をL2 Constrctできるところはやってしまった方がいいと思いますので、やってみました。
サンプルで作ったリソース群としては
- Lambda関数 x2
- それに付随するRole
- S3バケット x1(既存のバケットを利用してますが)
で、そんなには難しいないので、サクッとやってしまいましょう。
Lambdaのコードは、個人的にはsrcなりlambdaなりでディレクトリを作って、その中に置いて、デプロイするのがいいかなと思いますので、それも一緒にやっています。
こんな感じになりました。だいぶコード減りましたし、見やすくなったと思います。
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { LambdaDestination } from 'aws-cdk-lib/aws-s3-notifications';
import path = require('path');
export class CsvToParquetStack extends cdk.Stack {
public constructor(scope: cdk.App, id: string, props: cdk.StackProps) {
super(scope, id, props);
// Amazon S3 (既存バケットを使う)
const s3BucketName = 'csv-parquest-test-20211223';
const s3Bucket = s3.Bucket.fromBucketName(this, 'S3Bucket', s3BucketName);
// AWS Lambda
// Layer(すでにあるLayerを利用)
const layerArm64 = lambda.LayerVersion.fromLayerVersionArn(this, 'LambdaLayerArm64', 'arn:aws:lambda:ap-northeast-1:123456789012:layer:pyarrow_arm64:1');
const layerX86 = lambda.LayerVersion.fromLayerVersionArn(this, 'LambdaLayerX86', 'arn:aws:lambda:ap-northeast-1:123456789012:layer:pyarrow_x86_64:1');
// Lambda関数
const csvToParquetForArm64 = new lambda.Function(this, 'CsvToParquetForArm64', {
memorySize: 1024,
description: 'CSV To Parquet For arm64',
tracing: lambda.Tracing.ACTIVE,
timeout: cdk.Duration.seconds(300),
code: lambda.Code.fromAsset('src'),
handler: 'lambda_function.lambda_handler',
runtime: lambda.Runtime.PYTHON_3_12,
layers: [layerArm64]
});
s3Bucket.grantReadWrite(csvToParquetForArm64);
const csvToParquetForX86 = new lambda.Function(this, 'CsvToParquetForX86', {
memorySize: 1024,
description: 'CSV To Parquet For arm64',
tracing: lambda.Tracing.ACTIVE,
timeout: cdk.Duration.seconds(300),
code: lambda.Code.fromAsset('src'),
handler: 'lambda_function.lambda_handler',
runtime: lambda.Runtime.PYTHON_3_12,
layers: [layerX86]
});
s3Bucket.grantReadWrite(csvToParquetForX86);
// バケット既存の場合は、addEventNotificationを使う
// See https://dev.classmethod.jp/articles/cdk-s3notification-kick-lambda/
s3Bucket.addEventNotification(
s3.EventType.OBJECT_CREATED,
new LambdaDestination(csvToParquetForX86),
{
prefix: 'sourcefile/x86_64/',
suffix: '.csv'
},
);
s3Bucket.addEventNotification(
s3.EventType.OBJECT_CREATED,
new LambdaDestination(csvToParquetForArm64),
{
prefix: 'sourcefile/arm64/',
suffix: '.csv'
}
)
}
}
cdk diff
をして、さて、問題ないっすね。ということで、
徐に cdk deploy
を実行します・・・エラー。
❌ csvToParquet failed: Error [ValidationError]: The logical resource ids [LambdaFunction00csvtoparquetarm6400Kfnpu, LambdaPermission00functioncsvtoparquetx866400sjsDk, IAMRole00csvtoparquetarm64rolernwx829v00Qavjr, LambdaPermission00functioncsvtoparquetarm6400cMWpY, LambdaFunction00csvtoparquetx866400buz6X, IAMRole00csvtoparquetx8664rolel0xh7iad00lXoHe, S3Bucket00csvparquesttest2021122300RMKwd] provided in ResourceToImport do not exist in the template.
at Request.extractError (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:46692)
at Request.callListeners (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:91437)
at Request.emit (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:90885)
at Request.emit (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:199281)
at Request.transition (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:192833)
at AcceptorStateMachine.runTo (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:157705)
at /usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:158035
at Request.<anonymous> (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:193125)
at Request.<anonymous> (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:199356)
at Request.callListeners (/usr/local/share/npm-global/lib/node_modules/aws-cdk/lib/index.js:376:91605) {
code: 'ValidationError',
time: 2024-04-08T16:38:54.396Z,
requestId: '9f647823-0bb2-48a6-ae2d-0fde10ad2708',
statusCode: 400,
retryable: false,
retryDelay: 191.10195176978496
}
The logical resource ids [LambdaFunction00csvtoparquetarm6400Kfnpu, LambdaPermission00functioncsvtoparquetx866400sjsDk, IAMRole00csvtoparquetarm64rolernwx829v00Qavjr, LambdaPermission00functioncsvtoparquetarm6400cMWpY, LambdaFunction00csvtoparquetx866400buz6X, IAMRole00csvtoparquetx8664rolel0xh7iad00lXoHe, S3Bucket00csvparquesttest2021122300RMKwd] provided in ResourceToImport do not exist in the template.
いや、stack.ts からLambdaFunction00csvtoparquetarm6400Kfnpu
とか消したよね?ってなりまして、
よくよく見てみると、
migrate.json
というファイルがあることに気づきました。
そして、そこに LambdaFunction00csvtoparquetarm6400Kfnpu
等定義されておりました。
無論、そのまま使うケースもあるかと思いますが、別途ソースコードを用意しましたので、このファイルが不要です。
なので、migrate.json
を消して、再度 cdk deploy
を実行します。
はい。デプロイ完了です。
csvToParquet: creating CloudFormation changeset...
✅ csvToParquet
✨ Deployment time: 73.26s
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/csvToParquet/b362d8f0-f5cb-11ee-a2e7-0eaa6d8266f1
✨ Total time: 78.29s
コンソールでもちゃんとデプロイできていることがわかります。
あとは動作確認して問題なければOKです。
試してみて
1から作るよりは、短時間でProjectを形にできると思います。
L2 Constrct化は必須なような気がします(私は少なくともします)
実際に業務でも使ってみましたが、
Step Functionsのステートマシンなどもあり、Lambda関数もそこそこあるというものをimportとする形でしたので、同じものを作る&IaCツール管理下に置くという観点では、非常に役に立ちました。
※なお、フローの組み直しなどが発生したので、ほぼ見る影なしですがw
IaCジェネレーター自体は、既存リソースの洗い出しにも便利というXのPOSTを見ましたが、その通りだと思います。
まとめ
昨年末に書いた記事で、結構初期からVPC Lambdaを使っていたことを書いていますが・・・。
実は、機能別に関数を用意していたので、機能追加とともに、Lambda関数が増えていって、
最終的には結構な数のLambda関数を管理していて、デプロイとかもマネコンでやってたので、めっちゃ大変(CLIでやればよかったという話もありますが)でした。
Serverless FrameworkとかAWS SAMが出てきて、こういうツールで管理できたらいいよねぇ・・・っていう話があったんですが、なかなか既存リソースをそのまま管理下に置くのは大変という話もあり、挫折したんですが、その時にこれ欲しかったです!w(まあ、この機能でも置き換えが必要にはなりますが)
と言いつつ、現在、自身の検証環境でも、業務で使っている環境でもIaC管理下にないAWS Lambdaが結構ある(ついでに言うと、古いNode.jsのランタイムのLambdaがあったりする)ので、これを使って、IaCツールというかAWS CDKで管理できるように進めていこうかなと思います。
Discussion