EC2インスタンスのVirtualHost(Apache)からサイトを分離する

2021.10.28

はじめに

ApacheのVirtualHostで複数の静的ウェブサイトや動的ウェブサイトを運用するEC2インスタンスからサイトを分離してみました。

分離前の構成図

分離前の構成は以下の通りです。

example.com site1.example.com site2.example.com 3つのウェブサイトのリクエストを処理できるようにEC2インスタンスのApacheのVirtualHostで設定されています。

<VirtualHost *:80>
  ServerName example.com
  DocumentRoot /var/www/html/
</VirtualHost>
<VirtualHost *:80>
  ServerName site1.example.com
  DocumentRoot /var/www/site1/
</VirtualHost>
<VirtualHost *:80>
  ServerName site2.example.com
  DocumentRoot /var/www/site2/
</VirtualHost>

Route53で3つのウェブサイトのDNSレコードのValueをALB DNS名とします。ApacheのVirtualHostがHostヘッダを見て振り分けしてくれるのでALBのリスナールールは不要です。
ウェブサイト分離の動きを見るために、CFnテンプレート site.yml を用意しました。ウェブサイトのコンテンツは フリーホームページ.netを使わせていただきました。(ありがとうございます)

site.yml
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  domainName:
    Type: String

  domainName1:
    Type: String

  domainName2:
    Type: String

  ec2Ami:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  env:
    Type: String

  hostedZoneId:
    Type: String

  instanceClass:
    Type: String

  instanceType:
    Type: String

  masterPassword:
    Type: String
    NoEcho: true

  sysName:
    Type: String

  vpcCidr:
    Type: String

Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: !Sub ${env}-${sysName}-alb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref AlbSG
      Subnets:
        - !Ref MyPublicSubnet1
        - !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-api
        - Key: BillingGroup
          Value: !Ref billingTag
      Type: application

  AlbAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName
      DomainValidationOptions:
        - DomainName: !Ref domainName
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName1
      DomainValidationOptions:
        - DomainName: !Ref domainName1
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm1
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName2
      DomainValidationOptions:
        - DomainName: !Ref domainName2
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm2
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Unauthorized Access
            StatusCode: "403"
          Type: fixed-response
      LoadBalancerArn: !Ref Alb
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08

  AlbListenerCertificate1:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm1
      ListenerArn: !Ref AlbListener

  AlbListenerCertificate2:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm2
      ListenerArn: !Ref AlbListener

  AlbListnerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - '*'
      ListenerArn: !Ref AlbListener
      Priority: 1

  AlbRecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName
      Type: A

  AlbRecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName1
      Type: A

  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A

  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Application load barancer.
      GroupName: !Sub ${env}-${sysName}-alb-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-alb-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC
  DbInstance1:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db1
        - Key: BillingGroup
          Value: !Ref billingTag

  DbInstance2:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db2
        - Key: BillingGroup
          Value: !Ref billingTag

  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance1
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip 
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  Instance2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance2
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-igw
        - Key: BillingGroup
          Value: !Ref billingTag

  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPrivateRouteTable

  MyPrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 10
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet1

  MyPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 11
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet2

  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPublicRouteTable

  MyPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 0
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet1

  MyPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 1
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet2

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref vpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-vpc
        - Key: BillingGroup
          Value: !Ref billingTag

  MyVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  ParameterGroupAurora:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-parametergroup
      Family: aurora-mysql5.7
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Rds Instances.
      GroupName: !Sub ${env}-${sysName}-rds-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ec2SG
          ToPort: 3306
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  SubnetGroupRds:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
      DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
      SubnetIds:
        - !Ref MyPrivateSubnet1
        - !Ref MyPrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-SubnetGroup
        - Key: BillingGroup
          Value: !Ref billingTag

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-ec2
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-tg
        - Key: BillingGroup
          Value: !Ref billingTag
      TargetType: instance
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
      VpcId: !Ref MyVPC
  clusterAurora:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
      DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
      DBSubnetGroupName: !Ref SubnetGroupRds
      DeletionProtection: false
      EnableCloudwatchLogsExports:
        - audit
        - error
        - general
        - slowquery
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.0
      MasterUserPassword: !Ref masterPassword
      MasterUsername: root
      Port: 3306
      PreferredBackupWindow: 17:00-18:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcSecurityGroupIds:
        - !Ref RdsSG

  clusterParameterGroupAurora:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-cluster-parametergroup
      Family: aurora-mysql5.7
      Parameters:
        character_set_client: utf8
        character_set_connection: utf8
        character_set_database: utf8
        character_set_results: utf8
        character_set_server: utf8
        general_log: 1
        server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
        server_audit_logging: 1
        slow_query_log: 1
        time_zone: Asia/Tokyo

  ec2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ec2Role

  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

  ec2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 Instances.
      GroupName: !Sub ${env}-${sysName}-ec2-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-ec2-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

Rainを使ったデプロイで説明していきます。

