S3 Express One Zone 用の VPC エンドポイント Gateway 型を作成する CloudFormation テンプレートの紹介

2024.01.21

CloudFormation で構築済みの VPC に S3 Express One Zone 用の Gateway 型の VPC エンドポイントを追加する機会がありました。CloudFormation で構築するテンプレートのサンプルを紹介します。

S3 Express One Zone とは

re:Invent 2023 において、Amazon S3 の新機能として、低レイテンシとハイパフォーマンスを実現する新しいストレージクラスが発表されました。このストレージクラスは、特に S3 におけるデータアクセスの速度を重視するユースケースに適しています。詳細については下記のブログをご覧ください。

S3 Express One Zone の VPC エンドポイントは別

通常の S3 用の VPC エンドポイントとは異なる VPC エンドポイントが別途必要になります。S3 Express One Zone のディレクトリバケットへの通信を VPC エンドポイント経由するには S3 Express One Zone 用の VPC エンドポイントも必要です。

検討背景

今回はゲノム解析のワークロードで Mountpoint for Amazon S3 を利用して、インプットファイルを S3 に保存しています。シーケンシャルなリードアクセスは良しとして、リード時にランダムアクセスが走るものは相性が悪かったです。

ランダムなデータアクセスリクエストを行うファイルベースのアプリケーションを S3 Standard と比較して最大で 6 倍高速化できます。
Mountpoint for Amazon S3 が S3 Express One Zone ストレージクラスのサポートを開始

そこで S3 Express One Zone で実用的な速度で運用可能か試す準備として EC2 から S3 Express One Zone の通信経路を VPC エンドポイント経由にして経路を最適化します。なにより EC2 と S3 間の通信コストがかからなくなるのは大きいです。

VPC エンドポイントを作成するテンプレート

CloudFormation で S3 Express One Zone 用の VPC エンドポイント Gateway 型を作成するには以下の様に記述します。

  # S3 Express Gateway Type
  GatewayS3ExpressEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds: # ルートテーブルに紐づけが必要
        - !Ref PublicRouteTable1
        - !Ref PrivateRouteTable1
        - !Ref IsolatedRouteTable1
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3express" # ここを S3 Expreess 指定する
      VpcEndpointType: Gateway
      VpcId: !Ref VPC # VPC の ID を指定

下記は通常の S3 用の VPC エンドポイント Gateway 型の記述です。差分はServiceName:の箇所だけです。

  # S3 Gateway Type
  GatewayS3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PublicRouteTable1
        - !Ref PrivateRouteTable1
        - !Ref IsolatedRouteTable1
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3" # ここが S3 指定になってる違いがある
      VpcEndpointType: Gateway
      VpcId: !Ref VPC

VPC 作成テンプレート全文

3 AZ 構成の VPC に S3 と S3 Exreppss One Zone の 2 つ Gateway 型の VPC エンドポイントをアタッチした構成 + α のサンプルテンプレートを参考までに記載します。

折りたたみ
---
AWSTemplateFormatVersion: "2010-09-09"
Description: 3AZ Network 3 layers and VPC Endpoint with switching single Nat Gateway

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: NAT Gateway Setting
        Parameters:
          - EnableNatGateway

Parameters:
  EnableNatGateway:
    Description: Enable NAT Gateway
    Type: String
    Default: false
    AllowedValues: [true, false]

Mappings:
  Constant:
    EnvName:
      ProjectName: hpc
      Environment: dev
  Network:
    Cidrs:
      VPCCidr: 10.0.0.0/16
      PublicSubnetCidr1: 10.0.1.0/24
      PublicSubnetCidr2: 10.0.2.0/24
      PublicSubnetCidr3: 10.0.3.0/24
      PrivateSubnetCidr1: 10.0.17.0/24
      PrivateSubnetCidr2: 10.0.18.0/24
      PrivateSubnetCidr3: 10.0.19.0/24
      IsolatedSubnetCidr1: 10.0.33.0/24
      IsolatedSubnetCidr2: 10.0.34.0/24
      IsolatedSubnetCidr3: 10.0.35.0/24

Conditions:
  EnableNatGateway: !Equals [true, !Ref EnableNatGateway]

