自作RailsアプリをAWS上に開発する　〜CloudFormationを用いた環境構築〜

# 1. 概要
AWSサービスの一部にCloudFormationを用いて自作のRailsを公開するための環境を構築する。
長期間利用しない場合はCloudFormationスタックを削除することで、コストを抑える。
ECSのDokcerコンテナ上でRailsアプリを立ち上げ、RDS PostgreSQL上のデータベースと接続してアプリを稼働させる。

# 2. 使用するAWSサービス
RailsアプリをAWS上で稼働させるために、以下のAWSサービスを使用する。
なお、CloudFormationではなく手動作成したほうが運用性が高いもの、検証過程で作成済みであり、テンプレート化する必要がないものについてはテンプレート化の対象外としている。

- VPC（CloudFormationで定義）
- RDS（CloudFormationで定義）
- ALB（CloudFormationで定義）
- CloudFront（CloudFormationで定義）
- ECS
- Route53
- ACM
- IAM
- SSM Parameter Store

# 3. 構成図
![alcohol_構成図 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/818678/b8fccf70-d71a-a7b0-d019-ec64aa81fd37.png)

# 4. 構築方法
- 大規模なシステムでは、AWSサービスの種別、ライフサイクル、依存度、開発部署、等様々な観点でCloudFormationテンプレートを分割したほうが運用保守する上で便利だが、今回構築するシステムの規模は小さいため、一つのテンプレートにまとめて定義する
- CloudFormationで構築するAWSリソースの設定情報を整理したプロンプトを入力し、GitHub Copilotに読み込ませることで、テンプレートを作成した。
詳細については以下の記事に整理している。

https://qiita.com/kyobon/items/1141c5752fa2a7e9ab5a


# 5. 構築方法
出来上がったテンプレートファイルは以下の通り。
ただしRailsアプリを稼働するためには、以下のテンプレートのみではアプリの実行環境は定義できないため、追加でECS、IAM等のサービスを手動構築する必要があるため注意。