rain deploy -y ./site.yml site --params \
env=prd,\
sysName=example,\
billingTag=example,\
vpcCidr=10.0.0.0/16,\
domainName=example.com,\
domainName1=site1.example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
instanceType=t3.micro,\
instanceClass=db.t3.small,\
masterPassword=Passw0rd

Rainを使わない場合は、各パラメータを参考にコンソールなどからデプロイしてください。

静的ウェブサイトの分離

site2.example.com が静的ウェブサイトだとして、VirtualHostから分離していきます。静的ウェブサイトであればS3ホスティングの利用が考えられますが、S3ホスティングではhttpsプロトコルが利用できません。

ドキュメントにもある通り、Amazon CloudFront+S3で静的ウェブサイトをHTTPSで利用できるようにします。site2.example.comを分離した構成図は以下です。

オリジンのS3バケットには、 site2.example.com のコンテンツを格納します。CloudFrontのOrigin Access Identity(以下、OAI)でCloudFront DistributionのみS3バケットのアクセスを許可します。CloudFront+S3リソースが出来上がった後、Route53で site2.example.com レコードのValueをCloudFront URL dxxxxxxxxxxxx.cloudfront.net に書き換えます。

CloudFront+S3リソースの作成

まずは、site2.example.com のオリジンのS3バケットを作成する為、先程の site.yml テンプレートに site2Bucket リソースを追記しました。

  site2Bucket:
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref domainName2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site2-contents-bucket
        - Key: BillingGroup
          Value: !Ref billingTag

上記リソースを追記した site.yml テンプレートです。

site.yml(S3バケットリソース作成)
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  domainName:
    Type: String

  domainName1:
    Type: String

  domainName2:
    Type: String

  ec2Ami:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  env:
    Type: String

  hostedZoneId:
    Type: String

  instanceClass:
    Type: String

  instanceType:
    Type: String

  masterPassword:
    Type: String
    NoEcho: true

  sysName:
    Type: String

  vpcCidr:
    Type: String

Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: !Sub ${env}-${sysName}-alb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref AlbSG
      Subnets:
        - !Ref MyPublicSubnet1
        - !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-api
        - Key: BillingGroup
          Value: !Ref billingTag
      Type: application

  AlbAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName
      DomainValidationOptions:
        - DomainName: !Ref domainName
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName1
      DomainValidationOptions:
        - DomainName: !Ref domainName1
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm1
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName2
      DomainValidationOptions:
        - DomainName: !Ref domainName2
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm2
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Unauthorized Access
            StatusCode: "403"
          Type: fixed-response
      LoadBalancerArn: !Ref Alb
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08

  AlbListenerCertificate1:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm1
      ListenerArn: !Ref AlbListener

  AlbListenerCertificate2:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm2
      ListenerArn: !Ref AlbListener

  AlbListnerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - '*'
      ListenerArn: !Ref AlbListener
      Priority: 1

  AlbRecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName
      Type: A

  AlbRecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName1
      Type: A

  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A

  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Application load barancer.
      GroupName: !Sub ${env}-${sysName}-alb-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-alb-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC
  DbInstance1:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db1
        - Key: BillingGroup
          Value: !Ref billingTag

  DbInstance2:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db2
        - Key: BillingGroup
          Value: !Ref billingTag

  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance1
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip 
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  Instance2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance2
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-igw
        - Key: BillingGroup
          Value: !Ref billingTag

  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPrivateRouteTable

  MyPrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 10
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet1

  MyPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 11
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet2

  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPublicRouteTable

  MyPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 0
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet1

  MyPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 1
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet2

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref vpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-vpc
        - Key: BillingGroup
          Value: !Ref billingTag

  MyVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  ParameterGroupAurora:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-parametergroup
      Family: aurora-mysql5.7
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Rds Instances.
      GroupName: !Sub ${env}-${sysName}-rds-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ec2SG
          ToPort: 3306
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  SubnetGroupRds:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
      DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
      SubnetIds:
        - !Ref MyPrivateSubnet1
        - !Ref MyPrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-SubnetGroup
        - Key: BillingGroup
          Value: !Ref billingTag

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-ec2
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-tg
        - Key: BillingGroup
          Value: !Ref billingTag
      TargetType: instance
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
      VpcId: !Ref MyVPC
  clusterAurora:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
      DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
      DBSubnetGroupName: !Ref SubnetGroupRds
      DeletionProtection: false
      EnableCloudwatchLogsExports:
        - audit
        - error
        - general
        - slowquery
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.0
      MasterUserPassword: !Ref masterPassword
      MasterUsername: root
      Port: 3306
      PreferredBackupWindow: 17:00-18:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcSecurityGroupIds:
        - !Ref RdsSG

  clusterParameterGroupAurora:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-cluster-parametergroup
      Family: aurora-mysql5.7
      Parameters:
        character_set_client: utf8
        character_set_connection: utf8
        character_set_database: utf8
        character_set_results: utf8
        character_set_server: utf8
        general_log: 1
        server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
        server_audit_logging: 1
        slow_query_log: 1
        time_zone: Asia/Tokyo

  ec2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ec2Role

  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

  ec2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 Instances.
      GroupName: !Sub ${env}-${sysName}-ec2-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-ec2-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site2Bucket:
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref domainName2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site2-contents-bucket
        - Key: BillingGroup
          Value: !Ref billingTag

