AgentCore RuntimeにSession Storage・S3 Files・EFSを同時マウントしてCDKで構築・検証してみた

AgentCore RuntimeにSession Storage・S3 Files・EFSを同時マウントしてCDKで構築・検証してみた

AgentCore RuntimeにSession Storage・S3 Files・EFSを同時マウントし、セッション間でデータが共有されるか、分離されるかを検証しました。CDKによる構築方法と、EFS / S3 Files利用時に必要なIAM権限、Security Group設定のポイントも紹介します。
2026.06.09

はじめに

前回の記事ではインタラクティブシェルの基本操作を試しましたが、今回はストレージマウント機能にフォーカスした追加検証です。

https://dev.classmethod.jp/articles/bedrock-agentcore-runtime-interactive-shell/

公式ドキュメントでは最大5つのファイルシステムをマウントできるとされています。利用可能なストレージは3種類です。

ストレージ 提供形態 セッション間のデータ共有 データの寿命 構成更新(デプロイ)時 VPC
Session Storage マネージド (Preview) 不可 同一セッションの stop/resume 間で保持。構成更新・14日間アイドルで削除 データ削除 不要
S3 Files 自前(BYO) 同じ Access Point をマウントしたセッション間で可 削除するまで永続(S3と同期) データ自体への影響なし 必要
EFS 自前(BYO) 同じ Access Point をマウントしたセッション間で可 削除するまで永続 データ自体への影響なし 必要

比較表の永続性は公式ドキュメントの記載に基づいています。セッション間の共有・分離については今回の検証で動作確認しました。

VPCやNAT Gatewayの構築手順についてはこちらの記事で説明しています。

検証環境

項目
リージョン ap-northeast-1(東京)
aws-cdk-lib 2.258.0
aws-cdk CLI 2.1126.0
@aws/agentcore CLI 0.18.0
VPC 既存VPC(Isolated Subnet 3AZ)
検証日 2026-06-09

aws-cdk-lib/aws-s3files モジュール(L1コンストラクト)を利用するため、aws-cdk-lib 2.258.0以降が必要です。

環境構築(CDK)

アーキテクチャ

┌─ StorageTestStack ─────────────────────────┐
│  EFS FileSystem + Access Point             │
│  S3 Files FileSystem + Access Point        │
│  共有 Security Group (TCP 2049)            │
│  マウントターゲット (3AZ)                   │
└────────────────────────────────────────────┘
         ↓ 参照
┌─ RuntimeTestStack ─────────────────────────┐
│  AgentCore Runtime (VPCモード)             │
│    /mnt/workspace  ← Session Storage       │
│    /mnt/efs        ← EFS                   │
│    /mnt/s3data     ← S3 Files              │
│  Security Group (→ TCP 2049 アウトバウンド) │
│  IAM Role (EFS + S3 Files 権限)            │
└────────────────────────────────────────────┘

プロジェクト構成

agentcore-storage-test/
├── bin/app.ts
├── lib/
│   ├── storage-stack.ts    # EFS + S3 Files + SG + マウントターゲット
│   └── runtime-stack.ts    # AgentCore Runtime (VPCモード + filesystem)
├── docker-compose.yml      # インタラクティブシェル接続用
├── cdk.json
├── package.json
└── tsconfig.json

CDK L2 の制限と escape hatch

aws-cdk-lib/aws-bedrockagentcore のRuntime L2コンストラクトは filesystemConfigurations プロパティを未サポートです。
2026-06-09時点、CDK 2.258.0で確認しました。escape hatchでCfnRuntimeに直接設定しています。

const cfnRuntime = runtime.node.defaultChild as agentcore.CfnRuntime;
cfnRuntime.addPropertyOverride('FilesystemConfigurations', [
  {
    SessionStorage: {
      MountPath: '/mnt/workspace',
    },
  },
  {
    EfsAccessPoint: {
      AccessPointArn: storage.efsAccessPoint.accessPointArn,
      MountPath: '/mnt/efs',
    },
  },
  {
    S3FilesAccessPoint: {
      AccessPointArn: storage.s3FilesAccessPointArn,
      MountPath: '/mnt/s3data',
    },
  },
]);

VPC 接続と Security Group

既存VPCを参照し、EFS / S3 Filesのマウントターゲット用に共有のSecurity Groupを作成します。

const vpcId = this.node.tryGetContext('vpcId');
this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId });

