[社内勉強会資料] CloudFormationで構築する自動デプロイ環境 ～ 3/6

# 第3回CloudFormationテンプレートの実行（ECS）
さて第3回の今回は、本記事に記載のCloudFormationテンプレートを用いて、自動デプロイのデプロイ先となるECSとECRを構築していきます。

前回：[[社内勉強会資料] CloudFormationで構築する自動デプロイ環境 ～ 2/6](https://qiita.com/toru-yamagishi/items/580f49d158aecfc52473)

## CloudFormationで作成するリソース
* CloudFormationテンプレートを使って、以下のAWSリソースを作成します。
    * ECR
    * ALB
        * Listener
        * TargetGroup
    * ECS
        * Cluster
        * Service
        * TaskDefinition
    * IAM
        * Role
    * CloudWatch
        * LogGroup

## CloudFormationテンプレート
### ecr.yaml
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: ECR template

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Environment Name"
        Parameters:
          - ProjectEnvironment
      - Label:
          default: "ECR Repository Configuration"
        Parameters:
          - ImageTagMutability

Parameters:
  ProjectEnvironment:
    Type: String
    Default: 'usecase-auto-deploy'
  # 同じタグによる上書きを許可する（デフォルト）
  ImageTagMutability:
    Description: The tag mutability setting for the repository.
    Type: String
    Default: MUTABLE
    AllowedValues:
      - IMMUTABLE
      - MUTABLE

Resources:
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub "${ProjectEnvironment}-ecr"
      ImageTagMutability: !Ref ImageTagMutability
      # ライフサイクル : イメージが20個以上になった場合、古い順に削除する
      LifecyclePolicy:
        LifecyclePolicyText: |
          {
            "rules": [
              {
                "rulePriority": 1,
                "description": "Delete more than 20 images",
                "selection": {
                "tagStatus": "any",
                "countType": "imageCountMoreThan",
                "countNumber": 20
              },
                "action": {
                "type": "expire"
                }
              }
            ]
          }

Outputs:
  ECRRepository:
    Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}"
    Export:
      Name: !Sub "${ProjectEnvironment}-ecr-uri"
```

### fargate.yaml
```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create Fargate and ALB

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - ProjectName
      - Label:
          default: "InternetALB Configuration"
        Parameters:
          - InternetALBName
          - TargetGroupName
          - CertificateArn
          - HostedZoneName
          - WebsiteDomainName
      - Label:
          default: "Fargate for ECS Configuration"
        Parameters:
          - ECSClusterName
          - ECSTaskName
          - ECSTaskCPUUnit
          - ECSTaskMemory
          - ECSContainerName
          - ECSImageName
          - ECSServiceName
          - ECSTaskDesiredCount
      - Label:
          default: "Scaling Configuration"
        Parameters:
          - TaskMinContainerCount
          - TaskMaxContainerCount

    ParameterLabels:
      InternetALBName:
        default: "InternetALBName"
      TargetGroupName:
        default: "TargetGroupName"
      ECSClusterName:
        default: "ECSClusterName"
      ECSTaskName:
        default: "ECSTaskName"
      ECSTaskCPUUnit:
        default: "ECSTaskCPUUnit"
      ECSTaskMemory:
        default: "ECSTaskMemory"
      ECSContainerName:
        default: "ECSContainerName"
      ECSImageName:
        default: "ECSImageName"
      ECSServiceName:
        default: "ECSServiceName"
      ECSTaskDesiredCount:
        default: "ECSTaskDesiredCount"

Parameters:
  ProjectName:
    Default: usecase-auto-deploy
    Type: String

#InternetALB
  InternetALBName:
    Type: String
    Default: "alb"

#TargetGroupName
  TargetGroupName:
    Type: String
    Default: "tg"

#ECSClusterName
  ECSClusterName:
    Type: String
    Default: "cluster"

#ECSTaskName
  ECSTaskName:
    Type: String
    Default: "task"

#ECSTaskCPUUnit
  ECSTaskCPUUnit:
    AllowedValues: [ 256, 512, 1024, 2048, 4096  ]
    Type: String
    Default: "256"

#ECSTaskMemory
  ECSTaskMemory:
    AllowedValues: [ 256, 512, 1024, 2048, 4096  ]
    Type: String
    Default: "512"

#ECSContainerName
  ECSContainerName:
    Type: String
    Default: "container"

