📘

Amazon S3ファイルゲートウェイをTerraformで作ってみる

2025/01/04に公開

はじめに

業務上LinuxとWindowsのインスタンスでファイルの共有を簡単にする必要がありました。
元々、頻繁にファイルの共有しないつもりだったのでS3でファイルを管理していました。
そのため、S3をEC2にマウントする方向性で試してみました。

構成

architecture.drawio.png

元々 Private Subnet のみで構成しようしました。が、BastionサーバーにSSMマネージャーで接続したかったので、VPC外のAWSサービス接続のためにNATとInternetGatewayを用意しています。

VPC 設定

今回は、Subnet を Bastion と StorageGateway サーバーで分けているので、明示的にサブネットを関連づけています。

network.tf
### Route Table ###

resource "aws_route_table" "private_table" {
  vpc_id = aws_vpc.main.id
}

resource "aws_route_table_association" "private_association_a_1" {
  subnet_id      = aws_subnet.pri_a_1.id
  route_table_id = aws_route_table.private_table.id
}

resource "aws_route_table_association" "private_association_a_2" {
  subnet_id      = aws_subnet.pri_a_2.id
  route_table_id = aws_route_table.private_table.id
}

Bastion サーバー設定

SSMマネージャでの接続のために IAM Role を作成しています。

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "main" {
  name               = "${var.name}-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_role_policy_attachment" "main" {
  role       = aws_iam_role.main.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "main" {
  name = "${var.name}-profile"
  role = aws_iam_role.main.name
}

Amazon Linux 2023 の最新のAMIをパラメータストアから取得してインスタンスを作成します。
IAM Roleを指定する以外、特段変わった指定はありません。

bastion.tf
data "aws_ssm_parameter" "amzn_linux" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64" # x86_64
}

resource "aws_security_group" "main" {
  name   = "${var.name}-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "All traffic is denied"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "All traffic is allowed"
  }
}

resource "aws_instance" "bastion" {
  ami                  = data.aws_ssm_parameter.amzn_linux.insecure_value
  instance_type        = "t3.micro"
  subnet_id            = var.subnet_id
  iam_instance_profile = aws_iam_instance_profile.main.name
  vpc_security_group_ids = [
    aws_security_group.main.id
  ]
  root_block_device {
    volume_type           = "gp3"
    volume_size           = 8
    delete_on_termination = true
  }
}

Storage Gateway サーバーの設定

今回はNFSでのファイル共有を試すため、セキュリティグループの設定は以下とします。
80 ポートはこの後のアクティベーションキーの設定のために必要です。
NFS 向けのポートとして、111、2049、20048のポートのアクセスを可能にしておきます。
SMB での利用の場合は、139、445のポートを指定してください。

resource "aws_security_group" "main" {
  name   = var.name
  vpc_id = var.vpc_id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    description = "internal for s3-filegateway"
    cidr_blocks = [var.vpc_cidr]
  }
  ingress {
    from_port   = 111
    to_port     = 111
    protocol    = "tcp"
    description = "internal for nfs111"
    cidr_blocks = [var.vpc_cidr]
  }
  ingress {
    from_port   = 2049
    to_port     = 2049
    protocol    = "tcp"
    description = "internal for nfs2049"
    cidr_blocks = [var.vpc_cidr]
  }
  ingress {
    from_port   = 20048
    to_port     = 20048
    protocol    = "tcp"
    description = "internal for nfs20048"
    cidr_blocks = [var.vpc_cidr]
  }

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

インスタンスの必要要件として以下があります。

  • AMI : S3FileStorageGateway専用のAMIを指定
  • インスタンスタイプ : 最低でも m4.xlarge 以上が必要
  • ルートブロックサイズ : 最低 80 GB
  • キャッシュストレージサイズ : 最低 150 GB

これを踏まえて、インスタンスの設定は以下とします。

storage_gateway_instance.tf
# Storage Gateway Instance
data "aws_ssm_parameter" "s3fg_ami" {
  name = "/aws/service/storagegateway/ami/FILE_S3/latest"
}

# インスタンス設定
resource "aws_instance" "default" {
  ami           = data.aws_ssm_parameter.s3fg_ami.insecure_value
  instance_type = "m4.xlarge" # 大体 xlarge 以上のインスタンスが必要になる
  key_name      = aws_key_pair.main.key_name
  subnet_id     = var.subnet_id
  vpc_security_group_ids = [
    aws_security_group.main.id
  ]
  root_block_device {
    volume_type           = "gp3"
    volume_size           = 80 # 最低 80GB必要
    delete_on_termination = true
  }
}

# EBS追加ボリューム。Cacheストレージはサイズ変更してはならず、アタッチで追加しなければならない。
resource "aws_ebs_volume" "sdf" {
  availability_zone = aws_instance.default.availability_zone
  # ボリュームサイズ(GiB)
  size = 150 # 最低 150GB必要
}

resource "aws_volume_attachment" "sdf" {
  device_name = "/dev/sdf"
  volume_id   = aws_ebs_volume.sdf.id
  instance_id = aws_instance.default.id
}

