😊

CloudFormationで環境構築を自動化する (CI/CDまでの道⑩)

2022/02/02に公開

はじめに

ついに「CI/CDまでの道」最終回となりました。
今回は前回作成したFargateの環境構築をCloudFormationでなるべく自動構築できるようにしていきます。

構築しているインフラ環境は以下となります。

青枠の部分は今回は手動で構築することにしました。
今後IaCする機会があれば記事を更新する予定です。

青枠の構築については第9回通りです。

CI/CDまでの道シリーズ

環境

  • wsl2 (ubuntu20.04)
  • docker 20.10.9
  • docker-compose 1.29.1
  • git 2.25.1
  • vscode

ネットワークの構築

vpc.ymlというファイルにネットワーク設定を書き足していきます。
ファイル分割は一度完成してから行うつもりです。

共通: スタックの起動

スタックをAWS CLIから実行します。

# 検証
$  aws cloudformation validate-template --template-body file://vpc.yml

# スタック作成
$ aws cloudformation create-stack --stack-name vpc --template-body file://vpc.yml

# スタック削除
$ aws cloudformation delete-stack --stack-name vpc

VPC

vpc.yml
Resources:

 #=================================
 # VPCの作成
 #=================================
  SampleVPC:
    Type: "AWS::EC2::VPC"
    Properties: 
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: 'false'
      EnableDnsSupport: 'true'
      Tags:
      - Key: Name
        Value: sample-vpc

サブネット

vpc.yml

#=================================
 # サブネットの作成
 #=================================
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref SampleVPC
      CidrBlock: 10.0.0.0/20
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1a
      Tags:
      - Key: Name
        Value: sample-subnet-public01

  PublicSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref SampleVPC
      CidrBlock: 10.0.16.0/20
      MapPublicIpOnLaunch: true
      AvailabilityZone: ap-northeast-1c
      Tags:
      - Key: Name
        Value: sample-subnet-public02

  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref SampleVPC
      CidrBlock: 10.0.64.0/20
      AvailabilityZone: ap-northeast-1a
      Tags:
      - Key: Name
        Value: sample-subnet-private01

  PrivateSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref SampleVPC
      CidrBlock: 10.0.80.0/20
      AvailabilityZone: ap-northeast-1c
      Tags:
      - Key: Name
        Value: sample-subnet-private02

インターネットゲートウェイ

vpc.yml

 #=================================
 # インターネットゲートウェイの作成
 #=================================

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: sample-igw

  # InternetGatewayとVPCの接続
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref SampleVPC

ルートテーブル

vpc.yml
 #=================================
 # ルートテーブルの作成
 #=================================

  SampleRtPublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref SampleVPC
      Tags:
        - Key: Name
          Value: sample-rt-public

  # SubnetとRoutetableの関連付け
  PublicRouteTableAssociation1a:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref SampleRtPublic
      SubnetId: !Ref SamplePublicSubnet01
  PublicRouteTableAssociation1c:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref SampleRtPublic
      SubnetId: !Ref SamplePublicSubnet02

  # Routeの指定
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref SampleRtPublic
      GatewayId: !Ref SampleIgw

セキュリティグループ

vpc.yml
 #=================================
 # セキュリティグループの作成
 #=================================

  SampleSgElb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for load balancer
      GroupName: sample-sg-elb
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: sample-sg-elb
      VpcId: !Ref SampleVPC

  SampleSgEcs:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ecs
      GroupName: sample-sg-ecs
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: sample-sg-ecs
      VpcId: !Ref SampleVPC

  SampleSgDb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for rds
      GroupName: sample-sg-db
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: sample-sg-db
      VpcId: !Ref SampleVPC

RDS

vpc.yml
 #=================================
 # RDSの作成
 #=================================

  #  DBパラメーターグループ
  SampleDBPg:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Family: mysql8.0
      Description: sample parameter group
      Tags:
        - Key: Name
          Value: sample-db-pg

  # DBサブネットグループ
  SampleDBSubnet: 
    Type: AWS::RDS::DBSubnetGroup
    Properties: 
      DBSubnetGroupName: sample-db-subnet
      DBSubnetGroupDescription: sample db subnet
      SubnetIds: 
        - !Ref SamplePrivateSubnet01
        - !Ref SamplePrivateSubnet02

  #  DB
  SampleDB:
    Type: AWS::RDS::DBInstance
    Properties:
      AllocatedStorage : 20
      DBInstanceClass: db.t2.micro
      Port: 3306
      StorageType: gp2
      BackupRetentionPeriod: 7
      MasterUsername: admin
      MasterUserPassword: password
      DBInstanceIdentifier: sample-db
      Engine: mysql
      EngineVersion: 8.0.23
      DBSubnetGroupName: !Ref SampleDBSubnet
      DBParameterGroupName: !Ref SampleDBPg
      MultiAZ: true
      VPCSecurityGroups:
        - !Ref SampleSgDB