Resources:
  # ----------------------------------------------------------------------------------------
  # VPC
  # ----------------------------------------------------------------------------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !FindInMap [ Network, Cidrs, VPCCidr]
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub
            - "${ProjectName}-${Environment}-vpc"
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]

  # ----------------------------------------------------------------------------------------
  # Internet Gateway
  # ----------------------------------------------------------------------------------------
  # Create InternetGateway & VPC Attach
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-igw
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ----------------------------------------------------------------------------------------
  # NAT Gateway
  # ----------------------------------------------------------------------------------------
  # Create NatGateway
  NatGateway1:
    Type: AWS::EC2::NatGateway
    Condition: EnableNatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP1.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-natgw1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  NatGatewayEIP1:
    Type: AWS::EC2::EIP
    Condition: EnableNatGateway
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-natgw-eip1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]

  # ----------------------------------------------------------------------------------------
  # Route Table
  # ----------------------------------------------------------------------------------------
  # Create Public RouteTable & Setting Routing
  PublicRouteTable1:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-public-rtb1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  PublicRoute1:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  # Create Private RouteTable & Setting Routing
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-private-rtb1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  PrivateRouteNatGW1:
    Type: AWS::EC2::Route
    Condition: EnableNatGateway
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1
  # Create Isolated RouteTable & Setting Routing
  IsolatedRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-isolated-rtb1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]

  # ----------------------------------------------------------------------------------------
  # Public Subnet
  # ----------------------------------------------------------------------------------------
  # Public 1
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PublicSubnetCidr1]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-public-subnet1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  PublicSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable1
  # Public 2
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PublicSubnetCidr2]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-public-subnet2
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  PublicSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable1
  # Public 3
  PublicSubnet3:
    Type: AWS::EC2::Subnet
    DependsOn: AttachGateway
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [2, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PublicSubnetCidr3]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-public-subnet3
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  PublicSubnetRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet3
      RouteTableId: !Ref PublicRouteTable1

  # ----------------------------------------------------------------------------------------
  # Private Subnet
  # ----------------------------------------------------------------------------------------
  # Private 1
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PrivateSubnetCidr1]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-private-subnet1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  PrivateSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable1
  # Private 2
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PrivateSubnetCidr2]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-private-subnet2
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  PrivateSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable1
  # Private 3
  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [2, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, PrivateSubnetCidr3]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-private-subnet3
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  PrivateSubnetRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet3
      RouteTableId: !Ref PrivateRouteTable1

  # ----------------------------------------------------------------------------------------
  # Isolated Subnet
  # ----------------------------------------------------------------------------------------
  # Isolated 1
  IsolatedSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, IsolatedSubnetCidr1]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-isolated-subnet1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  IsolatedSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet1
      RouteTableId: !Ref IsolatedRouteTable1
  # Isolated 2
  IsolatedSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, IsolatedSubnetCidr2]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-isolated-subnet2
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  IsolatedSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet2
      RouteTableId: !Ref IsolatedRouteTable1
  # Isolated 3
  IsolatedSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [2, !GetAZs ""]
      CidrBlock: !FindInMap [ Network, Cidrs, IsolatedSubnetCidr3]
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-isolated-subnet3
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
        - Key: Environment
          Value: !FindInMap [ Constant, EnvName, Environment]
  IsolatedSubnetRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet3
      RouteTableId: !Ref IsolatedRouteTable1

  # ----------------------------------------------------------------------------------------
  # Network ACL
  # ----------------------------------------------------------------------------------------
  # Public NACL
  PublicNetworkACL1:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-public-nacl1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  NetworkACLEntryPublicIngress1:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: false
      NetworkAclId: !Ref PublicNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100
  NetworkACLEntryPublicEgress1:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: true
      NetworkAclId: !Ref PublicNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100
  # Private NACL
  PrivateNetworkACL1:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-private-nacl1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  NetworkACLEntryPrivateIngress1:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: false
      NetworkAclId: !Ref PrivateNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100
  NetworkACLEntryPrivateEgress1:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: true
      NetworkAclId: !Ref PrivateNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100
  # Isolated NACL
  IsolatedNetworkACL1:
    Type: AWS::EC2::NetworkAcl
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub
            - ${ProjectName}-${Environment}-isolated-nacl1
            - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
              Environment: !FindInMap [ Constant, EnvName, Environment]
  NetworkACLEntryIsolatedIngress1:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: false
      NetworkAclId: !Ref IsolatedNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100
  NetworkACLEntryIsolatedEgress1:
    Type: "AWS::EC2::NetworkAclEntry"
    Properties:
      CidrBlock: "0.0.0.0/0"
      Egress: true
      NetworkAclId: !Ref IsolatedNetworkACL1
      Protocol: -1
      RuleAction: "allow"
      RuleNumber: 100

  # NetworkACL Association
  PublicNetworkACLAssocation1:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      NetworkAclId: !Ref PublicNetworkACL1
  PublicNetworkACLAssocation2:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      NetworkAclId: !Ref PublicNetworkACL1
  PublicNetworkACLAssocation3:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PublicSubnet3
      NetworkAclId: !Ref PublicNetworkACL1
  PrivateNetworkACLAssocation1:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      NetworkAclId: !Ref PrivateNetworkACL1
  PrivateNetworkACLAssocation2:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      NetworkAclId: !Ref PrivateNetworkACL1
  PrivateNetworkACLAssocation3:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet3
      NetworkAclId: !Ref PrivateNetworkACL1
  IsolatedNetworkACLAssocation1:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet1
      NetworkAclId: !Ref IsolatedNetworkACL1
  IsolatedNetworkACLAssocation2:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet2
      NetworkAclId: !Ref IsolatedNetworkACL1
  IsolatedNetworkACLAssocation3:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref IsolatedSubnet3
      NetworkAclId: !Ref IsolatedNetworkACL1

  # ----------------------------------------------------------------------------------------
  # VPC Endpoints
  # ----------------------------------------------------------------------------------------
  # S3 Gateway Type
  GatewayS3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PublicRouteTable1
        - !Ref PrivateRouteTable1
        - !Ref IsolatedRouteTable1
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcEndpointType: Gateway
      VpcId: !Ref VPC
  # S3 Express Gateway Type
  GatewayS3ExpressEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PublicRouteTable1
        - !Ref PrivateRouteTable1
        - !Ref IsolatedRouteTable1
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3express"
      VpcEndpointType: Gateway
      VpcId: !Ref VPC

  # ------------------------------------------------------------#
  # S3 Bucket for VPC Flow Logs
  # ------------------------------------------------------------#
  S3Bucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub
        - ${ProjectName}-${Environment}-vpc-flowlogs-${AWS::AccountId}
        - ProjectName: !FindInMap [ Constant, EnvName, ProjectName]
          Environment: !FindInMap [ Constant, EnvName, Environment]
      OwnershipControls:
        Rules:
          - ObjectOwnership: "BucketOwnerEnforced"
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: "AES256"
            BucketKeyEnabled: false
      LifecycleConfiguration:
        Rules:
          - Id: AbortIncompleteMultipartUpload
            AbortIncompleteMultipartUpload:
              DaysAfterInitiation: 7
            Status: "Enabled"
          - Id: CurrentVersionExpiration
            ExpirationInDays: 365
            Status: "Enabled"
          - Id: NoncurrentVersionExpiration
            NoncurrentVersionExpiration:
              NoncurrentDays: 7
            Status: "Enabled"

  # ------------------------------------------------------------#
  # VPC Flow Logs
  # ------------------------------------------------------------#
  VPCFlowLogs:
    Type: "AWS::EC2::FlowLog"
    Properties:
      LogDestinationType: s3
      LogDestination: !GetAtt S3Bucket.Arn
      ResourceType: "VPC"
      ResourceId: !Ref VPC
      TrafficType: ALL
      DestinationOptions:
        {
          "FileFormat": "plain-text",
          "HiveCompatiblePartitions": false,
          "PerHourPartition": true,
        }