StorageGateway有効化のためにパラメータストアを用意しておきます。

resource "aws_ssm_parameter" "activation_key" {
  name        = "/${var.name}/filegateway/activation_key"
  value       = "password"
  type        = "SecureString"
  description = "activation_key for s3 file gateway"

  lifecycle {
    ignore_changes = [value] # 上書きして変更するので無視する
  }
}

BastionサーバーにSSMで接続してアクティベーションキーを取得します。

# パブリックエンドポイントのアクティベーションキーを取得する
$ curl "http://gateway_ip_address/?activationRegion=region_code&no_redirect"
# VPC エンドポイントのアクティベーションキーを取得する
$ curl "http://gateway_ip_address/?activationRegion=region_code&vpcEndpoint=vpc_endpoint&no_redirect"

公式によると上記を実行することで取得が可能です。

$ curl "http://gateway_ip_address/?activationRegion=ap-northeast-1&no_redirect"
ACTIVATION_KEY

コンソールからでもAWS CLIからでも良いので作成済みのパラメータストアの値を更新します。

$ aws ssm put-parameter --name /s3fg-instance/filegateway/activation_key --value ACTIVATION_KEY --overwrite

アクティベーションキーは30分間有効なので、取得後は StorageGateway の設定に移ります。

Storage Gateway の設定

上記で取得した際のアクティベーションキーを引き渡し、Storage Gateway を設定します。
ゲストアクセスとして簡単にSMBの設定もしているので、SMB で利用したい場合はパスワードの設定は見直してください。

storage_gateway.tf
resource "aws_storagegateway_gateway" "main" {
  activation_key     = var.activation_key
  gateway_name       = var.name
  gateway_timezone   = "GMT"
  gateway_type       = "FILE_S3"
  smb_guest_password = "password"
  lifecycle {
    ignore_changes = [smb_guest_password]
  }
}

# ディスクIDの取得のために aws_storage gateway_local_disk を使う
# 「disk_node」を以下の様に記載する
data "aws_storagegateway_local_disk" "main" {
  gateway_arn = aws_storagegateway_gateway.main.arn
  disk_node   = "/dev/sdf"
}

resource "aws_storagegateway_cache" "main" {
  gateway_arn = aws_storagegateway_gateway.main.arn
  # aws_storage gateway_local_diskからディスクIDを設定
  disk_id = data.aws_storagegateway_local_disk.main.id
}

NFSファイル共有に対してIAM Roleを用意します。
Storage GatewayがS3へのアクセスできるように設定しています。

file_share.tf
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["storagegateway.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "main" {
  name               = "file-share-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_role_policy_attachment" "s3" {
  role       = aws_iam_role.main.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

resource "aws_storagegateway_nfs_file_share" "nfs" {
  client_list  = ["0.0.0.0/0"]
  gateway_arn  = aws_storagegateway_gateway.main.arn
  location_arn = "s3 bucket の ARN"
  role_arn     = aws_iam_role.main.arn
}

動作確認

Bastion サーバーに SSM セッションマネージャーで接続してマウントします。

$ sudo mkdir /mnt/shared-bucket
# IPアドレスはストレージゲートウェイのインスタンスのプライベートアドレスを指定
$ sudo mount -t nfs -o nolock,hard 10.0.1.xxx:/s3fg-shared-bucket /mnt/shared-bucket

以下のようにファイルを作ると
create_new_file.png
きちんと S3 にアップロードされます。注意としてはキャッシュは EC2 インスタンス側に持つので、 S3 からアップロードしても反映されません。
s3_bucket.png

WindowsServer で利用する場合

NFS でそのままマウントしたい場合は、PowerShell で NFS のクライアントをインストールしてください。

$ Install-WindowsFeature NFS-Client

ファイル共有で NFS と SMB の両方を同一のバケットを指定すれば良さそうとも思うのですが、同一のバケットを指定しても異なるファイル共有ではうまく扱えません。
なので、 Linux と Windows の EC2 インスタンスで同じファイルを共有したいのであれば、プロトコルを統一し、1つのファイル共有をマウントする必要があります。

おわりに

S3 を簡単にマウントできるのかなと思い試したのですが、設定も多くて面倒でした。
加えて、 xlarge 以上の EC2 インスタンスを立てておかないといけないので、コスト面も安いとは言えないかなと思います。
次は Amazon FSx の方を試してみたいと思います。
https://aws.amazon.com/jp/fsx/

参考

https://docs.aws.amazon.com/ja_jp/storagegateway/latest/tgw/get-activation-key.html

https://docs.aws.amazon.com/filegateway/latest/files3/Performance.html#performance-multiple-file-shares

https://docs.aws.amazon.com/ja_jp/filegateway/latest/files3/Resource_Ports.html

https://dev.classmethod.jp/articles/create-s3-storage-gateway-using-terraform/

https://dev.classmethod.jp/articles/retrieve-latest-ami-id-of-amazonlinux-2023/

Discussion