はじめに
Amazon Bedrock AgentCore考察記事第2弾。
前回は、Bedrock AgentCoreのランタイムを作成する部分について考察した。
今回は、エンドポイントについて調べていく。
Amazon Bedrock AgentCoreにはエンドポイントとバージョンの概念がある。
これをうまくAWS CodePipelineと組み合わせることで、エージェントのランタイムバージョンを更新する際に、テストエンドポイントでのテスト後にプロダクションエンドポイントにデプロイするというパイプラインが作れる。
せっかくなので、今回もTerraformによる自動化を前提とする。
ただし、前回同様、AWS Providerが未対応のリソースであるため、externalデータソースで強引に構築する。
本記事を書いている2025/8/9時点で、Amazon Bedrock AgentCoreはパブリックプレビューのステータスだ。
GAされる際には仕様が変わる可能性があることをご留意いただきたい。
今回、検証範囲をエージェントのデプロイ箇所にフォーカスするために、AWS CodePipelineのソースステージをECRにしている。
このために、AWS CodePipelineネイティブでは実現できない箇所について少し強引なつくりをした。
実際には、GitHub等のリポジトリに対するコードのPUSH時にAWS CodePipelineを呼び出し、コンテナビルドからECRへのPUSH、その後のデプロイまでを一息で動かすパイプラインを作る方が、直感的に分かりやすい作りになる。
構成図
今回、やりたいことがやや複雑であるため、最初に構成図で全体像をとらえておく。
- イメージタグは旧バージョンをv1, 新バージョンをv2として、初期構築時はAmazon Bedrock AgentCoreのエンドポイントはどちらもv1を向いたランタイムバージョンを使用している
- Amazon ECRのリポジトリへのPUSH契機でAWS CodePipelineのパイプラインを起動する
- AWS CodePipelineからAWS CodeBuildを起動し、Amazon Bedrock AgentCoreの新規ランタイムバージョンとv2のコンテナイメージを結びつける
- 上記AWS CodeBuildと同じビルド内で、Amazon Bedrock AgentCoreのtestエンドポイントがv2のイメージを向くようにランタイムバージョンとエンドポイントの紐づけを更新する
- 管理者による承認後、AWS CodePipelineからAWS CodeBuildを起動し、Amazon Bedrock AgentCoreのprodエンドポイントがv2のイメージを向くようにランタイムバージョンとエンドポイントの紐づけを更新する
Amazon Bedrock AgentCoreエンドポイントの作成
Amazon Bedrock AgentCoreエンドポイントは以下のように作成する。
なお、Amazon Bedrock AgentCoreランタイムは前回の記事で紹介した方法で作成して、agent_runtime_idおよびagent_runtime_versionを返すようにしておく。
data "external" "bedrock_agentcore_runtime_endpoint_test" {
depends_on = [
aws_cloudwatch_log_group.bedrock_agentcore_endpoint_test,
]
program = ["python3", "./agentcore_runtime_endpoint.py"]
query = {
aws_region = data.aws_region.current.region
agent_runtime_id = data.external.bedrock_agentcore_runtime.result.agent_runtime_id
agent_runtime_endpoit_name = "test"
agent_runtime_version = data.external.bedrock_agentcore_runtime.result.agent_runtime_version
}
}
data "external" "bedrock_agentcore_runtime_endpoint_prod" {
depends_on = [
aws_cloudwatch_log_group.bedrock_agentcore_endpoint_test,
]
program = ["python3", "./agentcore_runtime_endpoint.py"]
query = {
aws_region = data.aws_region.current.region
agent_runtime_id = data.external.bedrock_agentcore_runtime.result.agent_runtime_id
agent_runtime_endpoit_name = "prod"
agent_runtime_version = data.external.bedrock_agentcore_runtime.result.agent_runtime_version
}
}
import boto3
import botocore
import json
import sys
import time
def main():
params = json.load(sys.stdin)
aws_region = params["aws_region"]
agent_runtime_id = params["agent_runtime_id"]
agent_runtime_endpoit_name = params["agent_runtime_endpoit_name"]
agent_runtime_version = params["agent_runtime_version"]
bac = boto3.client("bedrock-agentcore-control", region_name=aws_region)
try:
paginator = bac.get_paginator("list_agent_runtime_endpoints")
for page in paginator.paginate(agentRuntimeId=agent_runtime_id):
for runtime in page.get("runtimeEndpoints", []):
if runtime.get("name") == agent_runtime_endpoit_name:
print(
json.dumps(
{
"agent_runtime_endpoint_arn": runtime["agentRuntimeEndpointArn"],
}
)
)
sys.exit(0)
except botocore.exceptions.ClientError as e:
sys.stderr.write(str(e))
print(json.dumps({"error": "list_agent_runtime_endpoints() failed."}))
sys.exit(1)
except RuntimeError as e:
sys.stderr.write(json.dumps({"error": str(e)}))
sys.exit(1)
try:
create_result = bac.create_agent_runtime_endpoint(
agentRuntimeId=agent_runtime_id,
name=agent_runtime_endpoit_name,
agentRuntimeVersion=agent_runtime_version,
)
except botocore.exceptions.ClientError as e:
sys.stderr.write(
json.dumps({"error": f"create_agent_runtime_endpoint() failed {str(e)}"})
)
sys.exit(1)
except RuntimeError as e:
sys.stderr.write(json.dumps({"error": str(e)}))
sys.exit(1)
try:
while True:
get_result = bac.get_agent_runtime_endpoint(
agentRuntimeId=agent_runtime_id,
endpointName = agent_runtime_endpoit_name,
)
if get_result["status"] == "READY":
break
elif get_result["status"] in ("CREATE_FAILED", "UPDATE_FAILED"):
raise RuntimeError(f"invalid status {get_result["status"]}.")
time.sleep(1)
except botocore.exceptions.ClientError as e:
sys.stderr.write(json.dumps({"error": f"get_agent_runtime_endpoint() failed {str(e)}"}))
sys.exit(1)
except RuntimeError as e:
sys.stderr.write(json.dumps({"error": str(e)}))
sys.exit(1)
print(
json.dumps(
{
"agent_runtime_endpoint_arn": create_result["agentRuntimeEndpointArn"],
}
)
)
if __name__ == "__main__":
main()
IAMロールの準備
以下のように、Amazon EventBridge, AWS CodeBuild, AWS CodePipelineのIAMロールを作っておく。
AWS CodeBuild用IAMロール
IAMロールは全般的に難しくないが、ここだけ少し曲者。
bedrock-agentcore:UpdateAgentRuntimeEndpoint
で指定するリソースのエンドポイントが、 ListAgentRuntimeEndpointsで取れるARNと違うように見える。たぶんバグだが、確証はない。
あと、中でIAMロールを付与するため、iam:PassRoleの設定も必要なのが注意点だ。
resource "aws_iam_role" "codebuild" {
name = local.iam_codebuild_role_name
assume_role_policy = data.aws_iam_policy_document.codebuild_assume.json
}
data "aws_iam_policy_document" "codebuild_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"codebuild.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy" "codebuild" {
name = local.iam_codebuild_policy_name
role = aws_iam_role.codebuild.id
policy = data.aws_iam_policy_document.codebuild_custom.json
}
data "aws_iam_policy_document" "codebuild_custom" {
statement {
effect = "Allow"
actions = [
"codeBuild:StartBuild",
]
resources = [
aws_codebuild_project.bac_test.arn,
aws_codebuild_project.bac_prod.arn,
]
}
statement {
effect = "Allow"
actions = [
"s3:GetObject",
]
resources = [
"${aws_s3_bucket.example.arn}/*",
]
}
statement {
effect = "Allow"
actions = [
"logs:CreateLogStream",
"logs:PutLogEvents",
]
resources = [
aws_cloudwatch_log_group.codebuild_bac_test.arn,
"${aws_cloudwatch_log_group.codebuild_bac_test.arn}:log-stream:*",
aws_cloudwatch_log_group.codebuild_bac_prod.arn,
"${aws_cloudwatch_log_group.codebuild_bac_prod.arn}:log-stream:*",
]
}
statement {
effect = "Allow"
actions = [
"bedrock-agentcore:UpdateAgentRuntime",
]
resources = [
data.external.bedrock_agentcore_runtime.result.agent_runtime_arn,
"arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.self.id}:/runtimes/*"
]
}
statement {
effect = "Allow"
actions = [
"bedrock-agentcore:UpdateAgentRuntimeEndpoint",
]
resources = [
data.external.bedrock_agentcore_runtime.result.agent_runtime_arn,
data.external.bedrock_agentcore_runtime_endpoint_test.result.agent_runtime_endpoint_arn,
data.external.bedrock_agentcore_runtime_endpoint_prod.result.agent_runtime_endpoint_arn,
"arn:aws:bedrock-agentcore:${data.aws_region.current.region}:${data.aws_caller_identity.self.id}:/runtimes/*"
]
}
statement {
effect = "Allow"
actions = [
"iam:PassRole",
]
resources = [
aws_iam_role.example.arn,
]
}
}
AWS CodePipeline用IAMロール
resource "aws_iam_role" "codepipeline" {
name = local.iam_codepipeline_role_name
assume_role_policy = data.aws_iam_policy_document.codepipeline_assume.json
}
data "aws_iam_policy_document" "codepipeline_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"codepipeline.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy" "codepipeline" {
name = local.iam_codepipeline_policy_name
role = aws_iam_role.codepipeline.id
policy = data.aws_iam_policy_document.codepipeline_custom.json
}
data "aws_iam_policy_document" "codepipeline_custom" {
statement {
effect = "Allow"
actions = [
"ecr:DescribeImages",
]
resources = [
aws_ecr_repository.example.arn,
]
}
statement {
effect = "Allow"
actions = [
"codebuild:StartBuild",
"codebuild:StopBuild",
"codebuild:BatchGet*",
"codebuild:Get*",
"codebuild:List*",
"s3:*",
]
resources = [
"*",
]
}
}
Amazon EventBridge用IAMロール
resource "aws_iam_role" "eventbridge" {
name = local.iam_eventbridge_role_name
assume_role_policy = data.aws_iam_policy_document.eventbridge_assume.json
}
data "aws_iam_policy_document" "eventbridge_assume" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals {
type = "Service"
identifiers = [
"events.amazonaws.com",
]
}
}
}
resource "aws_iam_role_policy" "eventbridge" {
name = local.iam_eventbridge_policy_name
role = aws_iam_role.eventbridge.id
policy = data.aws_iam_policy_document.eventbridge_custom.json
}
data "aws_iam_policy_document" "eventbridge_custom" {
statement {
effect = "Allow"
actions = [
"codepipeline:StartPipelineExecution",
]
resources = [
aws_codepipeline.example.arn,
]
}
}
AWS CodeBUild
AWS CodeBuildの設定も特に難しいことはない。
BuildSpecは、今回の構成では後で上書きするので不要だが、実際に商用利用する際はここも正しくリポジトリ構成に合わせて設計しよう。
resource "aws_codebuild_project" "bac_test" {
name = local.codebuild_project_bac_test_name
service_role = aws_iam_role.codebuild.arn
source {
type = "CODEPIPELINE"
buildspec = "buildspec.yml"
}
artifacts {
type = "CODEPIPELINE"
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux-x86_64-standard:5.0"
environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.self.id
}
environment_variable {
name = "AWS_BEDROCK_AGENTCORE_RUNTIME_ID"
value = data.external.bedrock_agentcore_runtime.result.agent_runtime_id
}
environment_variable {
name = "AWS_BEDROCK_AGENTCORE_ROLE_ARN"
value = aws_iam_role.example.arn
}
}
}
resource "aws_codebuild_project" "bac_prod" {
name = local.codebuild_project_bac_prod_name
service_role = aws_iam_role.codebuild.arn
source {
type = "CODEPIPELINE"
buildspec = "buildspec.yml"
}
artifacts {
type = "CODEPIPELINE"
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux-x86_64-standard:5.0"
environment_variable {
name = "AWS_BEDROCK_AGENTCORE_RUNTIME_ID"
value = data.external.bedrock_agentcore_runtime.result.agent_runtime_id
}
}
}
AWS CodePipeline
AWS CodePipelineでは、ソースステージでECRを定義しているが、ECRでは単一のイメージタグでしかトリガが設定できない。今回の作りでは、v1, v2, v3……とバージョンを上げていくため、これでは都度パイプラインを作らなくてはいけない。わざわざAmazon EventBridgeを挟んでいるのはこのためで、これにより、一つのリポジトリの複数のイメージタグのPUSH時に同じパイプラインを呼べるようになるというわけだ。
このため、Amazon Bedrock AgentCoreのランタイムのバージョンアップ時に必要になるAmazon ECRのリポジトリ名とイメージタグ名は、パイプライン引数として受け取り、ソースステージの情報を上書きするようにしている。
resource "aws_codepipeline" "example" {
name = local.codepipeline_pipeline_name
role_arn = aws_iam_role.codepipeline.arn
pipeline_type = "V2"
artifact_store {
type = "S3"
location = aws_s3_bucket.example.bucket
}
variable {
name = "RepositoryName"
default_value = aws_ecr_repository.example.name
}
variable {
name = "ImageTag"
default_value = "Dummy"
}
stage {
name = "Source"
action {
run_order = 1
name = "Source"
category = "Source"
owner = "AWS"
provider = "ECR"
version = "1"
output_artifacts = ["SourceArtifact"]
configuration = {
RepositoryName = aws_ecr_repository.example.name
ImageTag = "dummy" # Ignore latest
}
}
}
stage {
name = "Deploy-Test-Endpoint"
action {
run_order = 2
name = "Deploy-Test-Endpoint"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
input_artifacts = ["SourceArtifact"]
namespace = "TestDeployVariables"
configuration = {
ProjectName = aws_codebuild_project.bac_test.name
BuildspecOverride = file("./buildspec_test.yml")
EnvironmentVariables = jsonencode([
{
type = "PLAINTEXT"
name = "AWS_CODEPIPELINE_VAR_REPOSITORYNAME"
value = "#{variables.RepositoryName}"
},
{
type = "PLAINTEXT"
name = "AWS_CODEPIPELINE_VAR_IMAGETAG"
value = "#{variables.ImageTag}"
},
])
}
}
}
stage {
name = "Approve-Production"
action {
run_order = 3
name = "Approve-Production"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = "1"
}
}
stage {
name = "Deploy-Production-Endpoint"
action {
run_order = 4
name = "Deploy-Production-Endpoint"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
input_artifacts = ["SourceArtifact"]
configuration = {
ProjectName = aws_codebuild_project.bac_test.name
BuildspecOverride = file("./buildspec_prod.yml")
EnvironmentVariables = jsonencode([
{
type = "PLAINTEXT"
name = "AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION"
value = "#{TestDeployVariables.AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION}"
},
])
}
}
}
}
Amazon EventBridge
AWS CodePipelineを起動するためのパイプラインを定義する。
今回はイメージのPUSH時に、vで始まるタグの場合のみ起動するようにしている。ターゲットでは、Input Transformerを使ってイベント通知された情報を編集し、AWS CodePipelineに渡す。
これにより、さきほど作ったAWS CodePipelineのパイプライン変数を上書きできる。
resource "aws_cloudwatch_event_rule" "example" {
name = local.eventbridge_event_name
event_pattern = jsonencode(
{
source : [
"aws.ecr",
],
"detail-type" : [
"ECR Image Action"
],
detail : {
"action-type" : [
"PUSH",
],
result : [
"SUCCESS",
],
"repository-name" : [
aws_ecr_repository.example.name,
],
"image-tag" : [
{
"prefix" : "v"
}
]
},
}
)
}
resource "aws_cloudwatch_event_target" "example" {
target_id = local.eventbridge_target_name
role_arn = aws_iam_role.eventbridge.arn
rule = aws_cloudwatch_event_rule.example.name
arn = aws_codepipeline.example.arn
input_transformer {
input_paths = {
image_digest = "$.detail.image-digest"
repository_name = "$.detail.repository-name"
image_tag = "$.detail.image-tag"
}
input_template = templatefile("./input_template.json", {
codepipeline_pipeline_name = aws_codepipeline.example.name,
})
}
}
{
"name": "${codepipeline_pipeline_name}",
"sourceRevisions": [
{
"actionName": "Source",
"revisionType": "IMAGE_DIGEST",
"revisionValue": <image_digest>
}
],
"variables": [
{
"name": "RepositoryName",
"value": <repository_name>
},
{
"name": "ImageTag",
"value": <image_tag>
}
]
}
BuildSpec
testエンドポイント向け
さて、最後にBuildSpecを作ろう。
installフェーズでAWS CLIのアップデートをしているのは、標準のAWS CodeBuildのイメージではまだ最新版が入っていなく、Amazon Bedrock AgentCoreのCLI実行ができないためだ。将来的に取り込まれた版のCLIがイメージに含まれるようになれば、このブロックは不要になる。
AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION
がこの後のprodのデプロイにも使うため、exported-variabled
にしているのも要確認ポイントだ。これは、上述した、AWS CodePipelineの部分で使われている。
EnvironmentVariables = jsonencode([
{
type = "PLAINTEXT"
name = "AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION"
value = "#{TestDeployVariables.AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION}"
},
])
version: 0.2
env:
exported-variables:
- AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION
phases:
install:
commands:
- curl -sS "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
build:
commands:
- |
RESULT=$(aws bedrock-agentcore-control update-agent-runtime \
--region=${AWS_REGION} \
--agent-runtime-id=${AWS_BEDROCK_AGENTCORE_RUNTIME_ID} \
--agent-runtime-artifact='{"containerConfiguration": {"containerUri": "'${REPOSITORY_URI}'" }}' \
--role-arn=${AWS_BEDROCK_AGENTCORE_ROLE_ARN} \
--network-configuration='{"networkMode": "PUBLIC"}') \
- AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION=$(echo ${RESULT} | jq -r .agentRuntimeVersion)
- echo ${AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION}
- |
aws bedrock-agentcore-control update-agent-runtime-endpoint \
--agent-runtime-id=${AWS_BEDROCK_AGENTCORE_RUNTIME_ID} \
--endpoint-name=test \
--agent-runtime-version=${AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION}
prodエンドポイント向け
こちらはもっと簡単だ。ひとつ前のパイプラインステージで作ったAWS_BEDROCK_AGENTCORE_RUNTIME_VERSIONを持ち回って設定すれば良い。
version: 0.2
phases:
install:
commands:
- ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
- aws --version
build:
commands:
- |
aws bedrock-agentcore-control update-agent-runtime-endpoint \
--agent-runtime-id=${AWS_BEDROCK_AGENTCORE_RUNTIME_ID} \
--endpoint-name=prod \
--agent-runtime-version=${AWS_BEDROCK_AGENTCORE_RUNTIME_VERSION}
いざ、動かす!
さて、上記のHCLをapplyしてみよう。
ちなみに、PUSHするタグのイメージはこの変数で定義しておく。必須パラメータなので、最初に書いた構成図の通り、v1で初期構築し、v2のデプロイをしてみよう。
variable "ecr_image_tag" {
type = string
}
また、v1とv2で以下のエージェントのプロンプトを変えている。
使用しているフレームワークはStrands Agentsだ。
agent = Agent(
model=bedrock_model,
system_prompt="あなたはfizzbuzzシミュレータです。1から入力された数までをカウントし、3の倍数でfizzと言ってください。",
tools=tools,
)
agent = Agent(
model=bedrock_model,
system_prompt="あなたはfizzbuzzシミュレータです。1から入力された数までをカウントし、3の倍数でfizz、5の倍数でbuzz、3と5の公倍数でfizzbuzzと言ってください。",
tools=tools,
)
v2のApplyが終わると、以下のようなパイプラインが作られるはずだ。
※画像はデプロイを走らせ切ってしまったが、実際は承認ステージで止まるはず。
承認ステージで止まっている状態で、以下のようにInvokeをしてみよう。
$ python3 main.py "15" --qualifier=prod
Agent Response: FizzBuzzシミュレータとして、1から15までカウントします!
1
2
Fizz (3の倍数)
4
5
Fizz (6の倍数)
7
8
Fizz (9の倍数)
10
11
Fizz (12の倍数)
13
14
Fizz (15の倍数)
3の倍数(3, 6, 9, 12, 15)でFizzと言いました!
上記状態で、今度はtestエンドポイントをInvokeする。
$ python3 main.py "15" --qualifier=test
30
Agent Response: FizzBuzz for 1 to 30:
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz
fizz
22
23
fizz
buzz
26
fizz
28
29
fizzbuzz
無事、それぞれのバージョンで想定した動作になっている。
ここで、承認を行い、prodエンドポイントもv2をロードする状態にする。
すると、
$ python3 main.py "15" --qualifier=prod
Agent Response: FizzBuzzシミュレータを実行します!1から15までカウントしていきますね。
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
完了!15までのFizzBuzzシミュレーションが終わりました。
- 3の倍数(3, 6, 9, 12, 15)で「fizz」
- 5の倍数(5, 10, 15)で「buzz」
- 3と5の公倍数(15)で「fizzbuzz」
と表示されました。
無事、Prodの応答も変わった。
これで、Amazon Bedrock AgentCoreを安全にデプロイできるようになった!