Amazon Bedrock AgentCore Runtime をVPCモードで動かしたときのENIの挙動を確認してみた

Amazon Bedrock AgentCore Runtime をVPCモードで動かしたときのENIの挙動を確認してみた

2026.04.17

はじめに

こんにちは、スーパーマーケットが大好きなコンサル部の神野です。

Amazon Bedrock AgentCore を VPC モードで触っていて、エージェントを増やすたびに ENI が増える挙動なんかなと個人的にふと気になったことがありました。

ただ、AgentCore の公式ドキュメントを読むと、この懸念に対して明確に回答が書いてありました。

ENIs are shared resources across agents that use the same subnet and security group configuration.

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html

同じサブネット・同じセキュリティグループを使っているエージェント間であれば、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 内のプライベートリソースにアクセスできるようになります。下記、絵のようなイメージです。

CleanShot 2026-04-17 at 17.56.11@2x

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 リポジトリで公開しています

https://github.com/yuu551/agentcore-eni-test

スタック構成

検証用に作成するリソースは下記のとおりです。

  • 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 アプリを用意します。

agent/app.py
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}
agent/Dockerfile
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 で一気に定義していきます。

lib/agentcore-eni-test-stack.ts
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 秒ごとに記録するスクリプトを用意しました。

scripts/load_test.py
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.16010.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 がプロビジョニングされる仕様のため、このクォータが実質的な並列セッション数の上限となります。今回はこのクォータに達したことでエラーが返ってきたと考えられます。

https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/bedrock-agentcore-limits.html

セキュリティグループを変えたら ENI は分かれるのか

ここまではすべての Runtime が同じ SGパターンで、ENI が 2 個のまま変わらないことを見てきました。最後の確認として、公式ドキュメントの「same subnet and security group」のうち SG だけを変えたら ENI がどう分かれるのかも確認します。

10 個の Runtime のうち、Runtime 0 だけ別の SG に切り替えてデプロイします。CDK 側で 2 つ目の SG を追加し、i === 0 のときだけそちらに割り当てる構成です。

lib/agentcore-eni-test-stack.ts
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はエージェントの数によらず増えることはありませんでした!ドキュメントで書いてあるものを実際に検証するのは面白いですね。

本記事が少しでも参考になりましたら幸いです。最後までご覧いただきありがとうございました!

この記事をシェアする

関連記事