1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudFormation個人メモ:VPC、6つのサブネット、IGW、RT、NAT、ALB、SG、ACM、ECS、RDS、CloudFront、S3のコード化

Last updated at Posted at 2025-01-25

はじめに

まず、大前提として、ここから先は現在キャッチアップしているCloudFormationを使いながら、さまざまなコードを自分用に書いていきます。

※補足事項
そのため、内容は完全に自分向けとなっています。

また、このブログ投稿サイトのプラットフォームについても、自分自身の備忘録としてメモを残す場として活用していきたいと考えています。このプラットフォーム上に記録を積み重ねていく予定です。

あらかじめ、その点をご理解いただければ幸いです。

今回作成する予定のAWS構成図について

詳細は省略しますが、今回は受講しているITスクールで、以下のようなAWSインフラ構成をコード化しています。

Screenshot 2025-01-25 at 1.44.57.png

Terraformによる実装も完了しており、以下の記事でコードの全体像をご紹介しています。

結論:先に成果物のご紹介

お名前.comで取得したドメイン名を使用してアクセスしたところ、以下のようにブラウザで正常に表示されることを確認しています。

Screenshot 2025-01-25 at 15.41.25.png

また、今回作成したCloudFrontのディストリビューションドメイン名でも、以下のように正常に表示されることを確認しています。

Screenshot 2025-01-25 at 15.42.42.png

今回追加で実装したRDSインスタンスについても、マネジメントコンソール上で正しく作成されていることを確認しました。

Screenshot 2025-01-25 at 15.43.18.png

実際にコードで行っていること

  • VPC(仮想プライベートクラウド)の作成

    • IPアドレス範囲(CIDRブロック)を「10.0.0.0/16」に設定。
    • DNSサポートとDNSホスト名を有効化。
  • サブネットの作成

    • パブリックサブネット
      • 2つのサブネットを作成(10.0.0.0/2010.0.16.0/20)。
      • 各サブネットを異なるアベイラビリティゾーン(ap-northeast-1aap-northeast-1c)に配置。
    • プライベートサブネット
      • 4つのプライベートサブネットを作成(例:10.0.128.0/20 など)。
      • アベイラビリティゾーンごとに分けて配置。
  • インターネットゲートウェイとNATゲートウェイの作成

    • 外部インターネット通信を可能にするインターネットゲートウェイを作成。
    • プライベートサブネットからインターネットアクセスを可能にするNATゲートウェイを設定。
  • ルートテーブルとルート設定

    • 各サブネットに対応するルートテーブルを作成。
    • パブリックサブネット: インターネットゲートウェイへのルートを設定。
    • プライベートサブネット: NATゲートウェイを経由するルートを設定。
  • セキュリティグループの設定

    • ECS用: アプリケーション用セキュリティグループを作成。
    • ALB用: HTTPS通信(443ポート)を許可。
    • RDS用: データベースアクセス(3306ポート)を許可。
  • ECSクラスターとサービスの作成

    • Fargateを使用したECSクラスターを作成。
    • タスク定義、コンテナ設定、ネットワーク構成を含むサービスを設定。
  • アプリケーションロードバランサー(ALB)の作成

    • HTTPSリスナー(443ポート)を設定。
    • ターゲットグループと接続。
  • RDS(データベース)インスタンスの作成

    • MySQLエンジンを使用したRDSインスタンスをプライベートサブネットにデプロイ。
    • 無料利用枠に対応した構成(db.t4g.microインスタンス、20GBストレージ)。
  • S3バケットとCloudFrontの設定

    • S3バケット: Reactアプリを格納予定の非公開バケットを作成。
    • CloudFront: S3バケットをオリジンとするディストリビューションを設定。
    • ウェブホスティング用にindex.htmlをアップロードし、HTTPS経由でアクセス可能に。

注意点:HTMLファイルのアップロードは手動で対応

作成したテンプレートをCloudFormationで使用しようとしたところ、以下のエラーが発生しました。

Template format error: Unrecognized resource types: [AWS::S3::Object]

