はじめに
Terraform AWS Provider さんにも新機能対応を頑張って欲しいと願う @___nix___ です。
前回、Lambda の90分タイムアウトを Lambda Managed Instances(以下 LMI)で使う手順を Terraform で書きました。
その中で timeout = 5400 だけは Terraform で書けず、apply のあとに CLI で設定して publish し直す、という逃げ方をしています。今回はこれを cdkd で CDK のコードのまま書き、デプロイ1回で済むところまで持っていきます。途中で2回つまずき、そのたびに issue を出して直しています。
背景
まず Terraform 側の状況です。AWS Provider の最新版 6.65.0(日本時間 2026年9月17日リリース)でも、aws_lambda_function の timeout は次のままでした。main ブランチも同じです。
names.AttrTimeout: {
Type: schema.TypeInt,
Optional: true,
Default: 3,
ValidateFunc: validation.IntBetween(1, 900),
},
キャパシティプロバイダや capacity_provider_config は書けるので、LMI のリソース自体は作れます。前回の構成で Terraform だけでは設定できなかったのは、900秒を超えるタイムアウトでした。なんとも中途半端な状態です。
CDK のほうは aws-cdk-lib 2.269.0 に LMI 用の L2(lambda.CapacityProvider)があり、timeout: Duration.minutes(90) と書けば synth したテンプレートに Timeout: 5400 がそのまま入ります。ただ CDK は CloudFormation に依存するので、個人的には苦手です。
そこで cdkd です。CDK アプリはそのままに、CloudFormation を通さず AWS SDK と Cloud Control API を直接呼んでデプロイするツールで、メンテナは後藤さん(@go-to-k)。私も8月から PR を出していてコントリビュータにもなっているので、cdkd で LMI を試してみることにしました。
なお README にあるとおり、cdkd は現時点で dev/test 向けで、本番利用は想定されていません。
概要
結論から書くと、cdkd 0.290.17 では、下のコードをリソースが何もない状態から cdkd deploy 1回でデプロイできました(cdkd の state バケットが以前からあるアカウントです)。そこに至るまでのバージョンごとの結果はこうでした。
| cdkd | 結果 |
|---|---|
| 0.290.6 | キャパシティプロバイダの作成が Resource Handler Internal Failure で失敗 |
| 0.290.11 |
One or more security group IDs are invalid で失敗 |
| 0.290.17 | 1回で成功 |
1つ目は go-to-k/cdkd#3174 を起票して PR #3182 で、2つ目は go-to-k/cdkd#3227 を起票して PR #3231 で直しています。どちらも PR は私が書き、後藤さんにマージしてもらいました。マージ後には後藤さんが、ロールバック時の再作成でも名前を補う修正(#3212)と、統合テストで再試行を数える修正(#3288)を追加で入れてくれています。
対応
CDK のコード
前回と同じくプライベートサブネットに LMI を置きます。今回は NAT を置かず、CloudWatch Logs 用の VPC エンドポイントを作ります。検証に使ったコードをそのまま載せます。
import { App, Duration, Stack } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as lambda from 'aws-cdk-lib/aws-lambda';
const app = new App();
const stack = new Stack(app, 'CdkdLmiStack', { env: { region: 'ap-northeast-1' } });
const vpc = new ec2.Vpc(stack, 'Vpc', {
maxAzs: 3,
natGateways: 0,
subnetConfiguration: [{ name: 'isolated', subnetType: ec2.SubnetType.PRIVATE_ISOLATED }],
});
// CloudWatch Logs 用の VPC エンドポイントを作成
vpc.addInterfaceEndpoint('Logs', { service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS });
const sg = new ec2.SecurityGroup(stack, 'LmiSg', { vpc });
const provider = new lambda.CapacityProvider(stack, 'Provider', {
subnets: vpc.isolatedSubnets,
securityGroups: [sg],
architectures: [lambda.Architecture.ARM_64],
});
const fn = new lambda.Function(stack, 'BatchJob', {
runtime: lambda.Runtime.PYTHON_3_13,
architecture: lambda.Architecture.ARM_64,
memorySize: 2048,
timeout: Duration.minutes(90),
handler: 'index.handler',
code: lambda.Code.fromInline(`
import json, time
def handler(event, context):
print(json.dumps({"remaining_ms": context.get_remaining_time_in_millis(),
"request_id": context.aws_request_id}))
start = time.time()
time.sleep(event.get("sleep", 0))
return {"elapsed_sec": time.time() - start}
`),
});
provider.addFunction(fn, {
perExecutionEnvironmentMaxConcurrency: 4,
executionEnvironmentMemoryGiBPerVCpu: 2,
publishToLatestPublished: true,
});
前回 Terraform で書いた publish_to と capacity_provider_config に当たる設定は、provider.addFunction() が関数に入れてくれます。オペレーターロールも CapacityProvider が作ります。env にアカウントを指定していないので、maxAzs: 3 でもサブネットは2つになります。
このコードを bin/app.ts に保存し、プロジェクト直下に cdk.json を置きます。
{"app":"npx tsx bin/app.ts"}
あとはインストールしてデプロイするだけです(リージョンは AWS_REGION などで ap-northeast-1 を向けておきます)。
npm i -D @go-to-k/cdkd@0.290.17 aws-cdk-lib@2.269.0 constructs tsx typescript
# アカウントで初めて cdkd を使うときだけ(README より)
npx cdkd bootstrap
npx cdkd deploy CdkdLmiStack --yes
私の検証アカウントには以前から cdkd の state バケットがあったので、今回 cdkd bootstrap は実行していません。
実験
概要で書いた 0.290.17 での1回のデプロイを、ログで見ていきます。以下のコマンドはすべて export AWS_REGION=ap-northeast-1 した状態で実行しています。--verbose 付きで実行したので、2回の失敗が再試行で吸収されているのが見えます。
01:47:57.749Z INFO [ProviderRegistry] BatchJob743C9ABD (AWS::Lambda::Function): routing via Cloud Control API (cdkd's SDK Provider does not yet wire CapacityProviderConfig, PublishToLatestPublished — CC API will forward the full property map. ...)
01:48:06.230Z DEBUG [DeployEngine] ⏳ Retrying Provider2281708E in 0.25s (attempt 1/26, 0.25s backoff through this attempt) - CREATE failed for Provider2281708E: The operator role is invalid or doesn't have sufficient permissions. ...
01:48:10.229Z DEBUG [DeployEngine] ⏳ Retrying Provider2281708E in 0.5s (attempt 2/26, 0.75s backoff through this attempt) - CREATE failed for Provider2281708E: One or more security group IDs are invalid. ...
01:48:15.874Z INFO [DeployEngine] [12/14] ✓ Provider2281708E (AWS::Lambda::CapacityProvider) created
01:49:42.031Z INFO [DeployEngine] [14/14] ✓ BatchJob743C9ABD (AWS::Lambda::Function) created
Duration: 108.34s
✓ Deployment completed successfully
1行目のとおり、関数も Cloud Control 経由で作られます。cdkd の Lambda の SDK 実装がまだ CapacityProviderConfig を扱えないためで、SDK 実装側で扱えるようにする issue として go-to-k/cdkd#1616 が立っています(2026年9月17日時点で open)。
デプロイ後に確認した $LATEST.PUBLISHED はこうなっています。前回 CLI で後付けしていた Timeout: 5400 が、最初から入っています。
$ aws lambda get-function-configuration --function-name CdkdLmiStack-BatchJob743C9ABD --qualifier '$LATEST.PUBLISHED' \
--query '{V:Version,State:State,Timeout:Timeout,Mem:MemorySize,Runtime:Runtime,CP:CapacityProviderConfig.LambdaManagedInstancesCapacityProviderConfig.CapacityProviderArn}' \
--output json | tr -d '\n '
{"V":"$LATEST.PUBLISHED","State":"Active","Timeout":5400,"Mem":2048,"Runtime":"python3.13","CP":"arn:aws:lambda:ap-northeast-1:<account-id>:capacity-provider:CdkdLmiStack-Provider2281708E"}
呼び出し方による残り時間の違いも見ておきます。1秒だけ sleep する処理を、非同期と同期で1回ずつ呼びました。
# 非同期
aws lambda invoke --function-name 'CdkdLmiStack-BatchJob743C9ABD:$LATEST.PUBLISHED' --invocation-type Event \
--payload '{"sleep":1}' --cli-binary-format raw-in-base64-out a.json
# 同期
aws lambda invoke --function-name 'CdkdLmiStack-BatchJob743C9ABD:$LATEST.PUBLISHED' \
--payload '{"sleep":1}' --cli-binary-format raw-in-base64-out s.json
残り時間はハンドラの先頭で print しているので、レスポンスではなく CloudWatch Logs から拾います。
aws logs filter-log-events --log-group-name /aws/lambda/CdkdLmiStack-BatchJob743C9ABD \
--filter-pattern remaining_ms --query 'events[].message' --output text | tr '\t' '\n'
{"remaining_ms": 5399998, "request_id": "09454507-..."} ← 非同期
{"remaining_ms": 899996, "request_id": "f88c1065-..."} ← 同期
前回と同じく、同期呼び出しは LMI でも15分のままです。今回は1秒で終わる処理で残り時間を見ただけで、15分を超えて走らせる確認は前回の記事で済ませています。
最後に npx cdkd diff CdkdLmiStack は No changes detected、npx cdkd destroy CdkdLmiStack --force で14リソースすべて削除でき、キャパシティプロバイダ・関数・ロール・VPC の残りがないことを確認しました。
終わりに
Terraform では timeout を ignore_changes で外して CLI で後付けしていたものが、cdkd なら Duration.minutes(90) の1行で済みます。CDK のコードのまま、CloudFormation は通りません。
ただし cdkd はまだ dev/test 向けです。Terraform のほうは、今回確認した AWS Provider 6.65.0 でも timeout に 5400 を指定できません。前回はここを CLI で後付けして回避しました。
2つの issue は、どちらも今回のデプロイで遭遇した問題です。起票から修正、リリースまでがそれぞれ1日足らずで済んだのは、後藤さんがどちらの PR も出してから1日以内にマージし、その後の手当てまで入れてくれたおかげです。cdkd と後藤さんに感謝します。
一言
この記事良かったと少しでも思って頂けたら是非 @___nix___ をフォローしてあげてください。或いは記事に対してリアクションをお願い致します。