# ----------------------------------------------------------------------------------------
# Exports
# ----------------------------------------------------------------------------------------
Outputs:
  ExportVPC:
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VPC
  ExportPublicSubnet1:
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet1
  ExportPublicSubnet2:
    Value: !Ref PublicSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet2
  ExportPublicSubnet3:
    Value: !Ref PublicSubnet3
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet3
  ExportPublicRoutetable1:
    Value: !Ref PublicRouteTable1
    Export:
      Name: !Sub ${AWS::StackName}-PublicRouteTable1
  ExportPrivateSubnet1:
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet1
  ExportPrivateSubnet2:
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet2
  ExportPrivateSubnet3:
    Value: !Ref PrivateSubnet3
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet3
  ExportPrivateRoutetable1:
    Value: !Ref PrivateRouteTable1
    Export:
      Name: !Sub ${AWS::StackName}-PrivateRouteTable1
  ExportIsolatedSubnet1:
    Value: !Ref IsolatedSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-IsolatedSubnet1
  ExportIsolatedSubnet2:
    Value: !Ref IsolatedSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-IsolatedSubnet2
  ExportIsolatedSubnet3:
    Value: !Ref IsolatedSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-IsolatedSubnet3
  ExportIsolatedRoutetable1:
    Value: !Ref IsolatedRouteTable1
    Export:
      Name: !Sub ${AWS::StackName}-IsolatedRouteTable1

S3 Express One Zone の IAM ポリシー

EC2 から S3 Express One Zone のディレクトリバケットへアクセスするには新たな IAM ポリシーが必要になります。詳細は以下のブログを参照ください。

おわりに

S3 Express One Zone は通常の S3 の VPC エンドポイントを別であること、S3 Express One Zone 用の VPC エンドポイントの作成 CloudFormation テンプレートの例をお客様へご案内したかったです。ですが、ちょうど良いサンプルがなかったので作成しました。最近は CloudFormation 以外で IaC のコードを書くことが多いような気もしますが、CloudFormation ユーザーの方々の参考になれば幸いです。