``` yaml:app-stack.yml
AWSTemplateFormatVersion: '2010-09-09'
Mappings:
  DefaultVPCID:
    VPC:
      Id: vpc-XXXXXXXXXXXXXXXX
  DefaultIGWID:
    IGW:
      Id: igw-XXXXXXXXXXXXXXXX

Resources:
  # VPC Resources
  AlcoholPubSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      CidrBlock: 172.31.0.0/27
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: alcohol-pub-subnet-01

  AlcoholPubSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      CidrBlock: 172.31.0.32/27
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: alcohol-pub-subnet-02

  AlcoholPrvSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      CidrBlock: 172.31.0.64/27
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: alcohol-prv-subnet-01

  AlcoholPrvSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      CidrBlock: 172.31.0.96/27
      AvailabilityZone: ap-northeast-1c
      Tags:
        - Key: Name
          Value: alcohol-prv-subnet-02

  AlcoholRouteTablePub:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      Tags:
        - Key: Name
          Value: alcohol-routetbl-pub

  AlcoholRouteTablePrv:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      Tags:
        - Key: Name
          Value: alcohol-routetbl-prv

  AlcoholRoutePub:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref AlcoholRouteTablePub
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !FindInMap [DefaultIGWID, IGW, Id]

  AlcoholSubnetRouteTableAssociationPub01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref AlcoholPubSubnet01
      RouteTableId: !Ref AlcoholRouteTablePub

  AlcoholSubnetRouteTableAssociationPub02:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref AlcoholPubSubnet02
      RouteTableId: !Ref AlcoholRouteTablePub

  AlcoholSubnetRouteTableAssociationPrv01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref AlcoholPrvSubnet01
      RouteTableId: !Ref AlcoholRouteTablePrv

  AlcoholSubnetRouteTableAssociationPrv02:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref AlcoholPrvSubnet02
      RouteTableId: !Ref AlcoholRouteTablePrv

  AlcoholSGALB:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ALB security group
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      Tags:
        - Key: Name
          Value: alcohol-sg-alb

  AlcoholSGECS:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ECS security group
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      Tags:
        - Key: Name
          Value: alcohol-sg-ecs

  AlcoholSGRDS:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: RDS security group
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      Tags:
        - Key: Name
          Value: alcohol-sg-rds

  AlcoholSGALBIngressHTTP:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref AlcoholSGALB
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      CidrIp: 0.0.0.0/0

  AlcoholSGALBIngressHTTPS:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref AlcoholSGALB
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: 0.0.0.0/0

  AlcoholSGALBEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref AlcoholSGALB
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      DestinationSecurityGroupId: !Ref AlcoholSGECS

  AlcoholSGECSIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref AlcoholSGECS
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !Ref AlcoholSGALB

  AlcoholSGECSEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref AlcoholSGECS
      IpProtocol: tcp
      FromPort: 5432
      ToPort: 5432
      DestinationSecurityGroupId: !Ref AlcoholSGRDS

  AlcoholSGECSEgressHTTPS:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref AlcoholSGECS
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: 0.0.0.0/0

  AlcoholSGECSEgressSMTP:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref AlcoholSGECS
      IpProtocol: tcp
      FromPort: 587
      ToPort: 587
      CidrIp: 0.0.0.0/0

  AlcoholSGRDSIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref AlcoholSGRDS
      IpProtocol: tcp
      FromPort: 5432
      ToPort: 5432
      SourceSecurityGroupId: !Ref AlcoholSGECS

  # RDS Resources
  AlcoholRDSSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet group for RDS
      SubnetIds:
        - !Ref AlcoholPrvSubnet01
        - !Ref AlcoholPrvSubnet02
      DBSubnetGroupName: alcohol-subnet-group

  AlcoholRDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: alcohol-rds
      AllocatedStorage: 20
      DBInstanceClass: db.t3.micro
      Engine: postgres
      MasterUsername: postgres # ユーザー名は適宜変更してください
      MasterUserPassword: password # パスワードは適宜変更してください
      DBSubnetGroupName: !Ref AlcoholRDSSubnetGroup
      VPCSecurityGroups:
        - !Ref AlcoholSGRDS
      PubliclyAccessible: false
      AvailabilityZone: ap-northeast-1a
      DBName: Alcohol_production

  # ALB Resources
  AlcoholTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: alcohol-target-group
      Port: 80
      Protocol: HTTP
      VpcId: !FindInMap [DefaultVPCID, VPC, Id]
      TargetType: ip

  AlcoholALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: alcohol-alb
      Subnets:
        - !Ref AlcoholPubSubnet01
        - !Ref AlcoholPubSubnet02
      SecurityGroups:
        - !Ref AlcoholSGALB

  AlcoholALBListenerHTTP: #リスナーはECSサービス作成時/作成後に手動設定するためダミーのリスナーを設定しておく
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref AlcoholALB
      Port: 8080
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref AlcoholTargetGroup

  # CloudFront Resources
  AlcoholCloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - DomainName: alb.stop-deisui.com
            Id: alcohol-alb-origin
            CustomOriginConfig:
              HTTPPort: 80
              HTTPSPort: 443
              OriginProtocolPolicy: match-viewer
        Enabled: true
        DefaultCacheBehavior:
          TargetOriginId: alcohol-alb-origin
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
            - OPTIONS
            - PUT
            - POST
            - PATCH
            - DELETE
          CachedMethods:
            - GET
            - HEAD
          ForwardedValues:
            QueryString: false
          CachePolicyId: 83da9c7e-98b4-4e11-a168-04f0df8e2c65 #UseOriginCacheControlHeaders
          OriginRequestPolicyId: 216adef6-5c7f-47e4-b989-5492eafa07d3 #AllViewers
          ResponseHeadersPolicyId: 60669652-455b-4ae9-85a4-c4c02393f86c #SimpleCORS
        ViewerCertificate:
          AcmCertificateArn: !Sub arn:aws:acm:us-east-1:${AWS::AccountId}:certificate/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
          SslSupportMethod: sni-only
        Aliases:
          - XXXX.com
```

# 6. 結果
アプリの実行環境を構築できた。
扱ったことがないサービスを構築する場合は、初めに手動でリソースを立てて検証したほうがいいと思うが、慣れているサービスを利用するときはテンプレートで定義したほうが早い。便利！