BEMAロゴ

エンジニアの
成長を支援する
技術メディア

【AWS】2026年最新機能「S3 Files」をTerraformで構築する手順と3つのハマりどころ

リンクをコピー
リンクをコピーしました
Xでシェアするfacebookでシェアする

はじめに

2026年4月にGA(一般提供)となったS3 Filesは、S3バケットをNFSファイルシステムとしてマウントできるサービスです。EC2などのクライアントから通常のディレクトリのように読み書きでき、ファイルサーバー移行の選択肢として注目されています。

従来、ファイルシステムが必要な場合はEFSのような専用のファイルストレージを用意し、S3上のデータを使う場合はオブジェクトとファイルの間でコピー・同期する構成が一般的でした。S3 Filesはデータをオブジェクトのまま保持しつつファイルシステムとしてもアクセスできるため、複製・同期が不要になります。また、バッキングストアがS3であることから、S3の高い耐久性・可用性やストレージクラスによるコスト効率、IAMや暗号化によるセキュリティもそのまま活用できます。

しかし、公式ドキュメントを読みながら実際にTerraformで構築を進めると、IAMの設定やセキュリティグループの要件など、いくつか「詰まりやすい」ポイントが存在しました。

本記事では、実際にTerraformでS3 Filesを構築した経験をもとに、必要な構成リソースとハマりどころを共有します。S3 Filesの導入を検討している方の参考になれば幸いです。

構成の全体像

今回の検証構成は以下のとおりです。

  • S3バケット(バッキングストア)

  • S3 Files ファイルシステム(S3バケットと紐づく)

  • S3 Files マウントターゲット(VPC内のサブネットに作成、AZごと)

  • EC2インスタンス2台(クライアント、/mnt/s3files にマウント)

  • IAMロール・ポリシー(EC2用、S3 Files用)

  • セキュリティグループ(NFS通信の許可)

Terraformはモジュール構成で管理しています。

terraform/
├── main.tf
├── provider.tf
├── variables.tf
└── modules/
    ├── network/   # VPC, Subnet, NAT Gateway
    ├── security/  # Security Group
    ├── storage/   # S3, S3 Files
    ├── app/       # EC2, IAM
    └── alb/       # ALB, Target Group

S3 Files関連リソースの設定

Providerの設定

S3 FilesのTerraformリソースは hashicorp/aws provider v6.40.0 以降で利用できます。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.40.0"
    }
  }
  required_version = ">= 1.0"
}

S3バケット

S3 Filesのバッキングストアになる通常のS3バケットを作成します。 バージョニングとサーバーサイド暗号化の有効化がS3 Files ファイルシステム作成の前提条件のため、depends_on で順序を制御します。 ※ 検証環境のため force_destroy = true を指定しています。本番環境では誤削除防止の観点から慎重に検討してください。

resource "aws_s3_bucket" "this" {
  bucket        = "${var.project_name}-data-bucket"
  force_destroy = true
}

resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  bucket = aws_s3_bucket.this.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

S3 Files用IAMロール

S3 Files自身がS3バケットにアクセスするためのIAMロールです。Principalは elasticfilesystem.amazonaws.com を指定します(S3 FilesはEFSの系譜のためこのサービス名になっています)。
SourceAccountとSourceArnの条件を付けることで、意図しないアカウントやリソースからのAssumeRoleを防ぎます。

resource "aws_iam_role" "s3_files_role" {
  name = "${var.project_name}-s3-files-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "elasticfilesystem.amazonaws.com" }
      Action    = "sts:AssumeRole"
      Condition = {
        StringEquals = { "aws:SourceAccount" = var.account_id }
        ArnLike      = { "aws:SourceArn" = "arn:aws:s3files:${var.region}:${var.account_id}:file-system/*" }
      }
    }]
  })
}

このロールに付与するポリシーは、S3バケットへの読み書き権限に加えて、S3 Filesが内部で使用するEventBridgeルール(DO-NOT-DELETE-S3-Files*)の管理権限も必要です。