これでデプロイしていきます。

rain deploy -y ./site.yml site --params \
env=prd,\
sysName=example,\
billingTag=example,\
vpcCidr=10.0.0.0/16,\
domainName=example.com,\
domainName1=site1.example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
instanceType=t3.micro,\
instanceClass=db.t3.small,\
masterPassword=Passw0rd

S3バケットの作成後、 /var/www/site2/ にあるコンテンツをS3にアップロードしておきます。マネジメントコンソール、AWS CLIどちらでも良いです。

CloudFront、OAI、ACMの作成

次にCloudFront Distribution、OAI、ACMを作成します。ACMを作成する意図は、CloufFrontリソースはバージニアリージョンのACMのみ割当できる為です。他のリージョンのACMとCloudFrontは割当できません。なのでCloudFront、OAI、ACMをバージニアリージョンで作成します。

site2.yml
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  hostedZoneId:
    Type: String

  domainName:
    Type: String

  domainName2:
    Type: String

  domainName2Bucket:
    Type: String

  env:
    Type: String

  sysName:
    Type: String

Resources:
  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Aliases:
          - !Sub ${domainName2}
        Origins:
          - ConnectionAttempts: 3
            ConnectionTimeout: 10
            DomainName: !Sub ${domainName2Bucket}
            Id: !Sub ${domainName2Bucket}
            OriginPath: ""
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontCloudFrontOriginAccessIdentity}"
        OriginGroups:
          Quantity: 0
        DefaultCacheBehavior:
          AllowedMethods:
            - HEAD
            - GET
          CachedMethods:
            - HEAD
            - GET
          Compress: true
          CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
          SmoothStreaming: false
          TargetOriginId: !Sub ${domainName2Bucket}
          ViewerProtocolPolicy: redirect-to-https
        Comment: ""
        PriceClass: PriceClass_All
        Enabled: true
        ViewerCertificate:
          AcmCertificateArn: !Ref cloudfrontAcm
          MinimumProtocolVersion: TLSv1.2_2021
          SslSupportMethod: sni-only
        Restrictions:
          GeoRestriction:
            RestrictionType: none
        HttpVersion: http2
        DefaultRootObject: index.html
        IPV6Enabled: false

  CloudFrontCloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub "access-identity-${domainName2Bucket}"

  cloudfrontAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Sub ${domainName2}
      DomainValidationOptions:
        - DomainName: !Sub ${domainName}
          HostedZoneId: !Sub ${hostedZoneId}
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

rainコマンドは、 -r, --retion オプションでリージョンを指定できます。バージニアリージョン(us-east-1)を指定してスタックをデプロイします。
domainName2Bucketパラメータには site2Bucketリソースの仮想ホスト形式(バケット名.s3.Region.amazonaws.com or バケット名.s3.amazonaws.com)の値を指定しましょう。

rain deploy -y -r us-east-1 ./site2.yml site2 --params \
env=prd,\
sysName=example,\
billingTag=example,\
domainName=example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
domainName2Bucket=site2.example.com.s3.ap-northeast-1.amazonaws.com

S3バケットポリシー、Route53レコードの更新

作成したOAIを指定して site2Bucket のバケットポリシーを設定します。このポリシーにより、CloudFront DistributionのみS3バケットにアクセスできます。併せて site2.example.com レコードをALBからCloudFront Distributionに変更します。