this.mountTargetSg = new ec2.SecurityGroup(this, 'MountTargetSg', {
  vpc: this.vpc,
  description: 'Allow NFS from AgentCore Runtime',
  allowAllOutbound: false,
});

Runtime側からマウントターゲットへTCP 2049を許可します。connections.allowTo はRuntime側のアウトバウンドとマウントターゲット側のインバウンドの両方にルールを追加します。

runtime.connections.allowTo(
  storage.mountTargetSg, ec2.Port.tcp(2049), 'NFS to EFS/S3Files'
);

IAM 権限

Runtimeのロールには以下の権限を付与しました。ClientMount / ClientWrite に加え、今回の構成ではRuntime作成時のバリデーションでDescribe系も必要でした。

サービス アクション 用途
EFS elasticfilesystem:ClientMount マウント
EFS elasticfilesystem:ClientWrite 書き込み
EFS elasticfilesystem:DescribeAccessPoints 作成時バリデーション
EFS elasticfilesystem:DescribeMountTargets 作成時バリデーション
S3 Files s3files:ClientMount マウント
S3 Files s3files:ClientWrite 書き込み
S3 Files s3files:GetAccessPoint 作成時バリデーション
S3 Files s3files:ListMountTargets 作成時バリデーション
S3 Files s3files:DescribeMountTargets 作成時バリデーション

今回の検証では、Describe系が不足している状態でRuntime作成時に400エラーが発生し、エラーメッセージに不足権限が示されました。まとめて付与しておくのがおすすめです。

デプロイ

npm install
npx cdk deploy StorageTestStack --require-approval never
npx cdk deploy RuntimeTestStack --require-approval never

インタラクティブシェル接続

接続手順の詳細は前回の記事を参照してください。今回は以下のdocker-compose.ymlを使用しました。Runtime ARNは自分の環境に合わせて変更してください。

services:
  shell:
    image: node:20-slim
    stdin_open: true
    tty: true
    environment:
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
      - AWS_SESSION_TOKEN
      - AWS_REGION=ap-northeast-1
    working_dir: /work
    command:
      - sh
      - -c
      - |
        npm install -g @aws/agentcore 2>/dev/null &&
        agentcore exec --it --runtime <YOUR_RUNTIME_ARN> --region ap-northeast-1

検証結果

マウントの確認

df でマウント状態を確認しました。

Filesystem                     1K-blocks    Used        Available Use% Mounted on
overlay                          9186644 1759644          6938760  21% /
127.0.0.1:/export                1048576       0          1048576   0% /mnt/workspace
127.0.0.1:/             9007199254739968       0 9007199254739968   0% /mnt/efs
127.0.0.1:/             9007199254739968       0 9007199254739968   0% /mnt/s3data

3種すべてが正常にマウントされています。

  • Session Storage (/mnt/workspace): 約1GiB(df 上の表示値)
  • EFS (/mnt/efs): 約8EiB(NFS上の表示値)
  • S3 Files (/mnt/s3data): 約8EiB(同上)

セッション間のデータ共有テスト

2つのターミナルから別セッションに接続しました。それぞれ異なるinstance IDが割り当てられていました。

  • セッション1: instance A
  • セッション2: instance B

セッション1で書き込み

echo "hello from session1" > /mnt/s3data/test.txt
echo "hello from session1" > /mnt/efs/test.txt
echo "hello from session1" > /mnt/workspace/test.txt

セッション2で確認

cat /mnt/s3data/test.txt   # → "hello from session1" ✅
cat /mnt/efs/test.txt      # → "hello from session1" ✅
ls /mnt/workspace/          # → 空(ファイルなし)    ✅

結果まとめ

マウント セッション間 反映タイミング
/mnt/s3data (S3 Files) 共有 close-to-open(ファイルを閉じた後、別セッションで開くと反映)
/mnt/efs (EFS) 共有 即時(今回の検証では書き込み直後に別セッションから参照可能)
/mnt/workspace (Session Storage) 独立 自セッションのみ

S3 バケットへの同期確認

今回の検証では、Runtime内で /mnt/s3data/test.txt に書き込み後、約1分後にS3バケット側で確認できました。

$ aws s3 ls s3://storageteststack-s3filesbucketXXXXXXXX-XXXXXXXXXXXX/agentcore/
2026-06-09 15:20:09         21 test.txt