resource "aws_iam_role_policy" "s3_files_policy" {
  name = "${var.project_name}-s3-files-policy"
  role = aws_iam_role.s3_files_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid      = "S3BucketPermissions"
        Effect   = "Allow"
        Action   = ["s3:ListBucket", "s3:ListBucketVersions"]
        Resource = aws_s3_bucket.this.arn
        Condition = {
          StringEquals = { "aws:ResourceAccount" = var.account_id }
        }
      },
      {
        Sid    = "S3ObjectPermissions"
        Effect = "Allow"
        Action = ["s3:AbortMultipartUpload", "s3:DeleteObject*", 
"s3:GetObject*", "s3:List*", "s3:PutObject*"]
        Resource = "${aws_s3_bucket.this.arn}/*"
        Condition = {
          StringEquals = { "aws:ResourceAccount" = var.account_id }
        }
      },
      {
        Sid    = "EventBridgeManage"
        Effect = "Allow"
        Action = [
          "events:DeleteRule", "events:DisableRule", 
"events:EnableRule",
          "events:PutRule", "events:PutTargets", "events:RemoveTargets"
        ]
        Condition = {
          StringEquals = { "events:ManagedBy" = "elasticfilesystem.amazonaws.com" }
        }
        Resource = 
["arn:aws:events:*:*:rule/DO-NOT-DELETE-S3-Files*"]
      },
      {
        Sid      = "EventBridgeRead"
        Effect   = "Allow"
        Action   = ["events:DescribeRule", "events:ListRuleNamesByTarget", "events:ListRules", "events:ListTargetsByRule"]
        Resource = ["arn:aws:events:*:*:rule/*"]
      }
    ]
  })
}

S3 Files ファイルシステム(aws_s3files_file_system

S3バケットのARNと、先ほど作成したIAMロールのARNを指定します。バージョニングと暗号化が有効になってから作成するように depends_on を設定してください。

resource "aws_s3files_file_system" "this" {
  bucket   = aws_s3_bucket.this.arn
  role_arn = aws_iam_role.s3_files_role.arn

  depends_on = [
    aws_s3_bucket_versioning.this,
    aws_s3_bucket_server_side_encryption_configuration.this,
  ]
}

S3 Files マウントターゲット(aws_s3files_mount_target

ここが最初の詰まりポイントです。

ファイルシステムを作成しただけではEC2からマウントできません。

EC2からマウントするためには、VPC内にマウントターゲットを作成する必要があります。

EFSと同様の考え方で、マウントするEC2が存在するサブネット(AZ)ごとに1つ作成します。

resource "aws_s3files_mount_target" "this" {
  count           = length(var.private_subnet_ids)
  file_system_id  = aws_s3files_file_system.this.id
  subnet_id       = var.private_subnet_ids[count.index]
  security_groups = [var.s3_files_mount_target_sg_id]
}

マウントターゲットのエンドポイントは <AZ ID>.fs-<ID>.s3files.<リージョン>.on.aws という形式になります。

EC2からこのエンドポイントへのNFS通信(ポート2049)が通っている必要があります。

セキュリティグループ

ここが2つ目の詰まりポイントです。

IAMが正しく設定されていても、セキュリティグループ(以下、SG)でポート2049が開いていないとマウント時にタイムアウトします。

必要なのはマウントターゲット側のインバウンドだけではありません。公式ドキュメントでは、EC2側(マウント元)にもマウントターゲットSGへのポート2049アウトバウンドの許可が要件として明記されています。

セキュリティグループ

ルール

プロトコル

ポート

送信元/送信先

EC2インスタンス

アウトバウンド

TCP

2049

マウントターゲットのSG

マウントターゲット

インバウンド

TCP

2049

EC2インスタンスのSG

マウントターゲット用のセキュリティグループを作成し、EC2のセキュリティグループからのポート2049(TCP)インバウンドを許可します。

resource "aws_security_group" "s3_files_mount_target" {
  name   = "${var.project_name}-s3-files-mount-target-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port       = 2049
    to_port         = 2049
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

なお、今回のEC2側セキュリティグループ(aws_security_group.app)はegressを 0.0.0.0/0 で全許可しているため、ポート2049のアウトバウンドも暗黙的に許可されています。
最小権限の構成にする場合は、EC2側にもマウントターゲットSGへの2049番ポートを明示したアウトバウンドルールを追加してください。

EC2用IAMロール

ここが3つ目の詰まりポイントです。
IAMはEC2側とS3 Files側の2方向で設定が必要です。またEC2側には AmazonS3FilesClientFullAccess マネージドポリシーのアタッチが必要です。

resource "aws_iam_role" "app_role" {
  name = "${var.project_name}-app-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "ec2.amazonaws.com" }
      Action    = "sts:AssumeRole"
    }]
  })
}

resource "aws_iam_role_policy_attachment" "s3_files_access" {
  role       = aws_iam_role.app_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FilesClientFullAccess"
}

