【CDK】Amazon Bedrock AgentCore L2 ConstructでStrands Agentsをデプロイしてみた
はじめに
こんにちは、スーパーマーケットが大好きなコンサルティング部の神野です。
先日AWS CDK v2.221.0がリリースされました!
このバージョンで、Amazon Bedrock AgentCore(以下、AgentCore) の L2 Construct が追加されましたー!
AgentCore は L1 Construct で提供されていましたが、L2 Construct が使えるようになったことで、より簡単にAIエージェントをデプロイできるようになりました。
今回は、この AgentCore L2 Construct を使って、シンプルな Strands Agents をデプロイしてみたので、その手順を共有したいと思います!
L2 Construct の嬉しさ
今までは、AgentCore Runtime をデプロイするには以下のような方法がありました。
- Starter Toolkit CLI:
agentcore configure→agentcore launchでデプロイ- 具体的なデプロイ方法は下記記事で紹介しています。
- AWS CLI: 細かいパラメータを手動で指定
- CDK L1 Constructs: CloudFormationの直接マッピング
- Terraform: 直近追加されました!
そして今回追加されたのが L2 Construct です!
L2 Construct の利点は下記になります。
- ECR・IAMロールが自動作成される
- デフォルト値が充実していて、最小限のコードで動く
適切に抽象化されているので、多くのパラメータを意識しなくても良いのは嬉しいですね。
Starter Toolkit も便利なのですが、AgentCore だけは IaC で管理できず別管理になってしまうので、AgentCore のインフラをコードで管理したい方にとって、これは嬉しいアップデートですよね!
実装してみる
それでは、実際にデプロイしていきましょう。
前提条件
私が使った環境は下記になります。
- Node.js v24.10.0
- npm v11.6.1
- Docker: v27.5.1
プロジェクト構成
今回作成するプロジェクトの構成はこんな感じです。
CDK のテンプレートから作成して、agent フォルダにエージェント関連の処理を記載します。
.
├── agent/
│ ├── agent.py # Python Agent
│ ├── requirements.txt # Python依存関係
│ ├── Dockerfile # コンテナ定義
│ └── .dockerignore
├── lib/
│ └── sample-agentcore-l2-stack.ts # CDK Stack
├── bin/
│ └── sample-agentcore-l2.ts # CDK App
└── package.json
簡単なコードですがサンプルも下記レポジトリで公開しているので、
必要に応じてご参照ください。
CDK のテンプレートから作成
下記コマンドでCDK のテンプレートを作成します。
cdk init --language=typescript
CDK依存関係の確認
package.json を確認します。今回使用するバージョンは2.221.0です。
{
"dependencies": {
"aws-cdk-lib": "2.221.0",
"@aws-cdk/aws-bedrock-agentcore-alpha": "2.221.0-alpha.0",
"constructs": "^10.0.0"
}
}
使用しているバージョンが異なる場合はアップデートしておきましょう。
npm install
Strands Agentsの実装
app.entrypointをentrypointとなる処理に指定して使用します。
シンプルに天気を返却するツールも@toolでツール指定して使用します。
"""
Simple Strands Agent for AgentCore Runtime
Uses BedrockAgentCoreApp for simplified deployment
"""
from strands import Agent, tool
from strands.models import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp
# Initialize the AgentCore app
app = BedrockAgentCoreApp()
@tool
def get_weather(city: str) -> str:
"""
Get the current weather for a specified city.
Args:
city: The name of the city
Returns:
A string describing the weather
"""
# This is a dummy implementation for demonstration
# In a real application, you would call a weather API
weather_data = {
"Tokyo": "晴れ、気温25度",
"東京": "晴れ、気温25度",
"Osaka": "曇り、気温22度",
"大阪": "曇り、気温22度",
"New York": "Rainy, 18°C",
"London": "Foggy, 15°C",
}
return weather_data.get(city, f"{city}の天気情報は現在利用できません")
@app.entrypoint
async def entrypoint(payload):
"""
Main entrypoint for the agent.
This function is called when the agent is invoked.
Args:
payload: The input payload containing prompt and optional model config
Yields:
Streaming messages from the agent
"""
# Extract message and model configuration from payload
message = payload.get("prompt", "")
model_config = payload.get("model", {})
model_id = model_config.get("modelId", "anthropic.claude-3-5-haiku-20241022-v1:0")
# Initialize Bedrock model
model = BedrockModel(
model_id=model_id,
params={"max_tokens": 4096, "temperature": 0.7},
region="us-west-2"
)
# Create agent with the weather tool
agent = Agent(
model=model,
tools=[get_weather],
system_prompt="""あなたは親切なAIアシスタントです。
ユーザーの質問に丁寧に答えてください。
天気情報が必要な場合は、get_weatherツールを使用してください。"""
)
# Stream responses back to the caller
stream_messages = agent.stream_async(message)
async for msg in stream_messages:
if "event" in msg:
yield msg
if __name__ == "__main__":
# Run the app when executed directly
app.run()
依存関係をrequirements.txtに記載します。Strands AgentsのパッケージとAgentCoreのパッケージのみインストールします。
strands-agents
bedrock-agentcore
Dockerfileも記載します。
今までStarter Toolkitで自動生成されたものを参考に作成しました。
# Dockerfile for AgentCore Runtime
# Must be ARM64 architecture for AgentCore Runtime
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
# Configure UV for container environment
ENV UV_SYSTEM_PYTHON=1 \
UV_COMPILE_BYTECODE=1
# Copy requirements and install dependencies
COPY requirements.txt requirements.txt
RUN uv pip install -r requirements.txt
# Install OpenTelemetry for observability
RUN uv pip install aws-opentelemetry-distro>=0.10.1
# Set AWS region environment variables
ENV AWS_REGION=us-west-2 \
AWS_DEFAULT_REGION=us-west-2
# Signal that this is running in Docker
ENV DOCKER_CONTAINER=1
# Create non-root user for security
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
# Expose required ports
EXPOSE 8080
EXPOSE 8000
# Copy application code
COPY . .
# Run the application with OpenTelemetry instrumentation
CMD ["opentelemetry-instrument", "python", "-m", "agent"]
CDKの実装
いよいよ、CDK のコードを書いていきます!
最小限の構成で実装してみます!
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import { Construct } from "constructs";
import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha";
export class SampleAgentcoreL2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ローカルのDockerイメージをビルド
const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromAsset(
path.join(__dirname, "../agent"),
);
// AgentCore Runtime (L2 Construct)
const runtime = new agentcore.Runtime(this, "StrandsAgentRuntime", {
runtimeName: "simpleStrandsAgent",
agentRuntimeArtifact: agentRuntimeArtifact,
description: "Simple Strands Agent with weather tool",
});
// 出力
new cdk.CfnOutput(this, "RuntimeArn", {
value: runtime.agentRuntimeArn,
});
}
}
めちゃくちゃシンプルでいいですね。エージェントを実装したパスを AgentRuntimeArtifact.fromAsset で指定して、Runtime に渡すだけビルドして ECR も自動作成して、イメージをプッシュする一連の作業が自動で行われるのは便利ですし、IAM ロールも指定しなければ自動で作成されて便利です。
Starter Toolkit と似たレベルでシンプルかつ自動作成が行われて良いですね。
参考:L1の記載方法
L1 だと下記のような書き方になるので、比較すると L2 はやはりシンプルですね。
ECR や IAM Role を指定しなくても自動生成されるのが嬉しいポイントですね。
コード全量
import * as path from 'node:path';
import * as cdk from 'aws-cdk-lib';
import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore';
import * as ecrAssets from 'aws-cdk-lib/aws-ecr-assets';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export interface MastraAgentCoreStackProps extends cdk.StackProps {
readonly dockerAssetDirectory?: string;
readonly dockerfileName?: string;
readonly agentRuntimeName?: string;
readonly agentRuntimeEndpointName?: string;
}
export class CdkStack extends cdk.Stack {
constructor(
scope: Construct,
id: string,
props: MastraAgentCoreStackProps = {},
) {
super(scope, id, props);
const {
dockerAssetDirectory = path.join(__dirname, '..', '..'),
dockerfileName = 'Dockerfile.agentcore',
agentRuntimeName = 'MastraAgentRuntime',
agentRuntimeEndpointName = 'MastraAgentEndpoint',
} = props;
const dockerImageAsset = new ecrAssets.DockerImageAsset(
this,
'MastraAgentDockerAsset',
{
directory: dockerAssetDirectory,
file: dockerfileName,
platform: ecrAssets.Platform.LINUX_ARM64,
exclude: ['cdk', 'cdk.out', '.git', 'node_modules'],
},
);
const runtimeRole = new iam.Role(this, 'MastraAgentRuntimeRole', {
roleName: cdk.PhysicalName.GENERATE_IF_NEEDED,
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
description: 'Execution role for Bedrock AgentCore runtime hosting Mastra agent',
});
const region = cdk.Stack.of(this).region;
const accountId = cdk.Stack.of(this).account;
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'ECRImageAccess',
effect: iam.Effect.ALLOW,
actions: ['ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'],
resources: [
`arn:aws:ecr:${region}:${accountId}:repository/${dockerImageAsset.repository.repositoryName}`,
],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'ECRAuthToken',
effect: iam.Effect.ALLOW,
actions: ['ecr:GetAuthorizationToken'],
resources: ['*'],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'CloudWatchLogsAccess',
effect: iam.Effect.ALLOW,
actions: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:DescribeLogGroups',
'logs:DescribeLogStreams',
'logs:PutLogEvents',
],
resources: [
`arn:aws:logs:${region}:${accountId}:log-group:/aws/bedrock-agentcore/runtimes/*`,
`arn:aws:logs:${region}:${accountId}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*`,
`arn:aws:logs:${region}:${accountId}:log-group:/aws/bedrock-agentcore/runtimes/*:*`,
`arn:aws:logs:${region}:${accountId}:log-group:*`,
],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'XRayTelemetry',
effect: iam.Effect.ALLOW,
actions: [
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
'xray:GetSamplingRules',
'xray:GetSamplingTargets',
],
resources: ['*'],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'AgentWorkloadAccessToken',
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:GetWorkloadAccessToken',
'bedrock-agentcore:GetWorkloadAccessTokenForJWT',
'bedrock-agentcore:GetWorkloadAccessTokenForUserId',
],
resources: [
`arn:aws:bedrock-agentcore:${region}:${accountId}:workload-identity-directory/default`,
`arn:aws:bedrock-agentcore:${region}:${accountId}:workload-identity-directory/default/workload-identity/*`,
],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'BedrockModelInvocation',
effect: iam.Effect.ALLOW,
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
resources: [
'arn:aws:bedrock:*::foundation-model/*',
`arn:aws:bedrock:${region}:${accountId}:*`,
],
}),
);
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'CloudWatchMetrics',
effect: iam.Effect.ALLOW,
actions: ['cloudwatch:PutMetricData'],
resources: ['*'],
conditions: {
StringEquals: {
'cloudwatch:namespace': 'bedrock-agentcore',
},
},
}),
);
// CloudWatch OTLP endpoint permissions
runtimeRole.addToPolicy(
new iam.PolicyStatement({
sid: 'CloudWatchOTLPEndpoints',
effect: iam.Effect.ALLOW,
actions: [
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
'logs:PutLogEvents',
'logs:CreateLogStream',
'logs:CreateLogGroup',
],
resources: ['*'],
}),
);
const runtime = new agentcore.CfnRuntime(this, 'MastraAgentRuntime', {
agentRuntimeName,
agentRuntimeArtifact: {
containerConfiguration: {
containerUri: dockerImageAsset.imageUri,
},
},
networkConfiguration: {
networkMode: 'PUBLIC',
},
roleArn: runtimeRole.roleArn,
protocolConfiguration: 'HTTP',
});
runtime.node.addDependency(runtimeRole);
}
}
デプロイ
コードが書けたので早速デプロイしてみます。
cdk deploy
デプロイが完了すると、Runtime の ARN が出力されます。
✅ SampleAgentcoreL2Stack
✨ Deployment time: 37.77s
Outputs:
SampleAgentcoreL2Stack.RuntimeArn = arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/simpleStrandsAgent-XXXXX
SampleAgentcoreL2Stack.RuntimeId = simpleStrandsAgent-XXXXX
Stack ARN:
arn:aws:cloudformation:us-west-2:123456789012:stack/SampleAgentcoreL2Stack/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
✨ Total time: 42.09s
検証
さて、デプロイが成功したので、早速エージェントを呼び出してみましょう!
AWS コンソールから、テスト > エージェントサンドボックス タブでペイロードを送信してみます。
{
"prompt": "こんにちは"
}
すると...