Runtimeのインタラクティブシェルにはファイル転送機能がないため、S3 FilesはRuntime外部との入出力の中継点として活用できます。S3バケット側に反映されたオブジェクトはRuntimeから参照でき、Runtimeで作成したファイルもS3バケット側への同期後にダウンロードできます。

注意事項

通信経路

ローカル PC
  ↓ HTTPS/WebSocket (SigV4認証)
bedrock-agentcore.ap-northeast-1.amazonaws.com
  ↓ AWS内部経路(利用者のVPCを通らない)
microVM
  ↓ TCP 2049 (NFS over TLS)
マウントターゲット (EFS / S3 Files)  ← 利用者VPC内
  • シェル接続はAgentCore API経由で、NAT GatewayやSSMは不要です
  • ファイルシステムへのアクセスはmicroVM → マウントターゲット間の通信です。マウントターゲットのSecurity GroupでRuntimeからのTCP 2049(NFS)インバウンドを許可する必要があります

Session Storage のデータ消失条件

Session Storageのデータは、Runtimeの構成更新(デプロイ)時に削除されます。セッションが14日間アイドル状態の場合も削除されます。

EFS / S3 Files の共有利用について

今回の構成ではAccess Pointの posixUser1000:1000 に固定しているため、全セッションが同じUID/GIDでアクセスします。ファイルパーミッションによる利用者単位のアクセス制御はできないため、複数名で同じRuntimeを共有する場合は他者が読み書きできる前提でご利用ください。

S3 Filesを中継用・一時データ置き場として使う場合は、バッキングバケットにライフサイクルルールを設定して保持期間を限定しておくと管理しやすくなります。

まとめ

AgentCore RuntimeのSession Storage・S3 Files・EFSを1つのRuntimeに同時マウントし、セッション間でのデータ共有・分離を検証しました。

S3 FilesとEFSはセッション間で共有され、Session Storageはセッションごとに独立していることを確認できました。ファイルパスへの入出力を前提とするアプリケーションでは、S3 FilesやEFSをマウントしておくことで、通常のファイル操作として外部ストレージにデータを保存できます。

一方、出力先をスクリプト側で制御できる場合は、SDKやCLIで直接S3に保存する方がシンプルです。VPCやマウントターゲットの構築要否も含め、要件に応じて使い分けるのがよさそうです。

参考リンク

CDKコード全文

lib/storage-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as efs from 'aws-cdk-lib/aws-efs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3files from 'aws-cdk-lib/aws-s3files';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class StorageStack extends cdk.Stack {
  public readonly vpc: ec2.IVpc;
  public readonly mountTargetSg: ec2.SecurityGroup;
  public readonly efsAccessPoint: efs.AccessPoint;
  public readonly s3FilesAccessPointArn: string;

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpcId = this.node.tryGetContext('vpcId');
    this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId });

    this.mountTargetSg = new ec2.SecurityGroup(this, 'MountTargetSg', {
      vpc: this.vpc,
      description: 'Allow NFS from AgentCore Runtime',
      allowAllOutbound: false,
    });

    // EFS
    const fileSystem = new efs.FileSystem(this, 'Efs', {
      vpc: this.vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      securityGroup: this.mountTargetSg,
      performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    this.efsAccessPoint = fileSystem.addAccessPoint('AgentCoreAP', {
      path: '/agentcore',
      createAcl: { ownerGid: '1000', ownerUid: '1000', permissions: '755' },
      posixUser: { gid: '1000', uid: '1000' },
    });

    // S3 Files
    const bucket = new s3.Bucket(this, 'S3FilesBucket', {
      versioned: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    const s3FilesRole = new iam.Role(this, 'S3FilesRole', {
      assumedBy: new iam.ServicePrincipal('elasticfilesystem.amazonaws.com'),
    });
    s3FilesRole.addToPolicy(new iam.PolicyStatement({
      actions: ['s3:ListBucket*'],
      resources: [bucket.bucketArn],
    }));
    s3FilesRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        's3:AbortMultipartUpload', 's3:DeleteObject',
        's3:GetObject*', 's3:List*', 's3:PutObject*',
      ],
      resources: [bucket.arnForObjects('*')],
    }));
    s3FilesRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        'events:DeleteRule', 'events:DisableRule', 'events:EnableRule',
        'events:PutRule', 'events:PutTargets', 'events:RemoveTargets',
      ],
      resources: [`arn:${cdk.Aws.PARTITION}:events:*:*:rule/DO-NOT-DELETE-S3-Files*`],
      conditions: {
        StringEquals: { 'events:ManagedBy': 'elasticfilesystem.amazonaws.com' },
      },
    }));
    s3FilesRole.addToPolicy(new iam.PolicyStatement({
      actions: [
        'events:DescribeRule', 'events:ListRuleNamesByTarget',
        'events:ListRules', 'events:ListTargetsByRule',
      ],
      resources: [`arn:${cdk.Aws.PARTITION}:events:*:*:rule/*`],
    }));

    const s3FilesFs = new s3files.CfnFileSystem(this, 'S3FilesFs', {
      bucket: bucket.bucketArn,
      roleArn: s3FilesRole.roleArn,
    });

    const subnets = this.vpc.isolatedSubnets.length > 0
      ? this.vpc.isolatedSubnets
      : this.vpc.privateSubnets;

    subnets.forEach((subnet, i) => {
      new s3files.CfnMountTarget(this, `S3FilesMt${i}`, {
        fileSystemId: s3FilesFs.attrFileSystemId,
        subnetId: subnet.subnetId,
        securityGroups: [this.mountTargetSg.securityGroupId],
      });
    });

    const s3FilesAp = new s3files.CfnAccessPoint(this, 'S3FilesAP', {
      fileSystemId: s3FilesFs.attrFileSystemId,
      rootDirectory: {
        path: '/agentcore',
        creationPermissions: { ownerGid: '1000', ownerUid: '1000', permissions: '755' },
      },
      posixUser: { gid: '1000', uid: '1000' },
    });

    this.s3FilesAccessPointArn = s3FilesAp.attrAccessPointArn;
  }
}
lib/runtime-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore';
import { Construct } from 'constructs';
import { StorageStack } from './storage-stack';

