備忘録：CFnでALB/ECS/Fargate作成

# 内容
下記環境をCFnで作成した備忘録
EC2はメンテ用でここでDockerFileからImageを作成してECRリポジトリにアップロードする。
CFnでEC2作成時、ユーザーデータでコンテナイメージ作成、ECRにPushを行なっている。
ECRはプライベートリポジトリでEC2やECSからはVPCエンドポイント経由で接続する。
![スクリーンショット 2022-12-05 19.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523652/38bd02f9-cfd1-89ca-0f6f-e5b9faaef33a.png)

https://github.com/Toru-Kubota/AWS-CFn-ECS

# 構築方法
下記のスタックを順番にCFnで作成する。
|ファイル|内容|
|:---:|:---|
|1-CFn-Base.yml|VPC、サブネット、セキュリティグループなど作成|
|2-CFn-ECR.yml|プライベートECRリポジトリ作成|
|3-CFn-IAM.yml|必要なIAMロール、IAMポリシー作成|
|4-CFn-Vpce.yml|VPCエンドポイント作成|
|5-CFn-EC2.yml|管理用のEC2作成|
|6-CFn-ALB.yml|ALB、ターゲットグループ作成|
|7-CFn-ECS.yml|ECSタスク定義、ECSクラスタ、ECSサービス作成|

## 手動でやる箇所

* ```1-CFn-Base.yml```のセキュリティグループのSSH接続許可が0.0.0.0/0となっているので必要に応じて絞る
* ```5-CFn-EC2.yml```のキーペア```KeyName```を指定する
```AWS::EC2::KeyPair```で指定出来るが既存のキーペアを使用する想定のため手動
* ```5-CFn-EC2.yml```の```AWS_ACCOUNT_ID=```を指定する
* 必要に応じて```5-CFn-EC2.yml```の```Dockerfile```作成の箇所を修正する
取り敢えず```echo FROM nginx > Dockerfile```でnginxのイメージを書き込んでいるだけである。
　
 


# テンプレートファイル

## 1-CFn-Base.yml

```yml:1-CFn-Base.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Network - VPC/Subnet/RouteTable/IGW/SecurityGroup
Resources:
#### VPC Setting ####
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: Vpc

#### Subnet Setting ####
### IGW Setting ###
  Igw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: igw
  VpcgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: Vpc
      InternetGatewayId:
        Ref: Igw

### Public Subnet Setting ###
## Subnet Setting ##
  SubnetPublic1A:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId:
        Ref: Vpc
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: subnet-public-1a
        - Key: Type
          Value: Public
  SubnetPublic1C:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId:
        Ref: Vpc
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: subnet-public-1c
        - Key: Type
          Value: Public

## Route Table Setting ##
  RoutePublic1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: route-public1
  RoutePublic1Association1A:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RoutePublic1
      SubnetId:
        Ref: SubnetPublic1A
  RoutePublic1Association1C:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RoutePublic1
      SubnetId:
        Ref: SubnetPublic1C
  RoutePublic1Default:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: RoutePublic1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: Igw
    DependsOn:
      - VpcgwAttachment

### Public Subnet Setting ###
## Subnet Setting ##
  SubnetPrivate1A:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      VpcId:
        Ref: Vpc
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: subnet-private-1a
        - Key: Type
          Value: Isolated
  SubnetPrivate1C:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      VpcId:
        Ref: Vpc
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: subnet-private-1c
        - Key: Type
          Value: Isolated

## Route Table Setting ##
  RoutePrivate1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc
      Tags:
        - Key: Name
          Value: route-private1
  RoutePrivate1Association1A:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RoutePrivate1
      SubnetId:
        Ref: SubnetPrivate1A
  RoutePrivate1Association1C:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: RoutePrivate1
      SubnetId:
        Ref: SubnetPrivate1C

#### Security Group Setting ####
### for public subnet ###
  SgPublic:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for public
      GroupName: public
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: from http from my network
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
        - CidrIp: 0.0.0.0/0
          Description: Allow ssh from my network
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
      Tags:
        - Key: Name
          Value: sg-public
      VpcId:
        Ref: Vpc

### for private subnet ###
  SgPrivate:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for private
      GroupName: private
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      Tags:
        - Key: Name
          Value: sg-private
      VpcId:
        Ref: Vpc

### for VPC Endpoint ###
  SgVpce:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group of VPC Endpoint
      GroupName: vpce
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      Tags:
        - Key: Name
          Value: sg-vpce
      VpcId:
        Ref: Vpc

### Secuity Group Link ####
## Public -> Private ##
  SgPrivateFromPublic:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: HTTP for Public
      FromPort: 80
      GroupId:
        Fn::GetAtt:
          - SgPrivate
          - GroupId
      SourceSecurityGroupId:
        Fn::GetAtt:
          - SgPublic
          - GroupId
      ToPort: 80

## Public -> VPC endpoint ##
  SgVpceFromSgPublic:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: HTTPS for Public
      FromPort: 443
      GroupId:
        Fn::GetAtt:
          - SgVpce
          - GroupId
      SourceSecurityGroupId:
        Fn::GetAtt:
          - SgPublic
          - GroupId
      ToPort: 443

## Private -> VPC endpoint ##
  SgVpceFromSgPrivate:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      IpProtocol: tcp
      Description: HTTPS for Private
      FromPort: 443
      GroupId:
        Fn::GetAtt:
          - SgVpce
          - GroupId
      SourceSecurityGroupId:
        Fn::GetAtt:
          - SgPrivate
          - GroupId
      ToPort: 443

##### Output parameters for cross stack #####
Outputs:
### VPC ###
  VPCId:
    Value: !Ref Vpc
    Export:
      Name: VPCId
### Subnet ###
  SubnetPublic1AId:
    Value: !Ref SubnetPublic1A
    Export:
      Name: SubnetPublic1AId
  SubnetPublic1CId:
    Value: !Ref SubnetPublic1C
    Export:
      Name: SubnetPublic1CId
  SubnetPrivate1AId:
    Value: !Ref SubnetPrivate1A
    Export:
      Name: SubnetPrivate1AId
  SubnetPrivate1CId:
    Value: !Ref SubnetPrivate1C
    Export:
      Name: SubnetPrivate1CId
### Security Group ###
  SgPublicId:
    Value: !Ref SgPublic
    Export:
      Name: SgPublicId
  SgPrivateId:
    Value: !Ref SgPrivate
    Export:
      Name: SgPrivateId
  SgVpceId:
    Value: !Ref SgVpce
    Export:
      Name: SgVpceId
### Route Table ###
  RoutePublic1Id:
    Value: !Ref RoutePublic1
    Export:
      Name: RoutePublic1Id
  RoutePrivate1Id:
    Value: !Ref RoutePrivate1
    Export:
      Name: RoutePrivate1Id
```

