CDKでALBとECS(EC2)クラスタを作成し、ecs-cliでDocker Composeの構成をデプロイする

awsdockercontainer

以前、CloudFormationでLBなしのECS(EC2)最小構成を構築したが、今回はALBのTargetGroupまでをCDKで作成し、Serviceのデプロイをecs-cliで行う。 EC2をECSクラスタに登録する際の echo ECS_CLUSTER=<cluster_name> >> /etc/ecs/ecs.config といった処理は クラスタのAsgCapacityProviderにASGを指定すると追加される

ECS(EC2)のCloudFormation最小構成 - sambaiz-net

import * as cdk from '@aws-cdk/core'
import * as ecr from '@aws-cdk/aws-ecr'
import * as ecs from '@aws-cdk/aws-ecs'
import * as ec2 from '@aws-cdk/aws-ec2'
import * as iam from '@aws-cdk/aws-iam'
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'
import * as autoscaling from '@aws-cdk/aws-autoscaling'
import { Cluster } from '@aws-cdk/aws-ecs'

export class ECSEC2Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    this.createECRRepository('test')
    this.createLogGroup("test")
    const ecsCluster = this.createECSCluster('test')
    this.createASGForCluster(ecsCluster)
    this.createALB('test', ecsCluster.vpc)
  }
  createECRRepository(name: string) {
    const repository = new ecr.Repository(this, 'ECRRepository', {
      repositoryName: name
    })
    return repository
  }

  createLogGroup(name: string) {
    return new logs.LogGroup(this, 'LogGroup', {
      logGroupName: name,
      retention: RetentionDays.TWO_WEEKS 
    })
  }

  createECSCluster(name: string) {
    const cluster = new ecs.Cluster(this, 'ECSCluster', {
      clusterName: name,
    })
    return cluster
  }

  createASGForCluster(ecsCluster: Cluster) {
    const securityGroup = new ec2.SecurityGroup(this, 'EC2SecurityGroup', {
      vpc: ecsCluster.vpc,
    })
    securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.allTcp())
    const autoScalingGroup = new autoscaling.AutoScalingGroup(this, 'ASG', {
      vpc: ecsCluster.vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
      machineImage:  ecs.EcsOptimizedImage.amazonLinux2(),
      minCapacity: 1,
      maxCapacity: 1,
      securityGroup
    })
    ecsCluster.addAsgCapacityProvider(new ecs.AsgCapacityProvider(this, 'ASGCapacityProvider', {
      autoScalingGroup
    }))
    autoScalingGroup.role.addManagedPolicy(
      iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy'))
    return autoScalingGroup
  }

  createALB(name: string, vpc: ec2.IVpc) {
    const securityGroup = new ec2.SecurityGroup(this, 'EC2SecurityGroup', {
      vpc: ecsCluster.vpc,
    })
    securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80))
    const alb = new elbv2.ApplicationLoadBalancer(this, `ALB`, {
      vpc: vpc,
      internetFacing: true,
      securityGroup
    })
    const target = new elbv2.ApplicationTargetGroup(this, 'TargetGroup', {
      targetGroupName: name,
      targetType: elbv2.TargetType.INSTANCE,
      vpc: vpc,
      protocol: elbv2.ApplicationProtocol.HTTP,
      port: 80,
      healthCheck: {
        path: "/ping"
      },
      deregistrationDelay: cdk.Duration.seconds(5)
    })
    new elbv2.ApplicationListener(this, 'Listener', {
      loadBalancer: alb,
      defaultTargetGroups: [target],
      protocol: elbv2.ApplicationProtocol.HTTP,
      port: 80,
    })
    return alb
  }
}

CloudWatchにログを送るためのawslogs log driverの設定を行う。 ECSコンソールのLogsにログを表示するにはprefixを指定する必要がある。

services:
  app:
    ...
    logging:
      driver: awslogs
      options:
        awslogs-group: test
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: app

docker-compose.ymlがサポートしていないパラメータはecs-params.ymlに記述する。

version: 1
task_definition:
  docker_volumes:
    - name: log
      scope: shared
      autoprovision: true

ecs-cli compose service upでdocker-compose.ymlからサービスを作成する。

$ ecs-cli compose service up --target-groups "targetGroupArn=${TARGET_GROUP_ARN},containerName=app,containerPort=8080" --launch-type EC2 --cluster ${CLUSTER_NAME}

もしLBが503か504を返す場合、ECSのTaskがエラーで落ちていないか、TargetGroupのTargetにEC2インスタンスが紐づいているか、HealthCheckが通っているかといったことを確認する。