AgentCore ランタイムを (とりあえず) CDK カスタムリソースでデプロイする方法
2025年 7月に Amazon Bedrock AgentCore がプレビューとしてリリースされました。プレビュー版だけに 2025年 9月時点ではまだ AgentCore の CloudFormation は公開されていません。ゆえに CDK の Constructs も提供されていません。AgentCore ランタイムのデプロイ方法として、Starter Toolkit を使う方法やマニュアルでのセットアップ方法がドキュメントでは案内されていますが、CDK 厨としてはやはり CDK でデプロイしたいものです。
CDK では AWS API を使ってカスタムリソースとしてリソースを作成できるので AgentCore のリソースも作成できそうです。正式に CDK で AgentCore をサポートするまでの暫定的な方法になりますが、参考までに紹介したいと思います。
(最終的な成果物 (Construct) はこちらに)
CDK における AWS API を使ったカスタムリソース
詳しくはドキュメントを参照いただければですが、簡単に紹介すると cdk.custom_resources.AwsCustomResource
という Construct を使って、以下のように記述できます。
new cdk.custom_resources.AwsCustomResource(this, 'AgentCoreRuntime', {
onCreate: {
// 新規作成時に利用する API
service: 'Bedrock-AgentCore-Control',
action: 'createAgentRuntime',
parameters: {}, // API に渡すパラメータ
physicalResourceId,
},
onUpdate: {
// 更新時に利用する API。onCreate と同じように設定
},
onDelete: {
// 削除時に利用する API。onCreate と同じように設定
},
policy: // 内部的に作成される Lambda 関数に与える IAM ポリシー
// : その他のオプション
});
スタックの新規作成、更新、削除にそれぞれ呼び出す API を定義してあげます。
内部的には Lambda 関数が作成され、AWS SDK for JavaScript を利用して API が呼び出されます。なので例えば AgentCore ランタイムを作成する API createAgentRuntime
に渡すパラメータは AWS SDK ドキュメントの CreateAgentRuntimeCommand
を参考にすることができます。
AgentCore ランタイムをデプロイするカスタムリソース
AgentCore ランタイムをカスタムリソースとしてデプロイするには、サービスとして Bedrock-AgentCore-Control
、アクションとして createAgentRuntime
、updateAgentRuntime
、deleteAgentRuntime
をそれぞれ利用します。
最終的なコードが知りたい方はこちらに。
onCreate
(作成時) 部分
SDK のドキュメント CreateAgentRuntimeCommand
を参考に、今回は必須のパラメータを設定していきます。
onCreate: {
service: 'Bedrock-AgentCore-Control',
action: 'createAgentRuntime',
parameters: {
// AgentCore ランタイムの名前。有効な文字は a~z、A~Z、0~9、_ (アンダースコア)
agentRuntimeName: 'MyAgent',
agentRuntimeArtifact: {
containerConfiguration: {
// コンテナイメージのリポジトリ URI
containerUri: `${repository.repositoryUri}:latest`,
}
},
// AgentCore ランタイムに付与する実行ロール
roleArn: executionRole.roleArn,
networkConfiguration: {
// ネットワークモードに現在指定可能なのは 'PUBLIC' のみ
networkMode: 'PUBLIC',
}
},
// 物理リソース ID は API の実行結果のレスポンスから 'agentRuntimeId' の値を設定
physicalResourceId: cr.PhysicalResourceId.fromResponse('agentRuntimeId'),
},
物理リソース ID physicalResourceId
として、API のレスポンス結果から agentRuntimeId
の値を参照して設定します。これによって、更新時、削除時に参照できるようになります。
今回は必須パラメータのみ指定しましたが、他に以下のようなパラメータが指定できます。詳細、および全てのパラメータは SDK ドキュメントを参照してください。
-
authorizerConfiguration
: JWT で認証する場合に指定します -
environmentVariables
: ランタイムに設定する環境変数を指定します -
protocolConfiguration
: プロトコル (HTTP か MCP) を指定します
onUpdate
(更新時) 部分
SDK のドキュメント UpdateAgentRuntimeCommand
を参考に、同様に必須のパラメータを設定していきます。作成時と比較して AgentCore ランタイムの名前の代わりにランタイムの ID が必須パラメータになります。
onUpdate: {
service: 'Bedrock-AgentCore-Control',
action: 'updateAgentRuntime',
parameters: {
// ランタイムの ID が必須指定。作成時に設定した物理リソース ID を指定
agentRuntimeId: new cr.PhysicalResourceIdReference(),
agentRuntimeArtifact: {
containerConfiguration: {
containerUri: `${repository.repositoryUri}:latest`,
}
},
roleArn: executionRole.roleArn,
networkConfiguration: {
networkMode: networkMode,
},
},
},
onDelete
(削除時) 部分
SDK のドキュメント DeleteAgentRuntimeCommand
を参考に、同様に必須のパラメータを設定していきます。削除時はランタイムの ID だけが必須パラメータになります。
onDelete: {
service: 'Bedrock-AgentCore-Control',
action: 'deleteAgentRuntime',
parameters: {
// ランタイムの ID が必須指定。作成時に設定した物理リソース ID を指定
agentRuntimeId: new cr.PhysicalResourceIdReference(),
},
},
policy
部分
AWS API によるカスタムリソースは内部的に作成された Lambda 関数によって実行されます。その Lambda 関数に付与する権限を policy
で設定します。呼び出す API の権限だけ必要な場合は、例えば以下のような記述で良いのですが、これでは権限不足になってしまいます。
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
AgentCore ランタイムを作成する場合は、他にも以下の権限が必要になります。
- bedrock-agentcore:CreateAgentRuntimeEndpoint
- bedrock-agentcore:CreateWorkloadIdentity,
- bedrock-agentcore:UpdateAgentRuntimeEndpoint,
- bedrock-agentcore:UpdateWorkloadIdentity,
- bedrock-agentcore:DeleteAgentRuntimeEndpoint,
- bedrock-agentcore:DeleteWorkloadIdentity,
また、Lambda 関数が AgentCore に対して実行ロールを付与する権限も必要です。
- iam:PassRole
まとめると、cr.AwsCustomResourcePolicy.fromStatements()
を使って以下のように指定します (長いので折りたたみます)。
AwsCustomResource policy 部分
policy: cr.AwsCustomResourcePolicy.fromStatements(
[
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:CreateAgentRuntime',
'bedrock-agentcore:CreateAgentRuntimeEndpoint',
],
resources: [ '*' ],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:CreateWorkloadIdentity',
'bedrock-agentcore:UpdateWorkloadIdentity',
'bedrock-agentcore:DeleteWorkloadIdentity',
],
resources: [
// ARN を組み立て
cdk.Stack.of(this).formatArn({
service: 'bedrock-agentcore',
resource: 'workload-identity-directory',
resourceName: `*`,
}),
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:UpdateAgentRuntime',
'bedrock-agentcore:UpdateAgentRuntimeEndpoint',
'bedrock-agentcore:DeleteAgentRuntime',
'bedrock-agentcore:DeleteAgentRuntimeEndpoint',
],
resources: [
// ARN を組み立て
cdk.Stack.of(this).formatArn({
service: 'bedrock-agentcore',
resource: 'runtime',
resourceName: `${agentCoreRuntimeName}*`,
}),
],
}),
new iam.PolicyStatement({
actions: [
'iam:PassRole',
],
resources: [ executionRole.roleArn ],
}),
]
),
他のオプション
念のため、最新の SDK を利用するようにします (デプロイ時間が少し長くなります)。また、Lambda が出力するログもデフォルト 2年間なので不要であれば短くします。
installLatestAwsSdk: true,
logRetention: cdk.aws_logs.RetentionDays.ONE_WEEK,
ランタイム作成時には実在するコンテナイメージが必要
ランタイム作成時にはプッシュ済みのコンテナイメージがリポジトリに存在している必要があります。今回は CDK でコンテナイメージもプッシュしたかったので、cdklabs/cdk-docker-image-deployment というものを利用しました。
npm install cdk-docker-image-deployment
以下のように記述します。
import * as imagedeploy from 'cdk-docker-image-deployment';
const deployedImage = new imagedeploy.DockerImageDeployment(this, 'ImageDeploymentWithTag', {
// Dockerfile があるディレクトリを指定
source: imagedeploy.Source.directory('assets/agent/'),
// ECR リポジトリとタグを指定
destination: imagedeploy.Destination.ecr(repository, {
tag: 'latest',
}),
});
全体的なコード
Construct として以下についてまとめました。
- ECR リポジトリの作成
- コンテナイメージのプッシュ
- AgentCore ランタイムに付与する実行ロールの作成
- AgentCore ランタイムをカスタムリソースとして作成
Construct としてまとめたコード
import * as cdk from 'aws-cdk-lib';
import {
aws_iam as iam,
custom_resources as cr,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as imagedeploy from 'cdk-docker-image-deployment';
type AgentCoreRuntimeProps = {
agentRuntimeName: string; // AgentCore ランタイムに設定する名前
}
export class AgentCoreRuntime extends Construct {
public readonly agentRuntimeArn: string;
constructor (scope: Construct, id: string, props: AgentCoreRuntimeProps){
super(scope, id);
const {
agentRuntimeName,
} = props;
// -----------------------------
// ECR repository
// -----------------------------
const repository = new cdk.aws_ecr.Repository(this, 'Repository', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
emptyOnDelete: true,
});
// Container image push
const deployedImage = new imagedeploy.DockerImageDeployment(this, 'ImageDeploymentWithTag', {
source: imagedeploy.Source.directory('assets/agent/'),
destination: imagedeploy.Destination.ecr(repository, {
tag: 'latest',
}),
});
// -----------------------------
// Execution Role
// -----------------------------
const executionRole = new iam.Role(this, 'ExecutionRole', {
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com').withConditions({
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account,
},
ArnLike: {
'aws:SourceArn': cdk.Stack.of(this).formatArn({service: 'bedrock-agentcore', resource: '*'}),
},
}),
inlinePolicies: {
'policy': new iam.PolicyDocument({
statements:[
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock:InvokeModel',
'bedrock:InvokeModelWithResponseStream',
],
resources: [ '*' ],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'ecr:GetAuthorizationToken',
],
resources: [ '*' ],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'ecr:GetDownloadUrlForLayer',
'ecr:BatchGetImage',
],
resources: [
repository.repositoryArn
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:GetWorkloadAccessToken',
'bedrock-agentcore:GetWorkloadAccessTokenForUserId',
'bedrock-agentcore:GetWorkloadAccessTokenForJWT',
],
resources: [ '*' ],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'cloudwatch:PutMetricData',
],
resources: [ '*' ],
conditions: {
StringEquals: {
'cloudwatch:namespace': 'bedrock-agentcore',
},
},
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'logs:DescribeLogStreams',
'logs:CreateLogGroup',
],
resources: [
cdk.Stack.of(this).formatArn({service: 'logs', resource: 'log-group:/aws/bedrock-agentcore/runtimes/*'}),
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'logs:CreateLogStream',
'logs:PutLogEvents',
],
resources: [
cdk.Stack.of(this).formatArn({service: 'logs', resource: 'log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*'}),
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'logs:DescribeLogGroups',
],
resources: [
cdk.Stack.of(this).formatArn({service: 'logs', resource: 'log-group:*'}),
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'xray:PutTelemetryRecords',
'xray:GetSamplingRules',
'xray:GetSamplingTargets',
'xray:UpdateTraceSegmentDestination',
'xray:PutTraceSegments',
],
resources: [ '*' ],
}),
]
})
}
});
// -----------------------------
// AgentCore Runtime using Custom Resource
// -----------------------------
// Common parameter on create and update
const commonParameter = {
agentRuntimeArtifact: {
containerConfiguration: {
containerUri: `${repository.repositoryUri}:latest`,
}
},
roleArn: executionRole.roleArn,
networkConfiguration: {
networkMode: 'PUBLIC',
},
//// Optional Parameters
// description: '...',
// authorizerConfiguration: {
// customJWTAuthorizer: {
// discoveryUrl: `https://cognito-idp.${REGION}.amazonaws.com/${COGNITO_USER_POOL_ID}/.well-known/openid-configuration`,
// allowedClients: [ `${COGNITO_CLIENT_ID}` ]
// }
// },
// environmentVariables: {
// "<keys>": "STRING_VALUE",
// },
// protocolConfiguration: {
// serverProtocol: 'MCP' || 'HTTP',
// },
};
// AgentCore Runtime using AwsCustomResource
const agentCoreRuntime = new cr.AwsCustomResource(this, 'AgentCoreRuntime', {
onCreate: {
service: 'Bedrock-AgentCore-Control',
action: 'createAgentRuntime',
parameters: {
agentRuntimeName: agentRuntimeName,
...commonParameter,
},
physicalResourceId: cr.PhysicalResourceId.fromResponse('agentRuntimeId'),
},
onUpdate: {
service: 'Bedrock-AgentCore-Control',
action: 'updateAgentRuntime',
parameters: {
agentRuntimeId: new cr.PhysicalResourceIdReference(),
...commonParameter,
},
},
onDelete: {
service: 'Bedrock-AgentCore-Control',
action: 'deleteAgentRuntime',
parameters: {
agentRuntimeId: new cr.PhysicalResourceIdReference(),
},
},
policy: cr.AwsCustomResourcePolicy.fromStatements(
[
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:CreateAgentRuntime',
'bedrock-agentcore:CreateAgentRuntimeEndpoint',
],
resources: [ '*' ],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:CreateWorkloadIdentity',
'bedrock-agentcore:UpdateWorkloadIdentity',
'bedrock-agentcore:DeleteWorkloadIdentity',
],
resources: [
cdk.Stack.of(this).formatArn({service: 'bedrock-agentcore', resource: 'workload-identity-directory', resourceName: `*`}),
],
}),
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:UpdateAgentRuntime',
'bedrock-agentcore:UpdateAgentRuntimeEndpoint',
'bedrock-agentcore:DeleteAgentRuntime',
'bedrock-agentcore:DeleteAgentRuntimeEndpoint',
],
resources: [
cdk.Stack.of(this).formatArn({service: 'bedrock-agentcore', resource: 'runtime', resourceName: `${agentRuntimeName}*`})
],
}),
new iam.PolicyStatement({
actions: [
'iam:PassRole',
],
resources: [ executionRole.roleArn ],
}),
]
),
installLatestAwsSdk: true,
logRetention: cdk.aws_logs.RetentionDays.ONE_WEEK,
});
// Wait for image deployed
agentCoreRuntime.node.addDependency(deployedImage);
// AgentCore Runtime ARN
const agentRuntimeArn = cdk.Stack.of(this).formatArn({
service: 'bedrock-agentcore',
resource: 'runtime',
resourceName: `${agentCoreRuntime.getResponseField('agentRuntimeId')}`
});
this.agentRuntimeArn = agentRuntimeArn;
// -----------------------------
// Output
// -----------------------------
new cdk.CfnOutput(this, 'EcrRepository-Uri', {
description: 'AgentCore ECR repository URI',
value: repository.repositoryUri,
});
new cdk.CfnOutput(this, 'AgentCoreExecutionRole-Arn', {
description: 'AgentCore Execution Role Arn',
value: executionRole.roleArn,
});
new cdk.CfnOutput(this, 'AgentCoreRuntime-Arn', {
description: 'AgentCore Runtime Arn',
value: agentRuntimeArn,
});
}
}
トラブルシューティング
今回のコードに辿り着くまでなんだかんだ試行錯誤がありました。どなたかのお役に立てるかもしれないので記録しておきます。
1. "UnknownError" が出力される
CDK でデプロイしようとしたところ、以下のように UnknownError
というエラーが出力されました。
2:45:10 | CREATE_FAILED | Custom::AWS | AgentCoreAgentCoreRuntimeA1B2C3D4
Received response status [FAILED] from custom resource. Message returned: UnknownError (RequestId: XXXXXXXX)
これはカスタムリソースを作成する際の Lambda のポリシーに権限が足りない場合に出力されました。前述の policy
に指定する権限 を参照ください。
2. WorkloadIdentity に関する権限不足
4:18:22 | CREATE_FAILED | Custom::AWS | AgentCoreAgentCoreRuntimeB04EC36A
Received response status [FAILED] from custom resource. Message returned: User: arn:aws:sts::123456789012:assumed-role/XXXXX/XXXXX
is not authorized to perform: bedrock-agentcore:CreateWorkloadIdentity on resource: arn:aws:bedrock-agentcore:us-east-1:123456789012:workload-identity-directory/default
because no identity-based policy allows the bedrock-agentcore:CreateWorkloadIdentity action (Service: AgentCredentialProvider, Status Code: 403, Request ID: XXXXXXXX) (SDK Attempt Count: 1) (RequestId: XXXXXXXX)
こちらも 1. と同様にカスタムリソースを作成する際の Lambda のポリシーに権限が足りない場合に出力されました。前述の policy
に指定する権限 を参照ください。
3. "The image with identifier 'ImageIdentifier(ImageTag=latest)' does not exist in the repository ..." と出力される
3:11:10 | CREATE_FAILED | Custom::AWS | AgentCoreAgentCoreRuntimeA1B2C3D4
Received response status [FAILED] from custom resource. Message returned: The image with identifier 'ImageIdentifier(ImageTag=latest)' does not exist in the re
pository with name '<RepositoryName>' in registry with id '123456789012'. (RequestId: XXXXXXXX)
AgentCore を作成する際に、コンテナイメージのリポジトリ URI を指定しますが、そのイメージが実在している必要があります。今回は CDK からコンテナイメージもプッシュしたかったので、cdklabs/cdk-docker-image-deployment というものを利用しました。前述の ランタイム作成時には実在するコンテナイメージが必要 を参照ください。
4. それでも解決できない場合
カスタムリソースとして実行される Lambda が API 実行のログを CloudWatch Logs に出力していますので、確認してみましょう。
ロググループは /aws/lambda/<スタック名>-AWS<ランダム文字列>-<ランダム文字列>
という形式になっていると思います。
まとめ
AgentCore ランタイムをカスタムリソースでデプロイする方法をご紹介しました。API 実行だけでデプロイできるリソースであれば、カスタムリソースとして Lambda を書かずともできる点もポイントですかね。作ったり消したりする機会が多いので、どうしても CDK でデプロイできるようにしておきたかったのです。あくまで CDK で正式にサポートされるまでの暫定的な方法になるかと思いますのでご留意ください。
Discussion