## 2-CFn-ECR.yml

```yml:2-CFn-ECR.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Private ECR Repository

#### ECR Repository ####
Resources:
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: ecr-rep
      ImageScanningConfiguration:
        ScanOnPush: false
      EncryptionConfiguration:
        EncryptionType: "KMS"
```

## 3-CFn-IAM.yml

```yml:3-CFn-IAM.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create IAM Policy / IAM Role

#### IAM Policy for EC2 ####
Resources:

  IAMRoleforEC2:
    Type: AWS::IAM::Role
    Properties:
      RoleName: role-for-ec2
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - !Ref IAMPolicyforEC2
      Tags:
        - Key: Name
          Value: role-for-ec2
    DependsOn:
      - IAMPolicyforEC2
  InstanceProfileforEC2:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      InstanceProfileName: role-for-ec2
      Roles:
        - !Ref IAMRoleforEC2
  IAMPolicyforEC2:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: access-to-ecr
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - ecr:GetDownloadUrlForLayer
              - ecr:GetRepositoryPolicy
              - ecr:DescribeRepositories
              - ecr:ListImages
              - ecr:DescribeImages
              - ecr:BatchGetImage
              - ecr:InitiateLayerUpload
              - ecr:UploadLayerPart
              - ecr:CompleteLayerUpload
              - ecr:PutImage
              - ecr:GetAuthorizationToken
              - ecr:ListImages
              - ecr:BatchCheckLayerAvailability
            Resource: "*"
  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "ecs-task-exec-role"
      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

Outputs:
  InstanceProfileforEC2Id:
    Value: !Ref InstanceProfileforEC2
    Export:
      Name: InstanceProfileforEC2Id
  ECSTaskExecutionRoleId:
    Value: !Ref ECSTaskExecutionRole
    Export:
      Name: ECSTaskExecutionRoleId
```

## 4-CFn-Vpce.yml

