Amazon Bedrock AgentCore が CloudFormation に対応したので試してみた
日本時間 2025/9/24 の出来事
現時点だと以下4つのリソースに対応しています。
Resource types
- AWS::BedrockAgentCore::BrowserCustom
- AWS::BedrockAgentCore::CodeInterpreterCustom
- AWS::BedrockAgentCore::Runtime
- AWS::BedrockAgentCore::RuntimeEndpoint
早速やってみよう!
作って使ってみる
Strands Agents を使います。また、Amazon Bedrock AgentCore はポート 8080 で /ping
と /invocations
を受け付けられる必要があります。詳細はこちらをご確認ください。
CloudFormation のテンプレート
こんな感じで用意しました。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Complete CodeBuild and AgentCore Runtime'
Resources:
# ECR Repository
ECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: bedrock-agent-runtime
# CodeBuild Service Role
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
- ecr:*
Resource: "*"
# CodeBuild Project
BuildProject:
Type: AWS::CodeBuild::Project
Properties:
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: NO_ARTIFACTS
Environment:
Type: ARM_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
PrivilegedMode: true
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: AWS_ACCOUNT_ID
Value: !Ref AWS::AccountId
- Name: IMAGE_REPO_NAME
Value: !Ref ECRRepository
Source:
Type: NO_SOURCE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- |
cat > Dockerfile << 'EOF'
FROM public.ecr.aws/docker/library/python:3.13-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
EOF
- |
cat > requirements.txt << 'EOF'
fastapi
pydantic
uvicorn
aws-opentelemetry-distro
strands-agents
EOF
- |
cat > app.py << 'EOF'
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime
from strands import Agent
app = FastAPI(title="Strands Agent Server", version="1.0.0")
# Initialize Strands agent
strands_agent = Agent()
class InvocationRequest(BaseModel):
input: Dict[str, Any]
class InvocationResponse(BaseModel):
output: Dict[str, Any]
@app.post("/invocations", response_model=InvocationResponse)
async def invoke_agent(request: InvocationRequest):
try:
user_message = request.input.get("prompt", "")
if not user_message:
raise HTTPException(
status_code=400,
detail="No prompt found in input. Please provide a 'prompt' key in the input."
)
result = strands_agent(user_message)
response = {
"message": result.message,
"timestamp": datetime.utcnow().isoformat(),
"model": "strands-agent",
}
return InvocationResponse(output=response)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Agent processing failed: {str(e)}")
@app.get("/ping")
async def ping():
return {"status": "healthy"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
EOF
- docker build -t $IMAGE_REPO_NAME:latest .
- docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
post_build:
commands:
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
# Lambda function to trigger build
BuildTrigger:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.9
Handler: index.handler
Role: !GetAtt LambdaRole.Arn
Timeout: 900
Code:
ZipFile: |
import boto3
import cfnresponse
import time
def handler(event, context):
try:
if event['RequestType'] == 'Create':
codebuild = boto3.client('codebuild')
project_name = event['ResourceProperties']['ProjectName']
response = codebuild.start_build(projectName=project_name)
build_id = response['build']['id']
for attempt in range(20):
build_status = codebuild.batch_get_builds(ids=[build_id])
status = build_status['builds'][0]['buildStatus']
if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
break
time.sleep(30)
if status == 'SUCCEEDED':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
else:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
else:
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
# Lambda execution role
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CodeBuildAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codebuild:*
Resource: "*"
# Trigger build
TriggerBuild:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt BuildTrigger.Arn
ProjectName: !Ref BuildProject
DependsOn: ECRRepository
# Runtime Role
RuntimeRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: bedrock-agentcore.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: RuntimePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
Resource: "*"
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
Resource: "*"
- Effect: Allow
Action:
- bedrock:*
Resource: "*"
# AgentCore Runtime
AgentRuntime:
Type: AWS::BedrockAgentCore::Runtime
Properties:
AgentRuntimeName: bedrock_agent_runtime
RoleArn: !GetAtt RuntimeRole.Arn
NetworkConfiguration:
NetworkMode: PUBLIC
AgentRuntimeArtifact:
ContainerConfiguration:
ContainerUri: !Sub "${ECRRepository.RepositoryUri}:latest"
DependsOn: TriggerBuild
Outputs:
AgentRuntimeArn:
Value: !GetAtt AgentRuntime.AgentRuntimeArn
ECRRepositoryUri:
Value: !GetAtt ECRRepository.RepositoryUri
デプロイ
AWS CLI でデプロイします。
aws cloudformation deploy \
--template-file bedrock-agentcore-runtime.yaml \
--stack-name bedrock-agent-runtime \
--capabilities CAPABILITY_IAM \
--region us-east-1
呼び出す
デプロイが終わったら呼び出しましょう。
呼び出すにあたって、AgentCore Runtime の ARN が必要なので取得します。
AGENT_RUNTIME_ARN=$(aws cloudformation describe-stacks \
--stack-name bedrock-agent-runtime \
--region us-east-1 \
--query 'Stacks[0].Outputs[?OutputKey==`AgentRuntimeArn`].OutputValue' \
--output text)
AWS CLI でもいいのですが、Boto3 で呼び出したほうが楽なので invoke するコードを用意しました。prompt は好きに差し替えられます。
import boto3
import json
import sys
arn = sys.argv[1]
client = boto3.client('bedrock-agentcore', region_name='us-east-1')
payload = json.dumps({
"input": {"prompt": "こんにちは"}
})
response = client.invoke_agent_runtime(
agentRuntimeArn=arn,
payload=payload,
)
response_body = response['response'].read()
response_data = json.loads(response_body)
print("Agent Response:", response_data)
そして推論します。
python3 invoke.py $AGENT_RUNTIME_ARN
するとこんな出力を得られます。
Agent Response: {'output': {'message': {'role': 'assistant', 'content': [{'text': 'こんにちは!お元気ですか?何かお手伝いできることがあれば、お気軽にお声かけください。'}]}, 'timestamp': '2025-09-25T13:24:07.602632', 'model': 'strands-agent'}}
ざっくり解説
ECR
AgentCore Runtime はコンテナベースで動くので、最初に ECR のリポジトリを作る必要があります。
ECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: bedrock-agent-runtime
コンテナイメージのビルドに CodeBuild
ただしこれだけだと空のリポジトリなのでイメージをビルドする必要があります。
AWS だといくつかビルドの方法がありますが、今回は CodeBuild を使いました。
気をつけないといけないのは、AgentCore Runtime は Graviton で動くので、 CodeBuild のプロジェクトの環境は Arm(Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
,ARM_CONTAINER
) である必要があることにご注意ください。
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CodeBuildPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
- ecr:*
Resource: "*"
# CodeBuild Project
BuildProject:
Type: AWS::CodeBuild::Project
Properties:
ServiceRole: !GetAtt CodeBuildRole.Arn
Artifacts:
Type: NO_ARTIFACTS
Environment:
Type: ARM_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
PrivilegedMode: true
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: AWS_ACCOUNT_ID
Value: !Ref AWS::AccountId
- Name: IMAGE_REPO_NAME
Value: !Ref ECRRepository
Source:
Type: NO_SOURCE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
build:
commands:
- |
cat > Dockerfile << 'EOF'
FROM public.ecr.aws/docker/library/python:3.13-slim
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
EOF
- |
cat > requirements.txt << 'EOF'
fastapi
pydantic
uvicorn
aws-opentelemetry-distro
strands-agents
EOF
- |
cat > app.py << 'EOF'
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict, Any
from datetime import datetime
from strands import Agent
app = FastAPI(title="Strands Agent Server", version="1.0.0")
# Initialize Strands agent
strands_agent = Agent()
class InvocationRequest(BaseModel):
input: Dict[str, Any]
class InvocationResponse(BaseModel):
output: Dict[str, Any]
@app.post("/invocations", response_model=InvocationResponse)
async def invoke_agent(request: InvocationRequest):
try:
user_message = request.input.get("prompt", "")
if not user_message:
raise HTTPException(
status_code=400,
detail="No prompt found in input. Please provide a 'prompt' key in the input."
)
result = strands_agent(user_message)
response = {
"message": result.message,
"timestamp": datetime.utcnow().isoformat(),
"model": "strands-agent",
}
return InvocationResponse(output=response)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Agent processing failed: {str(e)}")
@app.get("/ping")
async def ping():
return {"status": "healthy"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
EOF
- docker build -t $IMAGE_REPO_NAME:latest .
- docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
post_build:
commands:
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
コンテナイメージの中に Agent のコードを入れる必要がありますので、標準入力等を使って CodeBuild 内に無理やりファイルを作ったりしています。他、requirements.txt も作り、依存するパッケージもインストールするコードも入れています。
今回はここにある Agent のコードをそのまま使っています。
FastAPI を使ってます。また、/ping
と /invocations
, そしてポート8080 で受け付ける必要があるところにご注意ください。
ただし、このままだと CodeBuild のプロジェクトが出来上がるだけなので、動かす必要があります。Lambda のトリガーを使って動かしています。
BuildTrigger:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.9
Handler: index.handler
Role: !GetAtt LambdaRole.Arn
Timeout: 900
Code:
ZipFile: |
import boto3
import cfnresponse
import time
def handler(event, context):
try:
if event['RequestType'] == 'Create':
codebuild = boto3.client('codebuild')
project_name = event['ResourceProperties']['ProjectName']
response = codebuild.start_build(projectName=project_name)
build_id = response['build']['id']
for attempt in range(20):
build_status = codebuild.batch_get_builds(ids=[build_id])
status = build_status['builds'][0]['buildStatus']
if status in ['SUCCEEDED', 'FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
break
time.sleep(30)
if status == 'SUCCEEDED':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
else:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
else:
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
cfnresponse.send(event, context, cfnresponse.FAILED, {})
# Lambda execution role
LambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: CodeBuildAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- codebuild:*
Resource: "*"
# Trigger build
TriggerBuild:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt BuildTrigger.Arn
ProjectName: !Ref BuildProject
DependsOn: ECRRepository
AgentCore Runtime
最後に AgentCore Runtime を作ります。Runtime は AWS のリソースを触るため、ロールを作ってポリシーをアタッチする必要があります。CWL 及び ECR へのアクセスは必須、そしてほとんどのケースにおいて Bedrock へのアクセスが必要です。Bedrock のポリシーは面倒だったのでアクションとリソースをすべて許してしまいましたが、適切に絞ることが大切です。
RuntimeRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: bedrock-agentcore.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: RuntimePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:*
Resource: "*"
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
Resource: "*"
- Effect: Allow
Action:
- bedrock:*
Resource: "*"
そして最後に Runtime を作ります。
AgentRuntime:
Type: AWS::BedrockAgentCore::Runtime
Properties:
AgentRuntimeName: bedrock_agent_runtime
RoleArn: !GetAtt RuntimeRole.Arn
NetworkConfiguration:
NetworkMode: PUBLIC
AgentRuntimeArtifact:
ContainerConfiguration:
ContainerUri: !Sub "${ECRRepository.RepositoryUri}:latest"
DependsOn: TriggerBuild
すでに作ってある Role や ECR のコンテナイメージの URI を指定するだけです。
さいごに
IaC できるようになりました。近いうち(?)に CDK もきっと来ることでしょう。来てくれるとうれしいな。
Discussion