#ECSServiceName
  ECSServiceName:
    Type: String
    Default: "service"

#ECSTaskDesiredCount
  ECSTaskDesiredCount:
    Type: Number
    Default: 1

  TaskMinContainerCount:
    Type: Number
    Description: Minimum number of containers to run for the service
    Default: 1
    MinValue: 1
    ConstraintDescription: Value must be at least one

  TaskMaxContainerCount:
    Type: Number
    Description: Maximum number of containers to run for the service when auto scaling out
    Default: 2
    MinValue: 1
    ConstraintDescription: Value must be at least one

Resources:
  TargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
      VpcId:
        Fn::ImportValue: !Sub "${ProjectName}-vpc"
      Name: !Sub "${ProjectName}-${TargetGroupName}"
      Protocol: HTTP
      Port: 80
      TargetType: ip
      HealthCheckEnabled: true
      HealthCheckPath: '/index.html'
      Matcher:
        HttpCode: "302,200"

  InternetALB:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      Name: !Sub "${ProjectName}-${InternetALBName}"
      Tags:
        - Key: Name
          Value: !Sub "${ProjectName}-${InternetALBName}"
      Scheme: "internet-facing"
      LoadBalancerAttributes:
        - Key: "deletion_protection.enabled"
          Value: false
        - Key: "idle_timeout.timeout_seconds"
          Value: 60
      SecurityGroups:
        - Fn::ImportValue: !Sub "${ProjectName}-ALBSecurityGroupId"
      Subnets:
        - Fn::ImportValue: !Sub "${ProjectName}-ALBSubnetId1"
        - Fn::ImportValue: !Sub "${ProjectName}-ALBSubnetId2"

  ALBListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: forward
      LoadBalancerArn: !Ref InternetALB
      Port: 80
      Protocol: HTTP

  ECSCluster:
    Type: "AWS::ECS::Cluster"
    Properties:
      ClusterName: !Sub "${ProjectName}-${ECSClusterName}"

  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"

  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy-${AWS::Region}"
      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

  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: !Ref ECSTaskCPUUnit
      ExecutionRoleArn: !Ref ECSTaskExecutionRole
      Family: !Sub "${ProjectName}-${ECSTaskName}"
      Memory: !Ref ECSTaskMemory
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE

      ContainerDefinitions:
        - Name: "app"
          Image:
            !Sub
              - "${ECR_URI}:latest"
              - ECR_URI:  {"Fn::ImportValue": !Sub "${ProjectName}-ecr-uri"}
          Command:
            - "/bin/sh -c \"echo '<html> <head> <title>Amazon ECS Sample App</title> <style>body {margin-top: 40px; background-color: #333;} </style> </head><body> <div style=color:white;text-align:center> <h1>Amazon ECS Sample App</h1> <h2>Congratulations!</h2> <p>Your application is now running on a container in Amazon ECS.</p> </div></body></html>' >  /var/www/html/index2.html && /root/run_apache.sh\""
          EntryPoint:
            - "sh"
            - "-c"
          Environment:
            - Name: 'AWS_REGION'
              Value: 'ap-northeast-1'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: !Ref ProjectName
          MemoryReservation: 128
          Essential: true
          PseudoTerminal: true
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80

  ECSService:
    Type: AWS::ECS::Service
    DependsOn: ALBListener
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: !Ref ECSTaskDesiredCount
      LaunchType: FARGATE
      LoadBalancers:
        -
          TargetGroupArn: !Ref TargetGroup
          ContainerPort: 80
          ContainerName: "app"
      NetworkConfiguration:
       AwsvpcConfiguration:
           AssignPublicIp: ENABLED
           SecurityGroups:
            - Fn::ImportValue: !Sub "${ProjectName}-ECSSecurityGroupId"
           Subnets:
            - Fn::ImportValue: !Sub "${ProjectName}-ECSSubnetId1"
            - Fn::ImportValue: !Sub "${ProjectName}-ECSSubnetId2"
      ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
      TaskDefinition: !Ref ECSTaskDefinition
