AWS CDK を使って簡単に ECS(Fargate) 環境を構築する方法


streampack の Tana です。

ECS(Fargate) 環境を手動セットアップするには、画面からぽちぽちすればある程度できるものの、
ALB を作成したり、Service, Task Definition などを設定するのが複雑です。
また、コンセプトを理解するのにも時間がかかります。
Dynamic Port Mapping を使う場合は、Security Group に inbound を登録しないといけなかったり、トライ&エラーの繰り返しです。
構築だけで疲れちゃいます。

そこで、AWS Cloud Template Kit(CDK) を使えば、20行ぐらいのコードと数回のコマンド実行で
下記のリソースと共にベストプラクティスなECS(Fargate)環境を構築してくれる方法です。

ネットワーク

- VPC
- Subnets
- Internet Gateway
- NAT gateway(EIP)
- Route Tables
- Security Group

ECS(Fargate)

- ALB(Target Group, Security Group込み)
- Cluster
- Service
- Task Definition(Container)

CDK の installや初期手順などはこちらで記載あるので割愛します。
https://github.com/awslabs/aws-cdk#getting-started

cdk init で作成されたテンプレートを元に、下記が実際に書いたコードです。
Dockerhub を使って例は他にもあるので、今回は ECR を使用します。

``` typescript:lib/demo-stack.ts
import cdk = require('@aws-cdk/cdk');
import ecs = require("@aws-cdk/aws-ecs");
import ec2 = require("@aws-cdk/aws-ec2");
import ecr = require("@aws-cdk/aws-ecr");

export class DemoStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ------------- ここを記載 --------------
    // ベストプラクティスなネットワーク環境の構築
    const vpc = new ec2.VpcNetwork(this, 'MyCdkVpc', {maxAZs: 2});
    const cluster = new ecs.Cluster(this, 'Cluster', {vpc});

    // ECSクラスタ設定(t2.medium指定)
    cluster.addDefaultAutoScalingGroupCapacity({instanceType: new ec2.InstanceType("t2.medium"), instanceCount: 1});

    // ECRを使用
    const repository = new ecr.Repository(this, 'myRepoName');

    // ECS(ALB/Service/Task Defintion/Container) 関連をまとめて構築
    new ecs.LoadBalancedEc2Service(this, 'Service', {
      cluster,
      memoryLimitMiB: 512,
      image: ecs.ContainerImage.fromEcrRepository(repository),
      containerPort: 8080,   // コンテナポート番号
      environment: {
        ENV: 'production'
      }
    });
    // ------------- ここまで --------------
  }
}
```

synth で typescript 書いたコードから CloudFormation Template(Yaml)で事前確認できます。
CloudFormationの400行ぐらいのコードを書く必要が無くなります。

```
$ cdk synth
```

```
$ cdk deploy
```

deploy は IAM Policy や Security Group のプレビューが表示され、AWSアカウントに構築開始されます。

ECRを使う場合はレポジトリまで作成してくれますが、アプリケーションの Docker Image を push してあげる必要があります。
よって、作成されたレポジトリを取得し、Image を push します。

```
# リポジトリ名を取得
$ aws ecr describe-repositories | jq -r '.repositories[].repositoryName'
----
demos-demoa-xxxxxx
```

下記のようなコマンド(shell)を実行して build&push します。

``` bash:build_push.sh
ECRID=xxxxxxxxx
ECRNAME=demos-demoa-xxxxxx

# build an your docker app
docker build -t ${ECRNAME} .
docker tag ${ECRNAME}:latest ${ECRID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECRNAME}:latest

# push
$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker push ${ECRID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECRNAME}:latest
```



成功すると、ALBのURLが払い出されますので、アクセスして確認し、結果が返ってくれば完了です！
<img width="1130" alt="demo-stack_ts_—_cdk.png" src="https://qiita-image-store.s3.amazonaws.com/0/232803/a739aa4a-2f87-f8a3-1f7d-7372da879b86.png">

結果が正しく表示されなければ、Cluster の EventのログやTaskをAWSコンソールや ecs-cli などを使って再度確認します。

```
ecs-cli ps --cluster DemoStack-Clusterxxxx-xxxxx
```

たいてい、container port が正しくなかったり、環境変数、ECRリポジトリに登録されているかなどを確認します。

削除したい場合は、destory を実行します。(事前に ECRに登録した Image の削除の必要あり)

```
$ cdk destory
```

CDKデメリットとしては細かな指定や調整したい場合は、別途それぞれコードを書く必要がありそうです。
例えば、

- コスト削減のためにも NAT Gateway(EIP) を省きたい
- コンテナの log driver や ulimits を設定したい

下記はコンテナに log driver などを追加する場合です。

``` typescript:demo-stack.ts

    const demoContainer = demoTaskDefinition.addContainer('demo-container', {
      image: ecs.ContainerImage.fromEcrRepository(repository),
      logging: new ecs.AwsLogDriver(this, 'demo-logging', { streamPrefix: 'demo-app' })
    });

    demoContainer.addUlimits(
      {
        name : ecs.UlimitName.Fsize,
        hardLimit : 10240000,
        softLimit : 10240000
      }
    )
```

細かな引数の指定や props の指定は CDK doc にてブラウザにてドキュメントを開いてくれて、検索・確認できます。

```
$ cdk docs
```

まだ、プレビューで今後改善さらに改善されそうですが、ECS(Fargate) 環境を頻繁に作ってはテストして削除するケースには最適です。

CDK workshop もあるようなので、一度見て試してみるとイメージが湧くかと思います。
https://cdkworkshop.com/