調べてみたところ、どうやらCloudFormationではAWS::S3::Objectリソースタイプがサポートされていないみたいです…

正直、これが本当なのか少し自信がないのですが、少なくとも自分の調査では直接アップロードする方法は見つかりませんでした。

そのため、以下のような方法で対応するしかないのかなと思っています。もし他に良い方法があれば教えてほしいところです…。

今回は、index.htmlをS3バケットに手動でアップロードし、それをCloudFront経由で配信する形で対応しました。

アップロード後の画面

Screenshot 2025-01-25 at 7.37.51.png

事前準備と補足事項

1. お名前.comで購入したドメインを使用してSSL証明書を取得する方法

AWS ACMとRoute53を利用してSSL証明書を取得する手順については、以下の記事をご参照ください:

2. ALBのAレコードをRoute53のホストゾーンに追加する方法

CloudFormationで自動デプロイ後、ALBのAレコードをRoute53に追加する手順については、以下の記事をご参照ください:

作ったコードについて

test.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFormation template for creating a VPC, subnets, NAT gateway, and related resources.

# 変数の定義
Parameters:
  ACMCertificate:
    Type: String
    Default: "arn:aws:acm:ap-northeast-1:881490128743:certificate/83d4b57c-6b4f-4658-8fec-b826de14c5a9"
    Description: "ARN of the ACM Certificate"