```

## ECR構築
まずはDockerコンテナの実体となるイメージを配置するためのECRの構築から進めましょう。

## ECR構築：ecr.yamlの実行 ステップ1
1. テンプレートの準備で「テンプレートの準備完了」を選択
1. テンプレートの指定で「テンプレートファイルのアップロード」を選択
1. テンプレートファイルのアップロードで「ecr.yaml」(上記) をアップロード
1. [次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/07003428-b81a-c4e5-487e-dcf5a38b98d3.png)

## ECR構築：ecr.yamlの実行 ステップ2
1. スタック名に「usecase-auto-deploy-ecr」を指定する
    1. パラメータは変更不要
1. [次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/b77cdc5f-738c-9851-a133-b33f6511eed8.png)

## ECR構築：ecr.yamlの実行 ステップ3
1. すべてデフォルトのまま「次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/08fa01ba-ecdf-81e4-13c7-eed0e9288413.png)

## ECR構築：ecr.yamlの実行 ステップ4
1. そのまま「スタックの作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/f933dd48-27dc-81da-d412-ef408e2081b8.png)

## ECR構築：スタック作成の結果を確認
1. スタックの作成が成功すると、「CREATE_COMPLETE」のステータスになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/9d5fe565-054b-cd66-39b9-99498c16bf78.png)

## ECR構築：リソースの確認
1. テンプレートによって作成されたリソースを「リソース」タブで確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/1f9e9118-3659-6f0e-89a1-62711d30ab13.png)

## ECR構築：コンテナイメージのPush
1. コマンドプロンプト (ここではPowerShellを前提としています。) を開き、下記コマンドを実行します

:::note warn
Docker Desktopは起動しておいてください。
:::

```msshell
# 各自 GitHubの auto-deploy を git cloneしたディレクトリに移動します
PS cd C:\git\auto-deploy
# 各自リージョンで置換してください
PS $region=“リージョン (例 : ap-northeast-1)”
PS $account_id=ECRを作成したAWSアカウントID (例 : 1234567)
# Macの場合
$ region=“リージョン (例 : ap-northeast-1)”
$ account_id=ECRを作成したAWSアカウントID (例 : 1234567)
# ECRへログイン
PS aws ecr get-login-password --region $region | docker login --username AWS --password-stdin  "$account_id.dkr.ecr.$region.amazonaws.com"
# コンテナイメージをビルド
PS docker build -t "$account_id.dkr.ecr.$region.amazonaws.com/usecase-auto-deploy-ecr:latest" .
# コンテナイメージをECRへプッシュ
PS docker push "$account_id.dkr.ecr.$region.amazonaws.com/usecase-auto-deploy-ecr:latest"
```

:::note warn
ECRへのログイン時、必要に応じてプロファイルを指定してください。
:::

```msshell
# プロファイルの指定方法
PS aws ecr get-login-password --region $region --profile handson | ～
```

## ECR構築：ECRの確認
1. ECR > リポジトリ > usecase-auto-deploy-ecr から内容を確認することができます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349419/8595fcaf-4e0e-0b8f-4ac5-5e391433da7f.png)

## ECSの構築
続けて、直前の手順でECRにPushしたコンテナを起動するためのECSの構築を行います。

## ECSの構築：fargate.yamlの実行 ステップ1
1. テンプレートの準備で「テンプレートの準備完了」を選択
1. テンプレートの指定で「テンプレートファイルのアップロード」を選択
1. テンプレートファイルのアップロードで「fargate.yaml」(添付ファイル参照) をアップロード
1. [次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349419/ca76359d-82f7-8a2b-6f99-e8d32b171542.png)

## ECSの構築：fargate.yamlの実行 ステップ2
1. スタック名に「usecase-auto-deploy-fargate」を指定する
1. [次へ」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/c81b557a-cdad-6f8c-c411-3bd576d2d7f6.png)


## ECSの構築：fargate.yamlの実行 ステップ3
1. チェックボックスにチェックを入れ、「スタックの作成」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/f452fe18-8ccc-cff0-f76a-cbc65c169e9d.png)

## ECS構築：スタック作成の結果を確認
1. スタックの作成が成功すると、「CREATE_COMPLETE」のステータスになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/f6bb0816-cd82-f31d-080c-5db0f67f2a4e.png)

1. ECSのタスクが起動すると「RUNNING」のステータスになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2349569/a22fd3ef-555e-ac83-71e5-b6e2393f3dfd.png)

連載の第3回となる今回はここまで。

次回第4回は、自動デプロイの実行に必要な権限（IAMロール）を構築していきます。

次回：[[社内勉強会資料] CloudFormationで構築する自動デプロイ環境 ～ 4/6](https://qiita.com/toru-yamagishi/items/801612fd72347e98bcd7)