interface RuntimeStackProps extends cdk.StackProps {
  storage: StorageStack;
}

export class RuntimeStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: RuntimeStackProps) {
    super(scope, id, props);

    const { storage } = props;
    const imageUri = this.node.tryGetContext('agentcoreImageUri');

    const runtime = new agentcore.Runtime(this, 'Runtime', {
      runtimeName: 'storageTest',
      agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromImageUri(imageUri),
      networkConfiguration: agentcore.RuntimeNetworkConfiguration.usingVpc(this, {
        vpc: storage.vpc,
        vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
      }),
    });

    runtime.connections.allowTo(
      storage.mountTargetSg, ec2.Port.tcp(2049), 'NFS to EFS/S3Files'
    );

    runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: ['ecr:GetAuthorizationToken'],
      resources: ['*'],
    }));
    runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: [
        'ecr:BatchGetImage',
        'ecr:GetDownloadUrlForLayer',
        'ecr:BatchCheckLayerAvailability',
      ],
      resources: [
        `arn:aws:ecr:${this.region}:${this.account}:repository/bedrock-agentcore-agent`,
      ],
    }));

    runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: ['elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite'],
      resources: [storage.efsAccessPoint.fileSystem.fileSystemArn],
      conditions: {
        StringEquals: {
          'elasticfilesystem:AccessPointArn': storage.efsAccessPoint.accessPointArn,
        },
      },
    }));
    runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: [
        'elasticfilesystem:DescribeAccessPoints',
        'elasticfilesystem:DescribeMountTargets',
      ],
      resources: ['*'],
    }));

    runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
      actions: [
        's3files:ClientMount', 's3files:ClientWrite', 's3files:GetAccessPoint',
        's3files:ListMountTargets', 's3files:DescribeMountTargets',
      ],
      resources: ['*'],
    }));

    const cfnRuntime = runtime.node.defaultChild as agentcore.CfnRuntime;
    cfnRuntime.addPropertyOverride('FilesystemConfigurations', [
      {
        SessionStorage: {
          MountPath: '/mnt/workspace',
        },
      },
      {
        EfsAccessPoint: {
          AccessPointArn: storage.efsAccessPoint.accessPointArn,
          MountPath: '/mnt/efs',
        },
      },
      {
        S3FilesAccessPoint: {
          AccessPointArn: storage.s3FilesAccessPointArn,
          MountPath: '/mnt/s3data',
        },
      },
    ]);
  }
}

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事