出力を生成中...data: {"error": "An error occurred (AccessDeniedException) when calling the ConverseStream operation: User: arn:aws:sts::123456789012:assumed-role/SampleAgentcoreL2Stack-StrandsAgentRuntimeExecution-XXXXX/BedrockAgentCore-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx is not authorized to perform: bedrock:InvokeModelWithResponseStream on resource: arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-3-5-haiku-20241022-v1:0 because no identity-based policy allows the bedrock:InvokeModelWithResponseStream action", "error_type": "AccessDeniedException", "message": "An error occurred during streaming"}
おっと、エラーが発生しました。
エラー内容を確認すると、Bedrock のモデル呼び出し権限が不足しているようです。
確かに、自動作成された IAM ロールの権限を確認すると、ECR や CloudWatch Logs の権限はあるものの、Bedrock の権限がありませんでした。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/bedrock-agentcore/runtimes/*",
"Effect": "Allow",
"Sid": "LogGroupAccess"
},
{
"Action": "logs:DescribeLogGroups",
"Resource": "arn:aws:logs:us-west-2:123456789012:log-group:*",
"Effect": "Allow",
"Sid": "DescribeLogGroups"
},
{
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-west-2:123456789012:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*",
"Effect": "Allow",
"Sid": "LogStreamAccess"
},
{
"Action": [
"xray:GetSamplingRules",
"xray:GetSamplingTargets",
"xray:PutTelemetryRecords",
"xray:PutTraceSegments"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "XRayAccess"
},
{
"Condition": {
"StringEquals": {
"cloudwatch:namespace": "bedrock-agentcore"
}
},
"Action": "cloudwatch:PutMetricData",
"Resource": "*",
"Effect": "Allow",
"Sid": "CloudWatchMetrics"
},
{
"Action": [
"bedrock-agentcore:GetWorkloadAccessToken",
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
],
"Resource": [
"arn:aws:bedrock-agentcore:us-west-2:123456789012:workload-identity-directory/default",
"arn:aws:bedrock-agentcore:us-west-2:123456789012:workload-identity-directory/default/workload-identity/*"
],
"Effect": "Allow",
"Sid": "GetAgentAccessToken"
},
{
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:us-west-2:123456789012:repository/cdk-XXXXX-container-assets-123456789012-us-west-2",
"Effect": "Allow"
},
{
"Action": "ecr:GetAuthorizationToken",
"Resource": "*",
"Effect": "Allow"
}
]
}
権限を追加する
それでは、Bedrock の権限を追加していきましょう。
addToRolePolicy() というメソッドで簡単に権限を追加できます。
lib/sample-agentcore-l2-stack.ts を修正して、Bedrock のモデル全てを呼び出せるようにします。
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import { Construct } from "constructs";
import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha";
import * as iam from "aws-cdk-lib/aws-iam";
export class SampleAgentcoreL2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromAsset(
path.join(__dirname, "../agent"),
);
const runtime = new agentcore.Runtime(this, "StrandsAgentRuntime", {
runtimeName: "simpleStrandsAgent",
agentRuntimeArtifact: agentRuntimeArtifact,
description: "Simple Strands Agent with weather tool",
});
// Bedrock の呼び出し権限を追加!
runtime.addToRolePolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
],
resources: [
`arn:aws:bedrock:${this.region}::foundation-model/*`,
],
}),
);
new cdk.CfnOutput(this, "RuntimeArn", {
value: runtime.agentRuntimeArn,
description: "ARN of the AgentCore Runtime",
exportName: "AgentRuntimeArn",
});
new cdk.CfnOutput(this, "RuntimeId", {
value: runtime.agentRuntimeId,
description: "ID of the AgentCore Runtime",
exportName: "AgentRuntimeId",
});
}
}
追加が完了したので、再デプロイします。
npm run build
cdk deploy
別の方法: grantInvoke()メソッドを使う
実は、Bedrock の権限を追加する方法は他にもあります。
@aws-cdk/aws-bedrock-alpha パッケージを使うと、grantInvoke() メソッドで権限を付与できます。
ただ、執筆時点では Claude 3.7 Sonnet V1 まで対応なので、最新のモデルを使用する際は権限付与するのは難しいですね。最初に初回した方法のように権限付与するのが現実的かなと思います。
alpha版パッケージのインストール
まず、@aws-cdk/aws-bedrock-alpha パッケージをインストールする必要があります。
npm install @aws-cdk/aws-bedrock-alpha
方法1: BedrockFoundationModelを使う
特定のモデルに対して権限を付与する場合は、BedrockFoundationModel を使います。
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import { Construct } from "constructs";
import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha";
import * as bedrock from "@aws-cdk/aws-bedrock-alpha";
export class SampleAgentcoreL2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromAsset(
path.join(__dirname, "../agent"),
);
const runtime = new agentcore.Runtime(this, "StrandsAgentRuntime", {
runtimeName: "simpleStrandsAgent",
agentRuntimeArtifact: agentRuntimeArtifact,
description: "Simple Strands Agent with weather tool",
});
// BedrockFoundationModelを使った権限付与
const model = bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_HAIKU_V1_0;
model.grantInvoke(runtime);
// 出力は省略
}
}
方法2: CrossRegionInferenceProfileを使う
クロスリージョンの推論プロファイルを使う場合は、CrossRegionInferenceProfile を使います。
これは、複数リージョンでのモデル呼び出しを最適化する機能です。
import * as cdk from "aws-cdk-lib";
import * as path from "path";
import { Construct } from "constructs";
import * as agentcore from "@aws-cdk/aws-bedrock-agentcore-alpha";
import * as bedrock from "@aws-cdk/aws-bedrock-alpha";
export class SampleAgentcoreL2Stack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromAsset(
path.join(__dirname, "../agent"),
);
const runtime = new agentcore.Runtime(this, "StrandsAgentRuntime", {
runtimeName: "simpleStrandsAgent",
agentRuntimeArtifact: agentRuntimeArtifact,
description: "Simple Strands Agent with weather tool",
});
// CrossRegionInferenceProfileを使った権限付与
const inferenceProfile = bedrock.CrossRegionInferenceProfile.fromConfig({
geoRegion: bedrock.CrossRegionInferenceProfileRegion.US,
model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_HAIKU_V1_0
});
inferenceProfile.grantInvoke(runtime);
// 出力は省略
}
}
どの方法も総じて簡単に権限が追加できて良いですね。
動作確認
デプロイが完了したら、再度エージェントサンドボックスタブからエージェントを呼び出してみます。
{
"prompt": "東京の天気を教えて"
}

しっかりとレスポンスが返ってきました!
おわりに
今回は、AWS CDK v2.221.0で追加された AgentCore L2 Construct を使って、Strands Agents をデプロイしてみました!
実際に試してみて感じたポイントをまとめると、
L2 Construct のおかげで抽象化されているため IAM ロールやデフォルト設定をそこまで気にせずにシンプルにデプロイできるのがメリットかなと思いました。一方で LLM に Bedrock を使用する場合は、Bedrock の権限を追加する必要がありますね。
Starter Toolkit も便利ですが、既存の CDK プロジェクトに組み込みたい場合や、他の AWS リソースと一緒に管理したい場合は、この L2 Construct を使うのも良いですね。
今後も AgentCore の IaC 運用についてより試したり、考えていきたいです!
最後までご覧いただきありがとうございました!
補足
今回はサクッと紹介しましたが、公式ドキュメントに色々なパターンの L2 Construct の実装例が紹介されているので、ぜひご参照ください。
例えば、今回は ECR 自動作成しましたが、ECR を別途作成して、そのレポジトリを使用するやり方もあります。
const repository = new ecr.Repository(this, "TestRepository", {
repositoryName: "test-agent-runtime",
});
// The runtime by default create ECR permission only for the repository available in the account the stack is being deployed
const agentRuntimeArtifact = agentcore.AgentRuntimeArtifact.fromEcrRepository(repository, "v1.0.0");
// Create runtime using the built image
const runtime = new agentcore.Runtime(this, "MyAgentRuntime", {
runtimeName: "myAgent",
agentRuntimeArtifact: agentRuntimeArtifact
});