resource "aws_iam_role_policy_attachment" "ssm" {
  role       = aws_iam_role.app_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

さらに、公式ドキュメントではEC2ロールに対して、紐づくS3バケットを直接読み取るためのインラインポリシーもあわせて推奨されています。

これは「Intelligent read routing」と呼ばれる読み取り最適化のための追加ポリシーです。無くてもマウント・アクセス自体は可能ですが、読み取り性能が最適化されません。

resource "aws_iam_role_policy" "s3_read_policy" {
  name = "${var.project_name}-s3-read-policy"
  role = aws_iam_role.app_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid      = "S3ObjectReadAccess"
        Effect   = "Allow"
        Action   = ["s3:GetObject", "s3:GetObjectVersion"]
        Resource = "${aws_s3_bucket.this.arn}/*"
      },
      {
        Sid      = "S3BucketListAccess"
        Effect   = "Allow"
        Action   = "s3:ListBucket"
        Resource = aws_s3_bucket.this.arn
      }
    ]
  })
}

EC2のuser-data(マウント設定)

S3 Filesのマウントには amazon-efs-utils を使用します。S3 Filesの利用には amazon-efs-utils バージョン3.0.0以上が必要です。
AL2023の最新パッケージであれば問題ありませんが、古いAMIを使う場合はバージョンを確認してください。
今回は/etc/fstab にファイルシステムIDを記載しておくことで、EC2起動時に自動マウントされます。

resource "aws_instance" "app" {
  count                = 2
  ami                  = data.aws_ami.al2023.id
  instance_type        = "t3.micro"
  iam_instance_profile = aws_iam_instance_profile.app_profile.name
  subnet_id            = var.private_subnet_ids[count.index]
  vpc_security_group_ids = [var.app_sg_id]

  user_data = <<-EOT
    #!/bin/bash
    yum -y install amazon-efs-utils
    mkdir -p /mnt/s3files
    echo "${var.s3_file_system_id}:/ /mnt/s3files s3files _netdev 0 0" >> /etc/fstab
    mount -a
  EOT
}

ファイルシステムIDは aws_s3files_file_system リソースの出力から module.storage.s3_file_system_id として渡します。

詰まりポイントまとめ

マウントターゲットがないと名前解決エラーになる

Failed to resolve 
"apne1-az4.fs-xxxxxx.s3files.ap-northeast-1.on.aws"
- ensure that the VPC has an S3Files mount target for this file system ID.

ファイルシステムだけ作ってマウントするとこのエラーが出ます。マウントターゲットを作成してから再試行してください。

セキュリティグループのポート2049を忘れずに

マウントターゲットへのインバウンドでポート2049(TCP / NFS)を許可しないと、マウント時にタイムアウトします。IAMが正しくても通信が届かなければマウントできません。

公式ドキュメントではEC2側のアウトバウンドも要件として明記されているため、最小権限で構成する場合はEC2側のセキュリティグループにも2049番ポートのアウトバウンド許可が必要です(今回の構成ではEC2側のegressを全許可しているため、結果的に通信は通っています)。

IAMは「EC2 → S3 Files」と「S3 Files → S3」の2方向

どちらか一方が欠けてもマウントやファイルアクセスが失敗します。

方向

ポリシー

アタッチ先

EC2 → S3 Files

AmazonS3FilesClientFullAccess

EC2のIAMロール

EC2 → S3(読み取り最適化、任意)

カスタムポリシー(s3:GetObject等)

EC2のIAMロール

S3 Files → S3

カスタムポリシー(S3操作 + EventBridge)

S3 Files用IAMロール

まとめ

S3 Filesをゼロから構築する場合、最低限必要なリソースと注意点は以下のとおりです。

リソース

注意点

aws_s3_bucket

バージョニング・暗号化を先に有効化する

aws_s3files_file_system

depends_on でバージョニング・暗号化リソースを指定

aws_s3files_mount_target

AZごとに1つ作成する

IAMロール(S3 Files用)

Principal は elasticfilesystem.amazonaws.com

IAMロール(EC2用)

AmazonS3FilesClientFullAccess をアタッチ(読み取り最適化にはs3:GetObject等のインラインポリシーも)

セキュリティグループ

マウントターゲットへのインバウンドとEC2側のアウトバウンドの両方でポート2049(TCP)を許可

公式ドキュメントにはリソース単体の仕様は記載されていますが、構成全体の繋がりや順序依存関係は試してみないと分かりにくいポイントが多く、今回の検証を通じて解像度が上がりました。

S3 Filesを利用してみたいと考えている方へ、本記事が構築の参考になれば幸いです。

この記事が役に立ったと思ったら、
ぜひ「いいね」とシェアをお願いします!

リンクをコピー
リンクをコピーしました
Xでシェアするfacebookでシェアする

この記事を書いた人

ykatsuno
ykatsuno
2020年に大手SIerに新卒入社。AI精度改善やWebアプリ開発/運用などに従事。2023年にメンバーズに入社後はクライアント先でSREとしてプロダクトの信頼性向上に向けた取り組みを行っている。
詳しく見る
ページトップへ戻る