〜
  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Sub ${domain2Cloudfront} # CloudFront URL
        EvaluateTargetHealth: false
        HostedZoneId: Z2FDTNDATAQYW2 # cloudfront.netのHostedZoneID
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A
〜
  site2BucketPolicy: # バケットポリシー追加
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref site2Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${site2Bucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${site2OAI} # OAIを指定

上記リソースを追記した site.yml テンプレートです。

site.yml(S3バケットポリシー作成、Route53レコード変更)
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  domainName:
    Type: String

  domainName1:
    Type: String

  domainName2:
    Type: String

  ec2Ami:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  env:
    Type: String

  hostedZoneId:
    Type: String

  instanceClass:
    Type: String

  instanceType:
    Type: String

  masterPassword:
    Type: String
    NoEcho: true

  sysName:
    Type: String

  vpcCidr:
    Type: String

  domain2Cloudfront:
    Type: String

  site2OAI:
    Type: String

Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: !Sub ${env}-${sysName}-alb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref AlbSG
      Subnets:
        - !Ref MyPublicSubnet1
        - !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-api
        - Key: BillingGroup
          Value: !Ref billingTag
      Type: application

  AlbAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName
      DomainValidationOptions:
        - DomainName: !Ref domainName
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName1
      DomainValidationOptions:
        - DomainName: !Ref domainName1
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm1
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName2
      DomainValidationOptions:
        - DomainName: !Ref domainName2
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm2
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Unauthorized Access
            StatusCode: "403"
          Type: fixed-response
      LoadBalancerArn: !Ref Alb
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08

  AlbListenerCertificate1:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm1
      ListenerArn: !Ref AlbListener

  AlbListenerCertificate2:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm2
      ListenerArn: !Ref AlbListener

  AlbListnerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - '*'
      ListenerArn: !Ref AlbListener
      Priority: 1

  AlbRecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName
      Type: A

  AlbRecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName1
      Type: A

  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Sub ${domain2Cloudfront}
        EvaluateTargetHealth: false
        HostedZoneId: Z2FDTNDATAQYW2
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A

  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Application load barancer.
      GroupName: !Sub ${env}-${sysName}-alb-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-alb-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC
  DbInstance1:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db1
        - Key: BillingGroup
          Value: !Ref billingTag

  DbInstance2:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db2
        - Key: BillingGroup
          Value: !Ref billingTag

  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance1
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  Instance2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance2
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-igw
        - Key: BillingGroup
          Value: !Ref billingTag

  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPrivateRouteTable

  MyPrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 10
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet1

  MyPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 11
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet2

  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPublicRouteTable

  MyPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 0
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet1

  MyPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 1
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet2

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref vpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-vpc
        - Key: BillingGroup
          Value: !Ref billingTag

  MyVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  ParameterGroupAurora:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-parametergroup
      Family: aurora-mysql5.7
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Rds Instances.
      GroupName: !Sub ${env}-${sysName}-rds-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ec2SG
          ToPort: 3306
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  SubnetGroupRds:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
      DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
      SubnetIds:
        - !Ref MyPrivateSubnet1
        - !Ref MyPrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-SubnetGroup
        - Key: BillingGroup
          Value: !Ref billingTag

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-ec2
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-tg
        - Key: BillingGroup
          Value: !Ref billingTag
      TargetType: instance
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
      VpcId: !Ref MyVPC
  clusterAurora:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
      DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
      DBSubnetGroupName: !Ref SubnetGroupRds
      DeletionProtection: false
      EnableCloudwatchLogsExports:
        - audit
        - error
        - general
        - slowquery
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.0
      MasterUserPassword: !Ref masterPassword
      MasterUsername: root
      Port: 3306
      PreferredBackupWindow: 17:00-18:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcSecurityGroupIds:
        - !Ref RdsSG

  clusterParameterGroupAurora:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-cluster-parametergroup
      Family: aurora-mysql5.7
      Parameters:
        character_set_client: utf8
        character_set_connection: utf8
        character_set_database: utf8
        character_set_results: utf8
        character_set_server: utf8
        general_log: 1
        server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
        server_audit_logging: 1
        slow_query_log: 1
        time_zone: Asia/Tokyo

  ec2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ec2Role

  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

  ec2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 Instances.
      GroupName: !Sub ${env}-${sysName}-ec2-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-ec2-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site2Bucket:
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref domainName2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site2-contents-bucket
        - Key: BillingGroup
          Value: !Ref billingTag

  site2BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref site2Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${site2Bucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${site2OAI}

domain2Cloudfront、site2OAIパラメータを追加してデプロイします。

rain deploy -y ./site.yml site --params \
env=prd,\
sysName=example,\
billingTag=example,\
vpcCidr=10.0.0.0/16,\
domainName=example.com,\
domainName1=site1.example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
instanceType=t3.micro,\
instanceClass=db.t3.small,\
masterPassword=Passw0rd,\
domain2Cloudfront=dxxxxxxxxxxxx.cloudfront.net,\
site2OAI=E36GW62SYZ8RPJ

site2.example.com にhttpsでアクセスできることを確認できたらOKです。

動的ウェブサイトの分離

site1.example.com が動的ウェブサイトの場合(例えばhttpd,tomcat)の分離先として、別のEC2インスタンスもしくはECSなど選択肢があります。今回はsite1.example.comを動的ウェブサイトに見立てて、ECS(Fargate)に分離してみます。

ECSサービス作成前の事前準備

事前準備としてECRプライベートリポジトリ、コンテナログを出力する為のCloudwatchロググループとタスク実行ロールを作成し、タスク定義で指定します。

  site1Ecr:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: site1
      ImageScanningConfiguration:
        ScanOnPush: "true"
      LifecyclePolicy:
        LifecyclePolicyText: |
          {
            "rules": [
              {
                "rulePriority": 1,
                "description": "Delete images",
                "selection": {
                  "tagStatus": "any",
                  "countType": "imageCountMoreThan",
                  "countNumber": 10
                },
                "action": {
                  "type": "expire"
                }
              }
            ]
          }
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecr
        - Key: BillingGroup
          Value: !Ref billingTag

  ecsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/ecs/logs/${env}/${sysName}"

  ecsTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${env}-${sysName}-ecs-task-exec-role
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  site1TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Essential: true
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/site1:latest"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ecsLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: site1
          Name: site1
          PortMappings:
            - ContainerPort: 80
              HostPort: 80
              Protocol: tcp
      Family: site1
      ExecutionRoleArn: !GetAtt ecsTaskExecutionRole.Arn
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: "256"
      Memory: "512"

上記リソースを追記した site.yml テンプレートです。

site.yml(ECR、CloudWatch LogGroup、IAMロール、ECSタスク定義の追加)
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  domainName:
    Type: String

  domainName1:
    Type: String

  domainName2:
    Type: String

  ec2Ami:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  env:
    Type: String

  hostedZoneId:
    Type: String

  instanceClass:
    Type: String

  instanceType:
    Type: String

  masterPassword:
    Type: String
    NoEcho: true

  sysName:
    Type: String

  vpcCidr:
    Type: String

  domain2Cloudfront:
    Type: String

  site2OAI:
    Type: String

Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: !Sub ${env}-${sysName}-alb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref AlbSG
      Subnets:
        - !Ref MyPublicSubnet1
        - !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-api
        - Key: BillingGroup
          Value: !Ref billingTag
      Type: application

  AlbAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName
      DomainValidationOptions:
        - DomainName: !Ref domainName
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName1
      DomainValidationOptions:
        - DomainName: !Ref domainName1
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm1
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName2
      DomainValidationOptions:
        - DomainName: !Ref domainName2
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm2
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Unauthorized Access
            StatusCode: "403"
          Type: fixed-response
      LoadBalancerArn: !Ref Alb
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08

  AlbListenerCertificate1:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm1
      ListenerArn: !Ref AlbListener

  AlbListenerCertificate2:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm2
      ListenerArn: !Ref AlbListener

  AlbListnerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - '*'
      ListenerArn: !Ref AlbListener
      Priority: 1

  AlbRecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName
      Type: A

  AlbRecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName1
      Type: A

  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Sub ${domain2Cloudfront}
        EvaluateTargetHealth: false
        HostedZoneId: Z2FDTNDATAQYW2
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A

  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Application load barancer.
      GroupName: !Sub ${env}-${sysName}-alb-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-alb-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC
  DbInstance1:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db1
        - Key: BillingGroup
          Value: !Ref billingTag

  DbInstance2:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db2
        - Key: BillingGroup
          Value: !Ref billingTag

  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance1
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  Instance2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance2
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-igw
        - Key: BillingGroup
          Value: !Ref billingTag

  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPrivateRouteTable

  MyPrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 10
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet1

  MyPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 11
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet2

  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPublicRouteTable

  MyPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 0
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet1

  MyPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 1
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet2

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref vpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-vpc
        - Key: BillingGroup
          Value: !Ref billingTag

  MyVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  ParameterGroupAurora:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-parametergroup
      Family: aurora-mysql5.7
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Rds Instances.
      GroupName: !Sub ${env}-${sysName}-rds-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ec2SG
          ToPort: 3306
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  SubnetGroupRds:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
      DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
      SubnetIds:
        - !Ref MyPrivateSubnet1
        - !Ref MyPrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-SubnetGroup
        - Key: BillingGroup
          Value: !Ref billingTag

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-ec2
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-tg
        - Key: BillingGroup
          Value: !Ref billingTag
      TargetType: instance
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
      VpcId: !Ref MyVPC
  clusterAurora:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
      DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
      DBSubnetGroupName: !Ref SubnetGroupRds
      DeletionProtection: false
      EnableCloudwatchLogsExports:
        - audit
        - error
        - general
        - slowquery
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.0
      MasterUserPassword: !Ref masterPassword
      MasterUsername: root
      Port: 3306
      PreferredBackupWindow: 17:00-18:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcSecurityGroupIds:
        - !Ref RdsSG

  clusterParameterGroupAurora:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-cluster-parametergroup
      Family: aurora-mysql5.7
      Parameters:
        character_set_client: utf8
        character_set_connection: utf8
        character_set_database: utf8
        character_set_results: utf8
        character_set_server: utf8
        general_log: 1
        server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
        server_audit_logging: 1
        slow_query_log: 1
        time_zone: Asia/Tokyo

  ec2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ec2Role

  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

  ec2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 Instances.
      GroupName: !Sub ${env}-${sysName}-ec2-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-ec2-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site2Bucket:
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref domainName2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site2-contents-bucket
        - Key: BillingGroup
          Value: !Ref billingTag

  site2BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref site2Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${site2Bucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${site2OAI}

  site1Ecr:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: site1
      ImageScanningConfiguration:
        ScanOnPush: "true"
      LifecyclePolicy:
        LifecyclePolicyText: |
          {
            "rules": [
              {
                "rulePriority": 1,
                "description": "Delete images",
                "selection": {
                  "tagStatus": "any",
                  "countType": "imageCountMoreThan",
                  "countNumber": 10
                },
                "action": {
                  "type": "expire"
                }
              }
            ]
          }
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecr
        - Key: BillingGroup
          Value: !Ref billingTag

  ecsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/ecs/logs/${env}/${sysName}"

  ecsTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${env}-${sysName}-ecs-task-exec-role
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  site1TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Essential: true
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/site1:latest"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ecsLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: site1
          Name: site1
          PortMappings:
            - ContainerPort: 80
              HostPort: 80
              Protocol: tcp
      Family: site1
      ExecutionRoleArn: !GetAtt ecsTaskExecutionRole.Arn
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: "256"
      Memory: "512"

デプロイしていきます。

rain deploy -y ./site.yml site --params \
env=prd,\
sysName=example,\
billingTag=example,\
vpcCidr=10.0.0.0/16,\
domainName=example.com,\
domainName1=site1.example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
instanceType=t3.micro,\
instanceClass=db.t3.small,\
masterPassword=Passw0rd,\
domain2Cloudfront=dxxxxxxxxxxxx.cloudfront.net,\
site2OAI=E36GW62SYZ8RPJ

次にECRリポジトリにイメージをアップロードします。site1.example.com コンテンツ(https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip)を含むコンテナイメージをhttpd公式イメージをベースに作成します。

FROM httpd:2.4
COPY ./school_001_DL/ /usr/local/apache2/htdocs/

コンテナイメージのpushはドキュメントを参照してください。

ECSサービスの実行

事前準備ができたのでECSサービスを設定します。併せてALBのリスナールールで site1.example.com のリクエストを振り分けるルールと振り分け先のターゲットグループを設定します。

  site1EcsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: site1 ecs.
      GroupName: !Sub ${env}-${sysName}-site1-ecs-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecs-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site1TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-site1-ecs-tg
      Port: 80
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecs-tg
        - Key: BillingGroup
          Value: !Ref billingTag

  cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${env}-${sysName}-cluster
      ClusterSettings:
        - Name: containerInsights
          Value: enabled
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag

  site1ServiceDefinition:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref cluster
      DesiredCount: 0
      LaunchType: FARGATE
      LoadBalancers:
        - TargetGroupArn: !Ref site1TargetGroup
          ContainerPort: 80
          ContainerName: site1
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref site1EcsSG
          Subnets:
            - !Ref MyPublicSubnet1
            - !Ref MyPublicSubnet2
      ServiceName: !Sub ${env}-${sysName}-site1
      TaskDefinition: !Sub ${site1TaskDefinition}

上記リソースを追記した site.yml テンプレートです。

site.yml(ECSサービス、ターゲットグループ、ALBリスナールールの変更)
AWSTemplateFormatVersion: "2010-09-09"

Description: Template generated by rain

Parameters:
  billingTag:
    Type: String

  domainName:
    Type: String

  domainName1:
    Type: String

  domainName2:
    Type: String

  ec2Ami:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  env:
    Type: String

  hostedZoneId:
    Type: String

  instanceClass:
    Type: String

  instanceType:
    Type: String

  masterPassword:
    Type: String
    NoEcho: true

  sysName:
    Type: String

  vpcCidr:
    Type: String

  domain2Cloudfront:
    Type: String

  site2OAI:
    Type: String

Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: !Sub ${env}-${sysName}-alb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref AlbSG
      Subnets:
        - !Ref MyPublicSubnet1
        - !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-api
        - Key: BillingGroup
          Value: !Ref billingTag
      Type: application

  AlbAcm:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName
      DomainValidationOptions:
        - DomainName: !Ref domainName
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm1:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName1
      DomainValidationOptions:
        - DomainName: !Ref domainName1
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm1
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbAcm2:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Ref domainName2
      DomainValidationOptions:
        - DomainName: !Ref domainName2
          HostedZoneId: !Ref hostedZoneId
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-albacm2
        - Key: BillingGroup
          Value: !Ref billingTag
      ValidationMethod: DNS

  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm
      DefaultActions:
        - FixedResponseConfig:
            ContentType: text/plain
            MessageBody: Unauthorized Access
            StatusCode: "403"
          Type: fixed-response
      LoadBalancerArn: !Ref Alb
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-2016-08

  AlbListenerCertificate1:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm1
      ListenerArn: !Ref AlbListener

  AlbListenerCertificate2:
    Type: AWS::ElasticLoadBalancingV2::ListenerCertificate
    Properties:
      Certificates:
        - CertificateArn: !Ref AlbAcm2
      ListenerArn: !Ref AlbListener

  AlbListnerRule1:
    Type: AWS::ElasticLoadBalancingV2::ListenerRule
    Properties:
      Actions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      Conditions:
        - Field: path-pattern
          PathPatternConfig:
            Values:
              - '*'
      ListenerArn: !Ref AlbListener
      Priority: 1

  AlbRecordSet:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName
      Type: A

  AlbRecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Join
          - ""
          - - dualstack.
            - !GetAtt Alb.DNSName
        EvaluateTargetHealth: false
        HostedZoneId: Z14GRHDCWA56QT
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName1
      Type: A

  AlbRecordSet2:
    Type: AWS::Route53::RecordSet
    Properties:
      AliasTarget:
        DNSName: !Sub ${domain2Cloudfront}
        EvaluateTargetHealth: false
        HostedZoneId: Z2FDTNDATAQYW2
      HostedZoneId: !Ref hostedZoneId
      Name: !Ref domainName2
      Type: A

  AlbSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Application load barancer.
      GroupName: !Sub ${env}-${sysName}-alb-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          IpProtocol: tcp
          ToPort: 443
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-alb-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC
  DbInstance1:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db1
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db1
        - Key: BillingGroup
          Value: !Ref billingTag

  DbInstance2:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      AutoMinorVersionUpgrade: false
      DBClusterIdentifier: !Ref clusterAurora
      DBInstanceClass: !Ref instanceClass
      DBInstanceIdentifier: !Sub ${env}-${sysName}-db2
      DBParameterGroupName: !Ref ParameterGroupAurora
      EnablePerformanceInsights: false
      Engine: aurora-mysql
      MonitoringInterval: 0
      PubliclyAccessible: false
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-db2
        - Key: BillingGroup
          Value: !Ref billingTag

  Instance1:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance1
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  Instance2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: 3000
            VolumeSize: 8
            VolumeType: gp3
      CreditSpecification:
        CPUCredits: standard
      IamInstanceProfile: !Ref ec2Profile
      ImageId: !Ref ec2Ami
      InstanceType: !Ref instanceType
      SecurityGroupIds:
        - !Ref ec2SG
      SubnetId: !Ref MyPublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-instance2
        - Key: BillingGroup
          Value: !Ref billingTag
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum install -y httpd
          cp -p /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.org
          cat <<EOF >> /etc/httpd/conf/httpd.conf
          <VirtualHost *:80>
            ServerName ${domainName}
            DocumentRoot /var/www/html/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName1}
            DocumentRoot /var/www/site1/
          </VirtualHost>
          <VirtualHost *:80>
            ServerName ${domainName2}
            DocumentRoot /var/www/site2/
          </VirtualHost>
          EOF
          mkdir /var/www/{site1,site2}
          cd /var/www/html/
          curl -O https://free-hp.net/clinic/cl_002/clinic_002_DL.zip && unzip clinic_002_DL.zip && mv clinic_002_DL/* . && rmdir clinic_002_DL && rm clinic_002_DL.zip
          cd /var/www/site1/
          curl -O https://free-hp.net/restaurant/re_002/restaurant_002_DL.zip && unzip restaurant_002_DL.zip && mv restaurant_002_DL/* . && rmdir restaurant_002_DL && rm restaurant_002_DL.zip
          cd /var/www/site2/
          curl -O https://free-hp.net/school/sc_001/school_001_DL.zip && unzip school_001_DL.zip && mv school_001_DL/* . && rmdir school_001_DL && rm school_001_DL.zip
          systemctl start httpd

  MyInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-igw
        - Key: BillingGroup
          Value: !Ref billingTag

  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPrivateRouteTable

  MyPrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 10
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet1

  MyPrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 11
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 12
          - 8
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-private-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRouteTable
      SubnetId: !Ref MyPrivateSubnet2

  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyInternetGateway
      RouteTableId: !Ref MyPublicRouteTable

  MyPublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-rtb
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 0
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 0
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-1
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet1

  MyPublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select
        - 1
        - !GetAZs
          Ref: AWS::Region
      CidrBlock: !Select
        - 1
        - !Cidr
          - !GetAtt MyVPC.CidrBlock
          - 2
          - 8
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-public-subnet-2
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  MyPublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRouteTable
      SubnetId: !Ref MyPublicSubnet2

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref vpcCidr
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-vpc
        - Key: BillingGroup
          Value: !Ref billingTag

  MyVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref MyInternetGateway
      VpcId: !Ref MyVPC

  ParameterGroupAurora:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-parametergroup
      Family: aurora-mysql5.7
  RdsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Rds Instances.
      GroupName: !Sub ${env}-${sysName}-rds-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 3306
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref ec2SG
          ToPort: 3306
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  SubnetGroupRds:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: !Sub ${env}-${sysName}-rds-SubnetGroup
      DBSubnetGroupName: !Sub ${env}-${sysName}-rds-subnetgroup
      SubnetIds:
        - !Ref MyPrivateSubnet1
        - !Ref MyPrivateSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-rds-SubnetGroup
        - Key: BillingGroup
          Value: !Ref billingTag

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-ec2
      Port: 80
      Protocol: HTTP
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-tg
        - Key: BillingGroup
          Value: !Ref billingTag
      TargetType: instance
      Targets:
        - Id: !Ref Instance1
        - Id: !Ref Instance2
      VpcId: !Ref MyVPC
  clusterAurora:
    Type: AWS::RDS::DBCluster
    DeletionPolicy: Delete
    Properties:
      BackupRetentionPeriod: 7
      DBClusterIdentifier: !Sub ${env}-${sysName}-cluster
      DBClusterParameterGroupName: !Ref clusterParameterGroupAurora
      DBSubnetGroupName: !Ref SubnetGroupRds
      DeletionProtection: false
      EnableCloudwatchLogsExports:
        - audit
        - error
        - general
        - slowquery
      Engine: aurora-mysql
      EngineVersion: 5.7.mysql_aurora.2.10.0
      MasterUserPassword: !Ref masterPassword
      MasterUsername: root
      Port: 3306
      PreferredBackupWindow: 17:00-18:00
      PreferredMaintenanceWindow: tue:18:00-tue:19:00
      StorageEncrypted: true
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcSecurityGroupIds:
        - !Ref RdsSG

  clusterParameterGroupAurora:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      Description: !Sub ${env}-${sysName}-cluster-parametergroup
      Family: aurora-mysql5.7
      Parameters:
        character_set_client: utf8
        character_set_connection: utf8
        character_set_database: utf8
        character_set_results: utf8
        character_set_server: utf8
        general_log: 1
        server_audit_events: CONNECT,QUERY,QUERY_DCL,QUERY_DDL,QUERY_DML,TABLE
        server_audit_logging: 1
        slow_query_log: 1
        time_zone: Asia/Tokyo

  ec2Profile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref ec2Role

  ec2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess

  ec2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ec2 Instances.
      GroupName: !Sub ${env}-${sysName}-ec2-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-ec2-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site2Bucket:
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref domainName2
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site2-contents-bucket
        - Key: BillingGroup
          Value: !Ref billingTag

  site2BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref site2Bucket
      PolicyDocument:
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub arn:aws:s3:::${site2Bucket}/*
            Principal:
              AWS: !Sub arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${site2OAI}

  site1Ecr:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: site1
      ImageScanningConfiguration:
        ScanOnPush: "true"
      LifecyclePolicy:
        LifecyclePolicyText: |
          {
            "rules": [
              {
                "rulePriority": 1,
                "description": "Delete images",
                "selection": {
                  "tagStatus": "any",
                  "countType": "imageCountMoreThan",
                  "countNumber": 10
                },
                "action": {
                  "type": "expire"
                }
              }
            ]
          }
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecr
        - Key: BillingGroup
          Value: !Ref billingTag

  ecsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/ecs/logs/${env}/${sysName}"

  ecsTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${env}-${sysName}-ecs-task-exec-role
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  site1TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Essential: true
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/site1:latest"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ecsLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: site1
          Name: site1
          PortMappings:
            - ContainerPort: 80
              HostPort: 80
              Protocol: tcp
      Family: site1
      ExecutionRoleArn: !GetAtt ecsTaskExecutionRole.Arn
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      Cpu: "256"
      Memory: "512"

  site1EcsSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: site1 ecs.
      GroupName: !Sub ${env}-${sysName}-site1-ecs-sg
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          FromPort: 0
          IpProtocol: "-1"
          ToPort: 0
      SecurityGroupIngress:
        - FromPort: 80
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref AlbSG
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecs-sg
        - Key: BillingGroup
          Value: !Ref billingTag
      VpcId: !Ref MyVPC

  site1TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${env}-${sysName}-site1-ecs-tg
      Port: 80
      Protocol: HTTP
      TargetType: ip
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-site1-ecs-tg
        - Key: BillingGroup
          Value: !Ref billingTag

  cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${env}-${sysName}-cluster
      ClusterSettings:
        - Name: containerInsights
          Value: enabled
      Tags:
        - Key: Name
          Value: !Sub ${env}-${sysName}-cluster
        - Key: BillingGroup
          Value: !Ref billingTag

  site1ServiceDefinition:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref cluster
      DesiredCount: 0
      LaunchType: FARGATE
      LoadBalancers:
        - TargetGroupArn: !Ref site1TargetGroup
          ContainerPort: 80
          ContainerName: site1
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - !Ref site1EcsSG
          Subnets:
            - !Ref MyPublicSubnet1
            - !Ref MyPublicSubnet2
      ServiceName: !Sub ${env}-${sysName}-site1
      TaskDefinition: !Sub ${site1TaskDefinition}

最後のデプロイです。

rain deploy -y ./site.yml site --params \
env=prd,\
sysName=example,\
billingTag=example,\
vpcCidr=10.0.0.0/16,\
domainName=example.com,\
domainName1=site1.example.com,\
domainName2=site2.example.com,\
hostedZoneId=Z10116024XXXXXXXXXXX,\
instanceType=t3.micro,\
instanceClass=db.t3.small,\
masterPassword=Passw0rd,\
domain2Cloudfront=dxxxxxxxxxxxx.cloudfront.net,\
site2OAI=E36GW62SYZ8RPJ

site1.example.com にアクセスしてレスポンスが帰ってきたらOKです。お疲れ様でした。

終わりに

VirtualHostからの分離方法を整理してみました。VirtualHostだけじゃなく、サイトのリプレースも同様の流れになるので参考にしていただければ幸いです。また、S3、ECR,ECSはCodePipelineのビルド、デプロイ先として指定できるのでCI/CDの導入も一緒にご検討ください。