1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Lambda Managed Instances の90分を cdkd で CDK のまま書く

1
Posted at

はじめに

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___ をフォローしてあげてください。或いは記事に対してリアクションをお願い致します。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?