Resources:
  # VPCの作成
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.0.0.0/16"
      InstanceTenancy: "default"
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-vpc"

  # パブリックサブネット1の作成
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.0.0/20"
      AvailabilityZone: "ap-northeast-1a"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-public1-ap-northeast-1a"

  # パブリックサブネット2の作成
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.16.0/20"
      AvailabilityZone: "ap-northeast-1c"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-public2-ap-northeast-1c"

  # プライベートサブネット1の作成
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.128.0/20"
      AvailabilityZone: "ap-northeast-1a"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-private1-ap-northeast-1a"

  # プライベートサブネット2の作成
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.144.0/20"
      AvailabilityZone: "ap-northeast-1c"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-private2-ap-northeast-1c"

  # プライベートサブネット3の作成
  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.160.0/20"
      AvailabilityZone: "ap-northeast-1a"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-private3-ap-northeast-1a"

  # プライベートサブネット4の作成
  PrivateSubnet4:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.176.0/20"
      AvailabilityZone: "ap-northeast-1c"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-subnet-private4-ap-northeast-1c"

  # インターネットゲートウェイの作成
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-igw"

  # VPCへのインターネットゲートウェイのアタッチ
  AttachInternetGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # パブリックルートテーブルの作成
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-rtb-public"

  # パブリックルートの設定
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

  # パブリックサブネット1をルートテーブルに関連付け
  PublicRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  # パブリックサブネット2をルートテーブルに関連付け
  PublicRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  # NATゲートウェイ用のElastic IPの作成
  NatEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: "vpc"
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-eip-ap-northeast-1a"

  # NATゲートウェイの作成
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet1
      AllocationId: !GetAtt NatEIP.AllocationId
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-nat-public1-ap-northeast-1a"

  # プライベートルートテーブル1の作成
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-rtb-private1-ap-northeast-1a"

  # プライベートルート1の設定
  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway

  # プライベートサブネット1をルートテーブルに関連付け
  PrivateRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable1

  # プライベートルートテーブル2の作成
  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-rtb-private2-ap-northeast-1c"

  # プライベートルート2の設定
  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway

  # プライベートサブネット2をルートテーブルに関連付け
  PrivateRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable2

  # プライベートルートテーブル3の作成
  PrivateRouteTable3:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-rtb-private3-ap-northeast-1a"

  # プライベートルート3の設定
  PrivateRoute3:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable3
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway

  # プライベートサブネット3をルートテーブルに関連付け
  PrivateRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet3
      RouteTableId: !Ref PrivateRouteTable3

  # プライベートルートテーブル4の作成
  PrivateRouteTable4:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: "CloudFormation-vpc-rtb-private4-ap-northeast-1c"

  # プライベートルート4の設定
  PrivateRoute4:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable4
      DestinationCidrBlock: "0.0.0.0/0"
      NatGatewayId: !Ref NatGateway

  # プライベートサブネット4をルートテーブルに関連付け
  PrivateRouteTableAssociation4:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet4
      RouteTableId: !Ref PrivateRouteTable4

  # ECS用セキュリティグループ
  ECSGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Security group for ECS instances"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: 1323
          ToPort: 1323
          SourceSecurityGroupId: !GetAtt PublicSG.GroupId
      SecurityGroupEgress:
        - IpProtocol: "-1"
          FromPort: 0
          ToPort: 0
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: "CloudFormation-private-instance-sg"

  # ECSクラスターの作成
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: "my-ecs-cluster"

  # ECSタスク実行ロールの作成
  ECSExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "ecs-task-execution-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "ecs-tasks.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "ecs-task-execution-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "ecr:GetAuthorizationToken"
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:BatchGetImage"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "s3:GetObject"
                Resource: "*"

  # ECSタスク定義の作成
  ECSTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: "my-app-task"
      NetworkMode: "awsvpc"
      RequiresCompatibilities:
        - "FARGATE"
      Cpu: "256"
      Memory: "512"
      ExecutionRoleArn: !GetAtt ECSExecutionRole.Arn
      ContainerDefinitions:
        - Name: "my-app-repo"
          Image: "881490128743.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-repo"
          PortMappings:
            - ContainerPort: 1323
              HostPort: 1323
              Protocol: "tcp"

  # ECSサービスの作成
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: "my-ecs-service"
      Cluster: !Ref ECSCluster
      TaskDefinition: !Ref ECSTaskDefinition
      DesiredCount: 1
      LaunchType: "FARGATE"
      NetworkConfiguration:
        AwsvpcConfiguration:
          Subnets:
            - !Ref PublicSubnet1
          SecurityGroups:
            - !GetAtt ECSGroup.GroupId
          AssignPublicIp: "ENABLED"
      LoadBalancers:
        - TargetGroupArn: !Ref MainTargetGroup
          ContainerName: "my-app-repo"
          ContainerPort: 1323
    DependsOn:
      - HttpsListener # HttpListenerではなく、HttpsListenerを指定

  # パブリック用セキュリティグループ作成
  PublicSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Public security group for ALB"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: 443
          ToPort: 443
          CidrIp: "0.0.0.0/0"
      SecurityGroupEgress:
        - IpProtocol: "-1"
          FromPort: 0
          ToPort: 0
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: "CloudFormation-public-sg"

  # アプリケーションロードバランサー(ALB)作成
  MainALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: "cloudformation-main-alb"
      Scheme: "internet-facing"
      Type: "application"
      SecurityGroups:
        - !GetAtt PublicSG.GroupId
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: "cloudformation-main-alb"

  # ターゲットグループ作成
  MainTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: "cloudformation-main-target-group"
      Port: 1323
      Protocol: "HTTP"
      VpcId: !Ref VPC
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: "/"
      HealthCheckProtocol: "HTTP"
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 3
      TargetType: "ip" # ターゲットタイプをIPに設定
      Tags:
        - Key: Name
          Value: "cloudformation-main-target-group"

  # ALBのリスナー作成 (HTTPS)
  HttpsListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref MainALB
      Port: 443
      Protocol: "HTTPS"
      Certificates:
        - CertificateArn: !Ref ACMCertificate
      DefaultActions:
        - Type: "forward"
          TargetGroupArn: !Ref MainTargetGroup

  # RDS用セキュリティグループ
  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: "Security group for RDS"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: 3306
          ToPort: 3306
          CidrIp: "10.0.0.0/16"
      SecurityGroupEgress:
        - IpProtocol: "-1"
          FromPort: 0
          ToPort: 0
          CidrIp: "0.0.0.0/0"
      Tags:
        - Key: Name
          Value: "CloudFormation-rds-sg"

  # RDS用サブネットグループ
  RDSSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: "Subnet group for RDS"
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
      Tags:
        - Key: Name
          Value: "CloudFormation-rds-subnet-group"

  # RDSインスタンス
  RDSInstance:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AllocatedStorage: "20" # 無料利用枠に合わせたストレージ
      StorageType: "gp2"
      Engine: "mysql"
      EngineVersion: "8.0.39"
      DBInstanceClass: "db.t4g.micro" # Graviton2プロセッサを使用したインスタンス
      MasterUsername: "admin"
      MasterUserPassword: "mypassword123"
      DBParameterGroupName: "default.mysql8.0"
      DBSubnetGroupName: !Ref RDSSubnetGroup
      VPCSecurityGroups:
        - !GetAtt RDSSecurityGroup.GroupId
      PubliclyAccessible: false
      MultiAZ: false
      Tags:
        - Key: Name
          Value: "CloudFormation-rds-instance"
      DeletionProtection: false

  # S3バケット作成
  S3Bucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: "my-cloudfront-bucket-tokyo" # S3バケット名を指定
      AccessControl: "Private" # バケットのアクセス制御をプライベートに設定
      Tags:
        - Key: "Name"
          Value: "MyS3Bucket" # バケットの名前タグ
        - Key: "Environment"
          Value: "Production" # 環境情報のタグ(本番用)
      WebsiteConfiguration:
        IndexDocument: "index.html" # デフォルトのインデックスドキュメント
        ErrorDocument: "error.html" # エラーページの設定

  S3BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref S3Bucket # バケットポリシーを適用するS3バケット
      PolicyDocument:
        Version: "2012-10-17" # ポリシーのバージョン
        Statement:
          - Effect: "Allow"
            Principal:
              CanonicalUser: !GetAtt CloudFrontOAI.S3CanonicalUserId # CloudFront OAIを許可
            Action: "s3:GetObject" # オブジェクトの読み取り権限
            Resource: !Sub "${S3Bucket.Arn}/*" # バケット内のすべてのオブジェクトが対象

  CloudFrontOAI:
    Type: "AWS::CloudFront::CloudFrontOriginAccessIdentity"
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: "OAI for S3 bucket" # S3バケット用のCloudFront OAI設定

  CloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        Enabled: true # CloudFrontディストリビューションを有効化
        Comment: "CloudFront Distribution for S3 bucket in Tokyo region" # ディストリビューションの説明コメント
        DefaultRootObject: "index.html" # デフォルトのルートオブジェクト
        Origins:
          - Id: "S3-my-cloudfront-bucket" # オリジンのID
            DomainName: !GetAtt S3Bucket.RegionalDomainName # S3バケットのドメイン名
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOAI}" # OAIを使用してセキュアにアクセス
        DefaultCacheBehavior:
          TargetOriginId: "S3-my-cloudfront-bucket" # ターゲットオリジンのID
          ViewerProtocolPolicy: "redirect-to-https" # HTTPリクエストをHTTPSにリダイレクト
          AllowedMethods:
            - "GET"
            - "HEAD" # 許可されるHTTPメソッド
          CachedMethods:
            - "GET"
            - "HEAD" # キャッシュ可能なHTTPメソッド
          ForwardedValues:
            QueryString: false # クエリ文字列を転送しない
            Cookies:
              Forward: "none" # クッキーを転送しない
        PriceClass: "PriceClass_100" # 利用可能なリージョン(最も安価なプラン)
        Restrictions:
          GeoRestriction:
            RestrictionType: "none" # 地域制限なし
        ViewerCertificate:
          CloudFrontDefaultCertificate: true # デフォルトのCloudFront証明書を使用
      Tags:
        - Key: "Name"
          Value: "MyCloudFrontDistribution" # ディストリビューションの名前タグ
        - Key: "Environment"
          Value: "Production" # 環境情報のタグ(本番用)

まとめ

この記事は、自分用の備忘録としてまとめたものです。その点をご理解いただけると幸いです。

もし、この記事の内容が少しでも参考になれば嬉しく思います。

今後も同様の内容を継続して投稿していきますので、温かく見守っていただけるとありがたいです。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?