
Amazon Bedrock AgentCore Runtime をVPCモードで動かしたときのENIの挙動を確認してみた
はじめに
こんにちは、スーパーマーケットが大好きなコンサル部の神野です。
Amazon Bedrock AgentCore を VPC モードで触っていて、エージェントを増やすたびに ENI が増える挙動なんかなと個人的にふと気になったことがありました。
ただ、AgentCore の公式ドキュメントを読むと、この懸念に対して明確に回答が書いてありました。
ENIs are shared resources across agents that use the same subnet and security group configuration.
同じサブネット・同じセキュリティグループを使っているエージェント間であれば、ENI は共有されるとのこと。なるほど!
とはいえ実際に試して挙動を試していきたいので、本ブログで検証してみます!
AgentCore Runtime を VPC モードにするとどうなるか
今回 AgentCore Runtime を VPC モードで使用するのですが、そもそも AgentCore Runtime の VPC モードについて仕様をおさらいします。
AgentCore Runtime はデフォルトでは PUBLIC モードで動作していて、この場合は AWS 管理側のネットワークでエージェントが実行されるので、こちらの VPC 側に何かリソースが作られることはありません。
一方で VPC モードに切り替えると、指定したサブネット上にこちらの VPC 側の ENI(Elastic Network Interface)が新規作成されて、その ENI 経由でエージェントが RDS や社内 API といった VPC 内のプライベートリソースにアクセスできるようになります。下記、絵のようなイメージです。

