ECR、ECSをCloudFormation化してみた

# はじめに
この記事はコンテナ勉強用として試したことまとめたものです。
今回は今まで作成してきたECR、ECSをCloudFormationでソースコード化してみました。

# CloudFormationとは
AWSサービスのインフラ構成をコードに記述しテンプレート化し、テンプレートから環境構築できるサービス。VPCやEC2、S3など様々なサービスをプロビジョニングが可能である。テンプレートファイル形式はJSON、YAMLをサポートしている。CloudFormation自体に費用は無料。略してCFnと呼ばれる。

# スタック
* テンプレートからプロビジョニングされるリソースの集合体のことを指す。
* スタック単位リソースを作成することが可能。
* スタックを削除すると紐づくリソースをまるっと削除できる。

# テンプレートの要素
* CloudFormationは以下の要素から構成される。Resourcesは必須。
    * AWSTemplateFormatVersion（テンプレートのバージョン）
    * Description（テンプレートの説明）
    * Parameters（CloudFormation実行時のパラメータ設定）
    * Mappings（キーに対応する値のマッピング）
    * Conditions（作成条件を記載）
    * Resources（スタック構成するリソースを定義）
    * Outputs（スタック構築後に出力する値）

# ECRを作成するCloudFormation
* [ECRのCloudFormationリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html)を参考にyamlを記載する。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECR Stack Test

Resources:
  ########################################################
  ### ECR Repository
  ########################################################
  TestEcrPoc:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: test-ecr-repo
```

# ECSクラスターを作成するCloudFormation
* [ECSクラスターのCloudFormationリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html)を参考にyamlを記載する。
* このテンプレートの値をアウトプットして次に作成するテンプレートに読み込ませるように設定する。
* ```!Ref```は指定したパラメータやリソースの値を返す。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECS Task Test

Resources:
  ########################################################
  ### ECS Cluster
  ########################################################
  TestEcsCluster:
    Type: "AWS::ECS::Cluster"
    Properties:
      ClusterName: "test-ecs-cls"
 
  ########################################################
  ###  ECS LogGroup
  ########################################################
  TestEcsLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: "/ecs/logs/test-ecs-group"

  ########################################################
  ###  Target Group
  ########################################################
  TestTargetGroup:
    Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
    Properties:
      VpcId: "vpc-11111111111111111"
      Name: "test-target-group"
      Protocol: HTTP
      Port: 80
      TargetType: ip
 
  ########################################################
  ###  Internet ALB
  ########################################################
  TestInternetAlb:
    Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
    Properties:
      Name: "test-internet-alb"
      Tags:
        - Key: Name
          Value: "test-internet-alb"
      Scheme: "internet-facing"
      LoadBalancerAttributes:
        - Key: "deletion_protection.enabled"
          Value: false
        - Key: "idle_timeout.timeout_seconds"
          Value: 60
        - Key: "access_logs.s3.enabled"
          Value: true
        - Key: "access_logs.s3.bucket"
          Value: "test-s3b-alb-logs"
      SecurityGroups:
        - "sg-11111111111111111"
      Subnets:
        - "subnet-11111111111111111"
        - "subnet-22222222222222222"
 
  TestAlbListener:
    Type: "AWS::ElasticLoadBalancingV2::Listener"
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref TestTargetGroup
          Type: forward
      LoadBalancerArn: !Ref TestInternetAlb
      Port: 80
      Protocol: HTTP
  
Outputs:
  TestEcsCluster:
    Value: !Ref TestEcsCluster
    Export:
      Name: TestEcsCluster

  TestEcsLogGroup:
    Value: !Ref TestEcsLogGroup
    Export:
      Name: TestEcsLogGroup

  TestTargetGroup:
    Value: !Ref TestTargetGroup
    Export:
      Name: TestTargetGroup

  TestAlbListener:
    Value: !Ref TestAlbListener
    Export:
      Name: TestAlbListener
```

# ECSサービスを作成するCloudFormation
* [ECSサービスのCloudFormationリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html)を参考にyamlを記載する。
* ```!ImportValue```で前に実行したスタックの値を利用する。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECS Task Test

Resources:
  ########################################################
  ###  ECS Task Role
  ########################################################
  TestEcsTaskExecRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: "test-ecs-task-exec-role-policy"
      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
 
  ########################################################
  ###  ECS TaskDefinition
  ########################################################
  TestEcsTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    Properties:
      Cpu: 256
      ExecutionRoleArn: !Ref TestEcsTaskExecRole
      Family: "test-ecs-task"
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ### ContainerDefinitions
      ContainerDefinitions:
        - Name: "test-ecs-container"
          Image: "111111111111.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-repo:latest"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !ImportValue TestEcsLogGroup
              awslogs-region: !Ref "AWS::Region"
              awslogs-stream-prefix: "test"
          MemoryReservation: 128
          PortMappings:
            - HostPort: 80
              Protocol: tcp
              ContainerPort: 80
 
  ########################################################
  ###  ECS Service
  ########################################################
  TestEcsService:
    Type: "AWS::ECS::Service"
    Properties:
      Cluster: !ImportValue TestEcsCluster
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
        -
          TargetGroupArn: !ImportValue TestTargetGroup
          ContainerPort: 80
          ContainerName: "test-ecs-container"
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
            - "sg-11111111111111111"
          Subnets:
            - "subnet-11111111111111111"
            - "subnet-22222222222222222"
      ServiceName: "test-ecs-service"
      TaskDefinition: !Ref TestEcsTaskDefinition
```

# まとめ
* CloudFormationでコード化することで環境構築が楽になる。積極的にインフラをコード化していきたい。
* 基本的にAWSのドキュメントで公開されているリファレンスを参考に組み立てていく。
* JSON形式よりもYAML形式で記載するほうがコード化しやすい。

# 参考
* https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html#w2ab2c17c15b9