Agentic AI on EKS 実践 — AIエージェントをデプロイする
目次
はじめに
LLM を API 経由で呼ぶだけの時代は終わりつつある。ツールを使い、他のエージェントと協調し、セッション状態を保持する「AI エージェント」をどうやって本番運用するか — これが 2026 年のインフラエンジニアに突きつけられている問いだ。
AWS が公開したAgentic AI on EKSワークショップは、この問いに対する一つの回答を示している。Strands Agents SDK、MCP(Model Context Protocol)、A2A(Agent-to-Agent)プロトコルを組み合わせ、EKS 上に本番対応のエージェントプラットフォームを構築するハンズオンだ。ソースコードは aws-samples/sample-agentic-frameworks-on-aws リポジトリの eks/ ディレクトリにある。
本記事では、このワークショップを実際に検証した際の知見を共有する。全 3 回のシリーズで、MCP によるツール連携、A2A によるマルチエージェント協調、認証 UI まで段階的に構築していく。
前提条件と事前準備
検証環境の前提条件は以下のとおりだ。
- EKS クラスター(Auto Mode 有効)が稼働中
kubectlとawsCLI が設定済み- Bedrock の Claude Haiku 4.5 モデルアクセスが有効(cross-region inference profile
global.anthropic.claude-haiku-4-5-20251001-v1:0)
ワークショップのリポジトリには eks/infrastructure/terraform/ に Terraform による一括構築が用意されている。EKS クラスター、ECR、S3、Cognito、Pod Identity をまとめて作成できるので、手早く試したい場合はそちらを使うのが最短ルートだ。
cd infrastructure/terraform
terraform init
terraform apply本記事では各リソースの役割を理解するために手動で構築した。結果だけ見たい場合はデプロイと動作確認まで読み飛ばしてほしい。
事前準備(リポジトリクローン、ECR、S3、Pod Identity)
リポジトリのクローンと環境変数の設定。
git clone https://github.com/aws-samples/sample-agentic-frameworks-on-aws.git
cd sample-agentic-frameworks-on-aws/eks
export AWS_REGION=ap-northeast-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
export ECR_HOST=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
export CLUSTER_NAME=eks-sandbox # 自分のクラスター名に変更ECR リポジトリの作成。Part 1 で使う 2 コンポーネント分を作る。
for repo in agents-on-eks/weather-mcp agents-on-eks/weather-agent; do
aws ecr create-repository --repository-name $repo --region $AWS_REGION
doneS3 セッションバケットの作成。Weather Agent がユーザーごとの会話履歴を保持する。
aws s3 mb s3://weather-agent-session-${ACCOUNT_ID} --region $AWS_REGIONPod Identity 用の IAM ロール作成。エージェントが Bedrock と S3 にアクセスするために必要だ。
# Trust policy(Pod Identity 共通)
cat > /tmp/pod-identity-trust.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}
EOF
# Weather Agent 用ロール
cat > /tmp/weather-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BedrockAccess",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "*"
},
{
"Sid": "S3Access",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::weather-agent-session-${ACCOUNT_ID}/*"
},
{
"Sid": "S3List",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::weather-agent-session-${ACCOUNT_ID}"
}
]
}
EOF
aws iam create-role --role-name weather-agent-pod-role \
--assume-role-policy-document file:///tmp/pod-identity-trust.json
aws iam put-role-policy --role-name weather-agent-pod-role \
--policy-name bedrock-s3 --policy-document file:///tmp/weather-policy.json
# Pod Identity Association
WEATHER_ROLE_ARN=$(aws iam get-role --role-name weather-agent-pod-role \
--query 'Role.Arn' --output text)
aws eks create-pod-identity-association \
--cluster-name $CLUSTER_NAME --region $AWS_REGION \
--namespace agents --service-account weather-agent \
--role-arn $WEATHER_ROLE_ARNアーキテクチャの全体像
このワークショップでは、EKS 上に 4 つのコンポーネントをデプロイする。
4 つのコンポーネントの役割は以下のとおりだ。
- Weather MCP Server — 米国気象局(NWS)の API をラップし、
get_forecast(location)とget_alerts(state)の 2 つのツールを MCP プロトコルで公開する。エージェント自体は持たず、純粋なツールサーバーとして動作する。 - Weather Agent — Strands Agents SDK で構築された AI エージェント。Bedrock Claude Haiku 4.5 を LLM として使い、MCP Server のツールを自動発見・呼び出しして天気情報を回答する。
- Travel Agent — 旅行計画のオーケストレーター。自分では天気情報を生成せず、A2A(Agent-to-Agent)プロトコルで Weather Agent に処理を委譲する。
- Agent UI — Gradio ベースの Web チャット画面。Cognito OAuth でユーザー認証し、エージェントの REST API を呼び出す。
設計上の特長は、Weather Agent が 3 つのプロトコルを 1 コンテナで同時にサーブすることだ。UI からは FastAPI(REST, port 3000)で会話し、外部システムからは MCP(port 8080)でツールとして呼び出し、Travel Agent からは A2A(port 9000)でエージェント間連携する。用途に応じたプロトコルを選択でき、同一のエージェントを異なる文脈で再利用できる。
今回の検証範囲
本記事では、ワークショップの中核である Weather Agent + MCP Server の構成を検証した。Travel Agent(A2A マルチエージェント)と Agent UI(Cognito OAuth 認証)は次回以降の検証対象とする。
この構成で、エージェントがツールを自動発見して外部 API を呼び出し、LLM が応答を生成するまでの一連のフローを確認した。
Strands Agents SDK の実装パターン
Weather Agent の実装を通じて、Strands Agents SDK の設計思想が見えてくる。まず「何をビルドするのか」を理解した上で、次のセクションでビルド・デプロイの手順を解説する。
3 つの設定ファイルによる関心の分離
Weather Agent は、コードと設定を明確に分離する構成を取っている。
| ファイル | 役割 | 変更頻度 |
|---|---|---|
agent.py | エージェントの初期化・ツール読み込みロジック | 低(コード変更) |
agent.md | エージェント名・説明・システムプロンプト | 中(振る舞い調整) |
mcp.json | MCP サーバーの接続先定義 | 中(ツール追加・切替) |
agent.md と mcp.json は Helm の ConfigMap としてマウントされるため、コンテナイメージを再ビルドせずにエージェントの振る舞いやツール構成を変更できる。
agent.md — マークダウンでエージェントを定義
エージェントの人格はマークダウンファイルで定義される。## Agent Name、## Agent Description、## System Prompt の 3 セクションを正規表現でパースし、Agent クラスに渡す仕組みだ。
# Weather Assistant Agent Configuration
## Agent Name
Weather Assistant
## Agent Description
Weather Assistant that provides weather forecasts(US City, State) and alerts(US State)
## System Prompt
You are Weather Assistant that helps the user with forecasts or alerts:
- Provide weather forecasts for US cities for the next 3 days if no specific period is mentioned
- When returning forecasts, always include whether the weather is good for outdoor activities for each day
- Provide information about weather alerts for US cities when requestedYAML やJSON ではなくマークダウンを採用したのは、プロンプトが自然言語で長文になりがちだからだろう。可読性が高く、非エンジニアでも編集しやすい。
mcp.json — ツール接続先の宣言
MCP サーバーへの接続方式は mcp.json で定義する。stdio(ローカルプロセス)と HTTP(リモートサーバー)の 2 種類をサポートしている。
{
"mcpServers": {
"weather-mcp-http": {
"url": "http://weather-mcp.mcp-servers:8080/mcp"
}
}
}ローカル開発時は stdio でプロセス直接起動、EKS デプロイ時は HTTP でリモート MCP Server に切り替えるという使い分けが、この 1 ファイルの差し替えだけで実現できる。disabled: true フラグで個別のサーバーを無効化することも可能だ。
エージェント初期化 — MCP ツールの自動発見
agent.py のコア部分はわずか数行だ。mcp.json の各サーバーに接続し、公開されているツールを自動取得してエージェントに渡す。
from strands import Agent, tool
from strands.models import BedrockModel
from strands.tools.mcp import MCPClient
# ビルトインツール(Python 関数をそのままツール化)
@tool(name="get_todays_date", description="Retrieves today's date for accuracy")
def get_todays_date() -> str:
return datetime.today().strftime('%Y-%m-%d')
# LLM の設定
bedrock_model = BedrockModel(
model_id="global.anthropic.claude-haiku-4-5-20251001-v1:0"
)
# MCP サーバーからツールを自動取得
mcp_client = MCPClient(lambda: streamablehttp_client(url))
mcp_client.start()
mcp_tools = mcp_client.list_tools_sync() # → [get_forecast, get_alerts]
# エージェント生成(ビルトインツール + MCP ツール)
agent = Agent(
model=bedrock_model,
system_prompt=system_prompt,
tools=[get_todays_date] + mcp_tools
)MCPClient は MCP プロトコルでサーバーに接続し、list_tools_sync() で利用可能なツールの一覧(名前・説明・パラメータスキーマ)を取得する。エージェントはこの情報を LLM に渡し、ユーザーの質問に応じてどのツールを呼ぶかは LLM が自律的に判断する。
@tool デコレータで定義したビルトインツール(Python 関数)と MCP ツールを同列に扱えるのも Strands SDK の特長だ。
リクエスト処理の流れ
FastAPI サーバーがリクエストを受けてからレスポンスを返すまでの流れは以下のとおりだ。
/promptエンドポイントに{"text": "NYCの天気は?"}が届く- 認証チェック(Cognito JWT 検証、またはテストモードではスキップ)
- ユーザー ID をキーに S3 セッションマネージャーを生成(会話履歴の保持)
create_agent()でエージェントインスタンスを生成agent("NYCの天気は?")を呼び出し — LLM がget_forecast("New York City")の呼び出しを決定- MCP 経由で Weather MCP Server の
get_forecastツールが実行される - NWS API から取得した天気データを LLM が自然言語に整形して返却
今回の検証では DISABLE_AUTH=1 を設定し、認証をスキップしたテストモードで動作確認を行った。
Kaniko によるコンテナビルド
エージェントの実装を理解したところで、これをコンテナ化して EKS にデプロイする。
ワークショップのリポジトリには scripts/containers.sh で docker buildx を使うビルドスクリプトが用意されている。ローカルに Docker がある環境ではこれが最も手軽だ。今回は Docker デーモンのない環境で検証したため、Kaniko を採用した。
Kaniko は Google が開発したコンテナイメージビルドツールで、Docker デーモンに依存せず、Kubernetes Pod 内でコンテナイメージをビルドできるのが最大の特長だ。通常の docker build は Docker デーモン(特権プロセス)が必要だが、Kaniko はユーザー空間で Dockerfile を解釈・実行するため、特権なしの Pod でもイメージビルドが可能になる。CI/CD パイプラインや、Docker デーモンを動かせない環境でのビルド手段として広く使われている。
今回の構成では、ビルドコンテキスト(ソースコード一式)を S3 にアップロードし、EKS 上の Kaniko Job が S3 からコンテキストを取得してビルド、完成したイメージを ECR に直接 push する流れを取った。
Kaniko ビルド環境の構築手順
Kaniko 用の S3 バケット、IAM ロール、Pod Identity Association を作成する。
# S3 ビルドコンテキスト用バケット
aws s3 mb s3://kaniko-build-${ACCOUNT_ID} --region $AWS_REGION
# Kaniko 用 IAM ロール(ECR push + S3 read)
cat > /tmp/kaniko-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ecr:GetAuthorizationToken"],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability", "ecr:CompleteLayerUpload",
"ecr:InitiateLayerUpload", "ecr:PutImage", "ecr:UploadLayerPart",
"ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer"
],
"Resource": "arn:aws:ecr:${AWS_REGION}:${ACCOUNT_ID}:repository/agents-on-eks/*"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::kaniko-build-${ACCOUNT_ID}",
"arn:aws:s3:::kaniko-build-${ACCOUNT_ID}/*"
]
}
]
}
EOF
aws iam create-role --role-name kaniko-pod-role \
--assume-role-policy-document file:///tmp/pod-identity-trust.json
aws iam put-role-policy --role-name kaniko-pod-role \
--policy-name ecr-s3 --policy-document file:///tmp/kaniko-policy.json
# Kubernetes リソース
kubectl create ns build
kubectl create serviceaccount kaniko -n build
# Pod Identity Association
KANIKO_ROLE_ARN=$(aws iam get-role --role-name kaniko-pod-role \
--query 'Role.Arn' --output text)
aws eks create-pod-identity-association \
--cluster-name $CLUSTER_NAME --region $AWS_REGION \
--namespace build --service-account kaniko \
--role-arn $KANIKO_ROLE_ARNビルドコンテキストを S3 にアップロードし、Kaniko Job を実行する。
# ビルドコンテキストをS3にアップロード
cd agents/weather/mcp-servers/weather-mcp-server
tar czf /tmp/weather-mcp-context.tar.gz .
aws s3 cp /tmp/weather-mcp-context.tar.gz s3://kaniko-build-${ACCOUNT_ID}/build/
cd ../../ # agents/weather/
tar czf /tmp/weather-agent-context.tar.gz .
aws s3 cp /tmp/weather-agent-context.tar.gz s3://kaniko-build-${ACCOUNT_ID}/build/# Kaniko Job で EKS 上ビルド → ECR push
apiVersion: batch/v1
kind: Job
metadata:
name: kaniko-weather-mcp
namespace: build
spec:
backoffLimit: 1
template:
spec:
serviceAccountName: kaniko
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=s3://kaniko-build-${ACCOUNT_ID}/build/weather-mcp-context.tar.gz"
- "--destination=${ECR_HOST}/agents-on-eks/weather-mcp:latest"
restartPolicy: Never
---
apiVersion: batch/v1
kind: Job
metadata:
name: kaniko-weather-agent
namespace: build
spec:
backoffLimit: 1
template:
spec:
serviceAccountName: kaniko
containers:
- name: kaniko
image: gcr.io/kaniko-project/executor:latest
args:
- "--context=s3://kaniko-build-${ACCOUNT_ID}/build/weather-agent-context.tar.gz"
- "--destination=${ECR_HOST}/agents-on-eks/weather-agent:latest"
restartPolicy: NeverYAML を適用してビルドを開始し、完了を待つ。
kubectl apply -f kaniko-weather.yaml
kubectl wait --for=condition=complete \
job/kaniko-weather-mcp job/kaniko-weather-agent \
-n build --timeout=600sポイントは Pod Identity で ECR push 権限を付与すること。ecr:PutImage と ecr:CompleteLayerUpload を含むポリシーを build namespace の kaniko サービスアカウントに紐づけた。ビルド時間は MCP Server が約 2 分、Weather Agent が約 3 分だった。
デプロイと動作確認
Kaniko でビルドしたイメージを使い、Helm で EKS にデプロイする。MCP Server → Weather Agent の順に 2 ステップで完了する。
ワークショップのリポジトリでは scripts/terraform-prep-env-weather-agent.sh が Terraform の出力から workshop-mcp-weather-values.yaml と workshop-agent-weather-values.yaml を自動生成する。Terraform を使わない場合は、以下のように Helm の --set フラグで直接指定できる。
# 1. MCP Server(Weather Agent が参照するため先にデプロイ)
helm upgrade weather-mcp manifests/helm/mcp \
--install -n mcp-servers --create-namespace \
--set image.repository=${ECR_HOST}/agents-on-eks/weather-mcp \
--set image.tag=latest
# 2. Weather Agent(MCP HTTP接続 + テストモード)
helm upgrade weather-agent manifests/helm/agent \
--install -n agents --create-namespace \
-f manifests/helm/agent/mcp-remote.yaml \
--set image.repository=${ECR_HOST}/agents-on-eks/weather-agent \
--set image.tag=latest \
--set env.DISABLE_AUTH=1 \
--set env.SESSION_STORE_BUCKET_NAME=weather-agent-session-${ACCOUNT_ID} \
--set serviceAccount.name=weather-agent \
--set a2a.http_url=http://weather-agent.agents:9000/mcp-remote.yaml は MCP 接続先を stdio(ローカル)から HTTP(リモート MCP Server)に切り替える values override ファイルだ。a2a.http_url は Part 2 の A2A 連携で必要になる設定で、エージェントカードに記載される URL を Kubernetes Service の FQDN に設定する。
デプロイ完了を待つ。
kubectl rollout status deployment weather-mcp -n mcp-servers --timeout=180s
kubectl rollout status deployment weather-agent -n agents --timeout=180s動作確認では、NYC の 3 日間天気予報を正常に取得できた。クラスター内部から curl で直接アクセスするか、kubectl port-forward でローカルに転送して確認する。
# port-forward でローカルからアクセスする場合
kubectl port-forward svc/weather-agent -n agents 3000:80 &
curl -X POST http://localhost:3000/prompt \
-H "Content-Type: application/json" \
-d '{"text":"What is the weather forecast for New York City?"}'実際に返却されたレスポンスの一部を以下に示す。
Here's the weather forecast for New York City for the next 3 days:
**Today**
- Temperature: 60°F
- Conditions: Cloudy with areas of fog, showers, and thunderstorms
- Wind: 23-28 mph gusting up to 41 mph
- Precipitation: 100% chance with 0.5-0.75 inches of rainfall expected
- Good for outdoor activities: ❌ No - Heavy rain and thunderstorms expected
**Tuesday**
- Temperature: 42°F (falling to 40°F in afternoon)
- Conditions: Sunny
- Wind: 18-23 mph from the west
- Good for outdoor activities: ✅ Yes - Clear skies, though cool and windy
**Wednesday**
- Temperature: 39°F
- Conditions: Mostly sunny
- Wind: 6-12 mph from the southwest
- Good for outdoor activities: ✅ Yes - Pleasant sunny conditions, though coolシステムプロンプトで指示した「3 日間の予報」「屋外活動の適性を含める」というルールが正しく反映されている。NWS API から取得した生データ(気温・風速・降水確率)を、LLM が読みやすい形式にまとめて返却していることがわかる。
まとめ
- 1 つのエージェントに 3 つのアクセス経路 — UI からは REST(port 3000)で会話し、MCP(port 8080)でツールとして組み込み、A2A(port 9000)で他のエージェントから呼び出せる。同じコンテナに 3 プロトコルを同居させることで、再実装なしに利用形態を広げられる設計だ。
- ConfigMap でエージェントの振る舞いを制御 —
agent.mdとmcp.jsonを Helm values で注入する設計により、イメージ再ビルドなしでプロンプトやツール接続先を変更できる。 - Pod Identity + Kaniko で Docker 不要のビルドパイプライン — EKS 上の Kaniko + S3 ビルドコンテキスト + Pod Identity で、Docker デーモンに依存しないコンテナビルドが完結する。