```yml:4-CFn-Vpce.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create VPC Endpoint

#### VPC Endpoint ####
Resources:
### for ECR Api ###
  ECRApiEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.api"
      VpcId: !ImportValue VPCId
      SubnetIds:
        - !ImportValue SubnetPrivate1AId
        - !ImportValue SubnetPrivate1CId
      SecurityGroupIds:
        - !ImportValue SgVpceId
      PrivateDnsEnabled: true

### for ECR dkr ###
  ECRDkrEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
      VpcId: !ImportValue VPCId
      SubnetIds:
        - !ImportValue SubnetPrivate1AId
        - !ImportValue SubnetPrivate1CId
      SecurityGroupIds:
        - !ImportValue SgVpceId
      PrivateDnsEnabled: true

### for ECR s3 ###
  ECRS3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Gateway
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: !ImportValue VPCId
      RouteTableIds:
        - !ImportValue RoutePrivate1Id

### for cloudwatch ###
  CloudWatchEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcEndpointType: Interface
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.logs"
      VpcId: !ImportValue VPCId
      SubnetIds:
        - !ImportValue SubnetPrivate1AId
        - !ImportValue SubnetPrivate1CId
      SecurityGroupIds:
        - !ImportValue SgVpceId
      PrivateDnsEnabled: true
```

## 5-CFn-EC2.yml

```yml:5-CFn-EC2.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 environment

#### EC2 ####
Resources:
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-072bfb8ae2c884cc4
      InstanceType: t2.micro
      KeyName: xxxxxxxxxxxxxxxxxxxxxxxxxxx
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !ImportValue SubnetPublic1AId
          GroupSet:
            - !ImportValue SgPublicId
      IamInstanceProfile: !ImportValue InstanceProfileforEC2Id
      UserData: !Base64 |
        #!/bin/bash
        sudo su -
        yum update -y
        yum install -y docker
        systemctl start docker
        systemctl enable docker
        cd ~
        echo FROM nginx > Dockerfile
        AWS_ACCOUNT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxx
        aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
        docker build -t ecr-rep .
        docker tag ecr-rep:latest ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-rep:latest
        docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-rep:latest

      Tags:
        - Key: Name
          Value: Maintenance-Server
```

## 6-CFn-ALB.yml

```yml:6-CFn-ALB.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ALB and Target Group

#### ALB/Listener/Target Group ####
Resources:
### ALB ###
  ExternalALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Type: application
      Name: external-alb
      Scheme: internet-facing
      IpAddressType: ipv4
      SecurityGroups:
        - !ImportValue SgPublicId
      Subnets:
        - !ImportValue SubnetPublic1AId
        - !ImportValue SubnetPublic1CId
      Tags:
        - Key: Name
          Value: external-alb

### Target Group ###
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: elb-tg
      TargetType: ip
      Protocol: HTTP
      ProtocolVersion: HTTP1
      Port: 80
      VpcId: !ImportValue VPCId
      HealthCheckProtocol: HTTP
      HealthCheckPath: "/health"
      HealthCheckPort: traffic-port
      Matcher:
        HttpCode: "404"

### Listener ###
  Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref ExternalALB
      Port: 80
      Protocol: HTTP

#### Outputs paramerer for cross stack ####
Outputs:
  TargetGroupId:
    Value: !Ref TargetGroup
    Export:
      Name: TargetGroupId

```

## 7-CFn-ECS.yml

```yml:7-CFn-ECS.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: ECS

Resources:
### CloudWatch Log Group ###
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: "/ecs/logs/sbcntr-backend-def"

### ECS Task Definition ###
  ECStask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - FARGATE
      Cpu: 512
      Memory: 1024
      NetworkMode: awsvpc
      Family: nginx-def
      ExecutionRoleArn: !ImportValue ECSTaskExecutionRoleId
      containerDefinitions:
        - Name: nginx
          Image: !Sub "${AWS::AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/ecr-rep:latest"
          MemoryReservation: 512
          PortMappings:
            - containerPort: 80
              protocol: tcp
          Cpu: 256
          Essential: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref ECSLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: ecs

### ECS Cluster ###
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: ecs-cluster
      ClusterSettings:
        -
          Name: containerInsights
          Value: enabled

### ECS Service ###
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      LaunchType: FARGATE
      TaskDefinition: !Ref ECStask
      Cluster: !Ref ECSCluster
      ServiceName: ecs-service
      DesiredCount: 1
      EnableECSManagedTags: True
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !ImportValue SgPrivateId
          Subnets:
            - !ImportValue SubnetPrivate1AId
            - !ImportValue SubnetPrivate1CId
      HealthCheckGracePeriodSeconds: 60
      LoadBalancers:
        -
          ContainerName: nginx
          ContainerPort: 80
          TargetGroupArn: !ImportValue TargetGroupId
```