S3

vpc.yml
 #=================================
 # S3
 #=================================
  SampleS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: "[一意なバケット名]"
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      VersioningConfiguration:
        Status: Enabled

バケットの名前はAWSでユニークな名前に変更してください。

ロードバランサー

vpc.yml
 #=================================
 # ロードバランサー
 #=================================

  SampleElb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: ipv4
      Name: sample-elb
      Scheme: internet-facing
      SecurityGroups:
        - !Ref SampleSgElb
      Subnets:
        - !Ref SamplePublicSubnet01
        - !Ref SamplePublicSubnet02
      Tags:
        - Key: Name
          Value: sample-elb

  # ALBのターゲットグループの指定
  BlueGreenTarget1:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: BlueGreenTarget1
      TargetType: ip
      Port: 80
      Protocol: HTTP
      HealthCheckPath : /test
      VpcId: !Ref SampleVPC

  BlueGreenTarget2:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: BlueGreenTarget2
      TargetType: ip
      Port: 80
      Protocol: HTTP
      HealthCheckPath : /test
      VpcId: !Ref SampleVPC

  # ALBのリスナーの指定
  ALBListener1:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn:
            !Ref BlueGreenTarget1
      LoadBalancerArn:
        !Ref SampleElb
      Port: 80
      Protocol: HTTP

  ALBListener2:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn:
            !Ref BlueGreenTarget2
      LoadBalancerArn:
        !Ref SampleElb
      Port: 8080
      Protocol: HTTP

ECR

vpc.yml
 #=================================
 # ECR
 #=================================

  SampleRails:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: sample-rails

  SampleNginx:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: sample-nginx

ECS

vpc.yml
 #=================================
 # ECS
 #=================================

  # クラスター
  ECSCluster:
    Type: 'AWS::ECS::Cluster'
    Properties:
      ClusterName:  sample-cluster

  # ロググループ
  ECSLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties:
      LogGroupName: !Sub '/ecs/logs/sample-ecs-group'

  # タスク定義
  ECSTaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      Cpu: 256
      Memory: 512
      ExecutionRoleArn: arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole
      TaskRoleArn: arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole
      Family: sample-ecs
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE

      # コンテナ追加
      ContainerDefinitions:
        - Name: rails
          Image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:staging
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: ap-northeast-1
              awslogs-stream-prefix: sample
          MemoryReservation: 128
          PortMappings:
            - HostPort: 3000
              Protocol: tcp
              ContainerPort: 3000
          Environment:
            - Name: DB_USERNAME
              Value: admin
            - Name: DB_PASSWORD
              Value: password
            - Name: DB_DATABASE
              Value: myapp
            - Name: DB_HOST
              Value: !GetAtt SampleDB.Endpoint.Address
            - Name: SECRET_KEY_BASE
              Value: [master.keyの値]
        - Name: nginx
          Image: [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-nginx:latest
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: ap-northeast-1
              awslogs-stream-prefix: sample
          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
          VolumesFrom:
            - SourceContainer: rails

アカウントIDとmaster.keyの値を各自のものに置き換えてください。

その他

  • Rails(latest, staging)とNginx(latest)のPush
  • IAM (ecsTaskExecutionRole, CodeDeployRole)作成
  • CodeBuild(build, test)作成
  • ECSのサービス作成
  • CodePipeline作成
  • ACM(SSL証明書)作成
  • Route53でレコード作成

この作業を記事通りに行うことで同じ環境が再現できるようになります

削除はECRS3は中身があるのでコンソールから手作業で先に削除する必要があります。

おわりに

作成したものはこちらのリポジトリに用意しています。

今回は説明がほぼありませんが、いままでの設定をそのまま入力しているのでそれぞれのコンソールでの設定した値と見比べることで各項目が何を表しているか理解できると思います。

以上で「CI/CDまでの道」を終わります。
1か月かけて更新してきましたが1から調べてここまでやれたことに達成感を感じています。

次はRailsを使った個人開発に挑戦していきたいと思います。

参考

GitHubで編集を提案

Discussion