VPC Lambdaにイメージとしては近いですね。
前提
今回の検証環境は下記のとおりです。
| 項目 | バージョン・値 |
|---|---|
| リージョン | ap-northeast-1(Tokyo) |
| AWS CDK CLI | 2.1118.2 |
| aws-cdk-lib | 2.248.0 |
@aws-cdk/aws-bedrock-agentcore-alpha |
2.250.0-alpha.0 |
| Node.js | v25.9.0 |
| npm | 11.12.1 |
| Python(AgentCore 用 + 負荷スクリプト) | 3.12 |
| AgentCore Runtime の作成数 | 10 個(同一 VPC・同一 SG) |
AgentCore Runtime は alpha パッケージの @aws-cdk/aws-bedrock-agentcore-alpha に L2 コンストラクトが用意されているので、今回はそちらを使います。
公式ドキュメントの記載を確認
改めて公式ドキュメントの VPC 接続セクションを確認すると、ENI の作成仕様について下記のとおり記載があります。
When you configure VPC connectivity for Amazon Bedrock AgentCore Runtime and tools:
- Amazon Bedrock creates elastic network interfaces (ENIs) in your VPC using the service-linked role
AWSServiceRoleForBedrockAgentCoreNetwork- These ENIs enable your Amazon Bedrock AgentCore Runtime and tools to securely communicate with resources in your VPC
- Each ENI is assigned a private IP address from the subnets you specify
- Security groups attached to the ENIs control which resources your runtime and tools can communicate with
ENI の作成にはサービスリンクロール AWSServiceRoleForBedrockAgentCoreNetwork が使われるので、こちらで IAM ロールを用意する必要はありません。
また、ENI の削除に関してはこのような記載もあります。
When you delete an agent, the associated ENI may persist in your VPC for up to 8 hours before it is automatically removed.
ENI が共有される代わりに、エージェントを削除しても ENI は最大 8 時間残る仕様ですね。検証時はこの時間差も意識しておく必要があります。
検証用 CDK テンプレートの実装
ここからは実際に検証するためのスタックを実装していきます!
検証に使った CDK プロジェクト
検証で使った CDK コード・Dockerfile・負荷試験スクリプト一式は、下記の GitHub リポジトリで公開しています
スタック構成
検証用に作成するリソースは下記のとおりです。
- VPC(プライベートサブネット 2 つ/パブリックサブネット 2 つ/NAT Gateway 1 基)
- セキュリティグループ 1 つ(AgentCore Runtime 用に共有)
- ダミーエージェントの Docker イメージ(
AgentRuntimeArtifact.fromAssetで自動ビルド & ECR push) - AgentCore Runtime L2 (
agentcore.Runtime) を 10 個(IAM 実行ロールは L2 が自動生成)
ダミーエージェント
AgentCore Runtime には最低限動くコンテナイメージが必要なので、/ping と /invocations だけを返す FastAPI アプリを用意します。
from fastapi import FastAPI
app = FastAPI()
@app.get("/ping")
def ping() -> dict[str, str]:
return {"status": "ok"}
@app.post("/invocations")
def invocations(payload: dict) -> dict:
return {"echo": payload}
FROM public.ecr.aws/docker/library/python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py ./
EXPOSE 8080
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
AgentCore Runtime は 8080 ポートで HTTP を受け付ける仕様です。/ping と /invocations を備えていればヘルスチェックと呼び出しに応答できます。
CDK スタック
VPC、セキュリティグループ、Docker イメージ、IAM ロール、そして Runtime 10 個を CDK で一気に定義していきます。
import * as path from 'path';
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecrAssets from 'aws-cdk-lib/aws-ecr-assets';
import * as agentcore from '@aws-cdk/aws-bedrock-agentcore-alpha';
import { Construct } from 'constructs';
export interface AgentcoreEniTestStackProps extends cdk.StackProps {
readonly runtimeCount: number;
}
export class AgentcoreEniTestStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: AgentcoreEniTestStackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'AgentCoreVpc', {
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{ name: 'public', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24 },
{ name: 'private', subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 },
],
});
const agentSg = new ec2.SecurityGroup(this, 'AgentCoreSg', {
vpc,
description: 'Shared SG for AgentCore Runtime ENI test',
allowAllOutbound: true,
});
const artifact = agentcore.AgentRuntimeArtifact.fromAsset(
path.join(__dirname, '..', 'agent'),
{ platform: ecrAssets.Platform.LINUX_ARM64 },
);
for (let i = 0; i < props.runtimeCount; i++) {
new agentcore.Runtime(this, `AgentRuntime${i}`, {
runtimeName: `eni_test_runtime_${i}`,
agentRuntimeArtifact: artifact,
networkConfiguration: agentcore.RuntimeNetworkConfiguration.usingVpc(this, {
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [agentSg],
}),
});
}
}
}
for ループで 10 個の AgentCore Runtime を作成しています。すべてのランタイムが同じ VPC・同じサブネット・同じセキュリティグループを参照している点がポイントで、これが ENI 共有のトリガーとなります。
VPC 接続を有効にすると、AgentCore が AWSServiceRoleForBedrockAgentCoreNetwork を使って自動的に ENI を作ってくれます。CDK 側で ENI を明示的に作成する必要はありません。
デプロイと動作確認
それでは実際にデプロイして、ENI の数を見ていきます!
デプロイ
npx cdk deploy --profile private --context runtimeCount=10 --require-approval never
Runtime を 10 個作るため、デプロイ完了までは 10 分程度かかりました。Docker ビルドから CloudFormation デプロイまで一気に走ってくれます。
デプロイ完了後、スタックの Outputs に下記の値が返ってきました。
Outputs:
AgentcoreEniTestV2.VpcId = vpc-xxxxxxxxxxxxxxxxx
AgentcoreEniTestV2.SecurityGroupId = sg-xxxxxxxxxxxxxxxxx
AgentcoreEniTestV2.ImageUri = <ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/cdk-hnb659fds-container-assets-<ACCOUNT_ID>-ap-northeast-1:xxxxxxxx...
Runtime の状態確認
まず 10 個の Runtime が無事作成されているかを確認します。
aws bedrock-agentcore-control list-agent-runtimes --profile private --region ap-northeast-1 \
--query 'agentRuntimes[?starts_with(agentRuntimeName, `eni_test_runtime_`)].{Name:agentRuntimeName,Status:status}' \
--output table
---------------------------------------
| ListAgentRuntimes |
+----------------------+--------------+
| Name | Status |
+----------------------+--------------+
| eni_test_runtime_9 | READY |
| eni_test_runtime_8 | READY |
| eni_test_runtime_7 | READY |
| eni_test_runtime_6 | READY |
| eni_test_runtime_5 | READY |
| eni_test_runtime_4 | READY |
| eni_test_runtime_3 | READY |
| eni_test_runtime_2 | READY |
| eni_test_runtime_1 | READY |
| eni_test_runtime_0 | READY |
+----------------------+--------------+
10 個全部 READY ですね!問題なく作成されています。
ENI の数を確認
続いて本題の ENI を確認していきます。
aws ec2 describe-network-interfaces --profile private --region ap-northeast-1 \
--filters "Name=group-id,Values=sg-xxxxxxxxxxxxxxxxx" \
--query 'NetworkInterfaces[].{Id:NetworkInterfaceId,Subnet:SubnetId,Ip:PrivateIpAddress,AZ:AvailabilityZone,InterfaceType:InterfaceType,Status:Status}' \
--output table
結果はこちらです。
-----------------------------------------------------------------------------------------------------------------------
| DescribeNetworkInterfaces |
+------------------+------------------------+----------------+-------------+----------+----------------------------+
| AZ | Id | InterfaceType | Ip | Status | Subnet |
+------------------+------------------------+----------------+-------------+----------+----------------------------+
| ap-northeast-1c | eni-aaaaaaaaaaaaaaaaa | agentic_ai | 10.0.3.160 | in-use | subnet-xxxxxxxxxxxxxxxxx |
| ap-northeast-1a | eni-bbbbbbbbbbbbbbbbb | agentic_ai | 10.0.2.185 | in-use | subnet-yyyyyyyyyyyyyyyyy |
+------------------+------------------------+----------------+-------------+----------+----------------------------+
おお、ENI が 2 つしか作られていないですね!
AgentCore Runtime を 10 個デプロイしたけど、ENI は指定したプライベートサブネット 1 つあたり 1 つの、合計 2 つしか作られていませんでした。公式ドキュメントの記載どおり、同一サブネット・同一 SG のエージェント間で ENI が共有されていることが確認できました!
負荷をかけても ENI は増えないのか
ここまではエージェントを 10 個作っただけの状態です。実際に呼び出されて AgentCore が内部的に microVM をスケールさせたときに、ENI が増える瞬間があるのでは?というのが気になるところです。
というわけで、並列で invoke して負荷をかけながら ENI を監視してみます。
負荷試験スクリプト
boto3 の bedrock-agentcore データプレーンから invoke_agent_runtime を並列で呼びつつ、別スレッドで ENI 一覧を 5 秒ごとに記録するスクリプトを用意しました。
import concurrent.futures
import json
import time
import uuid
from threading import Event, Thread
import boto3
REGION = "ap-northeast-1"
PROFILE = "private"
SG_ID = "sg-xxxxxxxxxxxxxxxxx"
RUNTIME_NAME_PREFIX = "eni_test_runtime_"
CONCURRENCY = 50
DURATION_SEC = 120
session = boto3.Session(profile_name=PROFILE, region_name=REGION)
agentcore_ctl = session.client("bedrock-agentcore-control")
agentcore = session.client("bedrock-agentcore")
ec2 = session.client("ec2")
arns = [
r["agentRuntimeArn"]
for r in agentcore_ctl.list_agent_runtimes()["agentRuntimes"]
if r["agentRuntimeName"].startswith(RUNTIME_NAME_PREFIX)
]
def describe_eni():
return ec2.describe_network_interfaces(
Filters=[{"Name": "group-id", "Values": [SG_ID]}]
)["NetworkInterfaces"]
def invoke_once(idx):
arn = arns[idx % len(arns)]
session_id = f"load-{uuid.uuid4().hex}-{idx:08d}"
resp = agentcore.invoke_agent_runtime(
agentRuntimeArn=arn,
runtimeSessionId=session_id,
payload=json.dumps({"prompt": f"load {idx}"}).encode("utf-8"),
contentType="application/json",
)
resp["response"].read()
50 並列で 120 秒間ひたすら invoke を回します。runtimeSessionId はリクエストごとにユニークな UUID にしており、AgentCore 側から見ると完全に別セッションとして扱われる構成です。記事では省略していますが、ウォッチャースレッドで 5 秒おきに describe_eni() を叩いて ENI 数と IP 一覧を記録していきます。
負荷試験を実行
uv run --with boto3 python scripts/load_test.py
結果はこちらです。
Target runtimes: 10
=== Before load ===
ENI count: 2
eni-aaaaaaaaaaaaaaaaa / 10.0.3.160 / ap-northeast-1c / agentic_ai / in-use
eni-bbbbbbbbbbbbbbbbb / 10.0.2.185 / ap-northeast-1a / agentic_ai / in-use
=== Load result ===
Duration: 130.8s
Total: 2148, succ=768, fail=1380
Error samples:
- ServiceQuotaExceededException: maxVms limit exceeded for account ...
=== ENI count over time ===
+ 0.1s: ENI=2 IPs=['10.0.3.160', '10.0.2.185']
+ 5.2s: ENI=2 IPs=['10.0.3.160', '10.0.2.185']
+ 10.3s: ENI=2 IPs=['10.0.3.160', '10.0.2.185']
...
+ 122.9s: ENI=2 IPs=['10.0.3.160', '10.0.2.185']
+ 128.0s: ENI=2 IPs=['10.0.3.160', '10.0.2.185']
Max ENI observed during load: 2
Post-load ENI count: 2
130 秒間で合計 2,148 回の invoke が走り、うち 768 回が成功しました。残りの 1,380 回は後述する ServiceQuotaExceededException で失敗しています。
負荷開始前・負荷中・負荷後を通じて、ENI 数は 2 個で一定、プライベート IP も 10.0.3.160 と 10.0.2.185 のまま不変でした。
あくまで今回の環境に限ってですが、負荷をかけても ENI は増えないことが確認できました!
失敗した invoke のエラーについて
負荷試験で 1,380 回失敗していますが、エラーはすべて下記の ServiceQuotaExceededException でした。
ServiceQuotaExceededException: maxVms limit exceeded for account XXXXXXXXXXXX.
Please contact AWS Support for more information.
AgentCore Runtime のクォータは公式ドキュメントにも Active session workloads per account として記載されており、ap-northeast-1 のような us-east-1 / us-west-2 以外のリージョンではデフォルト 500 となっています。各 runtimeSessionId ごとに専用の microVM がプロビジョニングされる仕様のため、このクォータが実質的な並列セッション数の上限となります。今回はこのクォータに達したことでエラーが返ってきたと考えられます。
セキュリティグループを変えたら ENI は分かれるのか
ここまではすべての Runtime が同じ SGパターンで、ENI が 2 個のまま変わらないことを見てきました。最後の確認として、公式ドキュメントの「same subnet and security group」のうち SG だけを変えたら ENI がどう分かれるのかも確認します。
10 個の Runtime のうち、Runtime 0 だけ別の SG に切り替えてデプロイします。CDK 側で 2 つ目の SG を追加し、i === 0 のときだけそちらに割り当てる構成です。
const agentSg = new ec2.SecurityGroup(this, 'AgentCoreSg', { vpc, allowAllOutbound: true });
const agentSgSecondary = new ec2.SecurityGroup(this, 'AgentCoreSgSecondary', { vpc, allowAllOutbound: true });
for (let i = 0; i < props.runtimeCount; i++) {
const sg = i < (props.secondarySgRuntimeCount ?? 0) ? agentSgSecondary : agentSg;
new agentcore.Runtime(this, `AgentRuntime${i}`, {
runtimeName: `${prefix}${i}`,
agentRuntimeArtifact: artifact,
networkConfiguration: agentcore.RuntimeNetworkConfiguration.usingVpc(this, {
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [sg],
}),
});
}
secondarySgRuntimeCount=1 でデプロイすると、Runtime 0 だけが Secondary SG 側にぶら下がり、Runtime 1〜9 は Primary SG を共有する構成になります。
デプロイ後、各 SG に紐づく ENI を確認します。
aws ec2 describe-network-interfaces --profile private --region ap-northeast-1 \
--filters "Name=group-id,Values=<Primary SG ID>" \
--query 'NetworkInterfaces[].{Id:NetworkInterfaceId,Ip:PrivateIpAddress,AZ:AvailabilityZone}' \
--output table
aws ec2 describe-network-interfaces --profile private --region ap-northeast-1 \
--filters "Name=group-id,Values=<Secondary SG ID>" \
--query 'NetworkInterfaces[].{Id:NetworkInterfaceId,Ip:PrivateIpAddress,AZ:AvailabilityZone}' \
--output table
=== Primary SG (Runtime 1-9 の 9 個が共有) ===
+------------------+------------------------+-------------+
| AZ | Id | Ip |
+------------------+------------------------+-------------+
| ap-northeast-1a | eni-ccccccccccccccccc | 10.0.2.254 |
| ap-northeast-1c | eni-ddddddddddddddddd | 10.0.3.36 |
+------------------+------------------------+-------------+
=== Secondary SG (Runtime 0 のみ) ===
+------------------+------------------------+-------------+
| AZ | Id | Ip |
+------------------+------------------------+-------------+
| ap-northeast-1a | eni-eeeeeeeeeeeeeeeee | 10.0.2.233 |
| ap-northeast-1c | eni-fffffffffffffffff | 10.0.3.185 |
+------------------+------------------------+-------------+
Secondary SG 側にもちゃんと 2 個の ENI が作られましたね!
今回の検証範囲では、2 サブネット × 2 SG 構成で ENI が合計 4 個という結果になりました。少なくとも今回の構成ではサブネット数と SG 構成の組み合わせが ENI 数に反映されるとドキュメント通りの挙動が理解できました。
おわりに
ドキュメント通り、同じサブネット・同じセキュリティグループを使っていればENIはエージェントの数によらず増えることはありませんでした!ドキュメントで書いてあるものを実際に検証するのは面白いですね。
本記事が少しでも参考になりましたら幸いです。最後までご覧いただきありがとうございました!










