🦁

AWS CDKでFargateなECSクラスタを30分で立てる

2020/11/04に公開

はじめに

AWS Dev Dayを色々見ていたら、やけにCDKを推している感じがあり、今Terraformで管理しているインフラもCDKに移行できたら開発チームでもインフラ管理しやすくなるかなぁと思い、ちょっと試してみた感じです。

AWS CDKとは

2019年7月にGAとなった、AWSリソースを既存の開発言語で定義することができるフレームワークです。
GA当初は2種類のみだったサポート言語も、2020年11月現在では

  • TypeScript
    • JavaScript
  • Python
  • Java
  • C#

の5種類となります。

バックエンドにはCloudFormationが作られるので、Web上のAWSコンソールからもGUIを用いて確認することができます。

aws-cdkコマンドをインストール

早速やっていきます。

プロジェクトの初期化や、テンプレートの確認、実際のデプロイなどにはAWS CDKで用意されたコマンドが必要になるので、まずはこちらをインストールしていきます。

今回は対象のマシンはMac前提で書いていきます。

公式にはnpmでのインストールが書いてあるんですが、どうも色んなマシンで開発をしていると、どのプロジェクトがnpmでどのプロジェクトがyarnなのか分からなくなることがあり、brewとかにないのかなと思ったらあったので今回はbrewでインストールします。

もちろん公式の通り、npmでも(yarnでも)構わないと思います。

$ brew install aws-cdk

https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install

init

インストールができたところで、プロジェクトの初期化を行っていきます。

とは言えまだプロジェクトのディレクトリすらないので作っておきます。

$ mkdir cdk-sample && cd $_

ディレクトリを移動したところで、まずは cdk init だけ打って説明を見てみましょう。

$ cdk init
  Available templates:
  * app: Template for a CDK Application
     └─ cdk init app --language=[csharp|fsharp|java|javascript|python|typescript]
  * lib: Template for a CDK Construct Library
     └─ cdk init lib --language=typescript
  * sample-app: Example CDK Application with some constructs
     └─ cdk init sample-app --language=[csharp|fsharp|java|javascript|python|typescript]

テンプレートを選べと。

app lib sample-app から選ぶようです。

cdk init --help で見ると色々とオプションはあるんですが、ここに示されているのは --language だけなのでまずはシンプルに行ってみましょう。

$ cdk init sample-app --language=typescript
Applying project template sample-app for typescript
# Welcome to your CDK TypeScript project!

You should explore the contents of this project. It demonstrates a CDK app with an instance of a stack (`CdkSampleStack`)
which contains an Amazon SQS queue that is subscribed to an Amazon SNS topic.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

Initializing a new git repository...
Executing npm install...
~~~~~~

✅ All done!

こんな感じで完了しました。

今後使っていくであろうコマンドも表示されているので、さっと目を通しておきます。

 * `npm run build`   compile typescript to js
 * `npm run watch`   watch for changes and compile
 * `npm run test`    perform the jest unit tests
 * `cdk deploy`      deploy this stack to your default AWS account/region
 * `cdk diff`        compare deployed stack with current state
 * `cdk synth`       emits the synthesized CloudFormation template

一旦デプロイしてみる

デプロイ手順は本当に簡単で、コマンドを2つほど叩くだけです。

まずはこちら。

$ cdk synth

こちらは現在の定義でどんなリソースがどう作られるかを確認するコマンドです。

出力結果は長いので全部は載せられませんがこんな感じ。

$ cdk synth
Resources:
  CdkSampleQueue5EE69D51:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    Metadata:
      aws:cdk:path: CdkSampleStack/CdkSampleQueue/Resource
  CdkSampleQueuePolicyF9F8BD75:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: sqs:SendMessage
            Condition:
~~~~~

前半部分だけですが、AWS SQSのキューとそこで使われるポリシーが作られるようです。

その後、リソースに問題はない(サンプルなので問題あるかどうかまでわかんないですけど)ので、いよいよデプロイします。

コマンドは cdk deploy です。

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬───────────────────────┬────────┬─────────────────┬───────────────────────────┬───────────────────────────────────────────────────────┐
│   │ Resource              │ Effect │ Action          │ Principal                 │ Condition                                             │
├───┼───────────────────────┼────────┼─────────────────┼───────────────────────────┼───────────────────────────────────────────────────────┤
│ + │ ${CdkSampleQueue.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                        │
│   │                       │        │                 │                           │   "aws:SourceArn": "${CdkSampleTopic}"                │
│   │                       │        │                 │                           │ }                                                     │
└───┴───────────────────────┴────────┴─────────────────┴───────────────────────────┴───────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? 

「変更内容合ってるかー?」と聞いてくれるので y を。

CdkSampleStack: deploying...
CdkSampleStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (6/6)


 ✅  CdkSampleStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:611036819457:stack/CdkSampleStack/d5108ff0-1de9-11eb-8873-0a4084594d18

そうしたらこのようにデプロイできたよーと教えてくれます。

ここでWebコンソールで確認してみると、確かに出来ているようです。

sqs

stack

削除しておこう

このアプリケーションを残しておく意味もないので消しておきましょう。

$ cdk destroy
Are you sure you want to delete: CdkSampleStack (y/n)?

またもやちゃんと確認を取ってくれます。親切ですね。 y です。

CdkSampleStack: destroying...
0:38:27 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | CdkSampleStack
0:38:31 | DELETE_IN_PROGRESS   | AWS::SQS::Queue        | CdkSampleQueue

 ✅  CdkSampleStack: destroyed

今回もちゃんと絵文字を使って完了を教えてくれました。

FargateなECSを作ろう

さてここからが本題のリソースです。長かった。

AWS CDKは、普段開発している言語で書けるということは、勝手に欲しいライブラリがimportされるようなことはありません。

なので、欲しい機能はそれぞれインストールしてimportしてやる必要があります。

今回必要となるのは

  • @aws-cdk/aws-ec2
  • @aws-cdk/aws-ecs
  • @aws-cdk/aws-ecs-patterns

なので、インストールしていきます。

$ npm i @aws-cdk/aws-ec2 @aws-cdk/aws-ecs @aws-cdk/aws-ecs-patterns

もう一回プロジェクトを作る

別のディレクトリを作り、再度 cdk init を行います。

今度はサンプルアプリではないので、テンプレートは app を選択しておきましょう。

$ cdk init app --language=typescript

ファイルをいじってみる

基本的にいじるのは lib/ 配下にある ○○-stack.ts です。
○○ の部分にはディレクトリ名が入ってきます。

初期の内容はこちら。

ディレクトリを cdk-fargate で作っていたのでその名前が入ってますね。

lib/cdk-fargate-stack.ts
import * as cdk from '@aws-cdk/core';

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

    // The code that defines your stack goes here
  }
}

必要なライブラリをimport

import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns';

念の為説明をしておくと、

  • @aws-cdk/aws-ec2
    • VPCを扱う型がこのパッケージにある
  • @aws-cdk/aws-ecs
    • ECSの基本的な型が全て入っている
  • @aws-cdk/aws-ecs-patterns
    • ECSのクラスタを立てるにあたって必要なものをまるっと作ってくれる汎用パターン集

という感じ。

編集

ここからがコードです。

初期化されたコードのうち、

// The code that defines your stack goes here

となっている箇所を、

const vpc = new ec2.Vpc(this, 'Vpc', {})

const cluster = new ecs.Cluster(this, 'Cluster', {
  vpc,
})

const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  },
});

loadBalancedFargateService.targetGroup.configureHealthCheck({
  path: "/custom-health-path",
});

で置き換えます。

TypeScriptのコードではあるので、読めば大体わかるかとは思うんですが、

const vpc = new ec2.Vpc(this, 'Vpc', {})

VPCを作ります。(第3引数は props? なので実は要らないですねこれ)

const cluster = new ecs.Cluster(this, 'Cluster', {
  vpc,
})

作ったVPC内にECSクラスタを作ります。

const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', {
  cluster,
  memoryLimitMiB: 1024,
  cpu: 512,
  taskImageOptions: {
    image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
  },
});

loadBalancedFargateService.targetGroup.configureHealthCheck({
  path: "/custom-health-path",
});

ここが aws-ecs-patterns パッケージのすごいところで、これだけで

  • ALB
  • Target Group
  • FargateのECS Service
  • TaskDefinition
  • 各種ロールやポリシー

などを一気に作ってくれます。

1つずつ作っていくと、気の遠くなるほど・・・ではないですが、それなりにめんどくさい各種リソースをまとめて作ってくれるのはありがたいですよね。

cdk synth

さて、それでは先程と同じように cdk synth を実行してみましょう。

$ cdk synth
Resources:
  Vpc8378EB38:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: CdkFargateStack/Vpc
    Metadata:
      aws:cdk:path: CdkFargateStack/Vpc/Resource
  VpcPublicSubnet1Subnet5C2D37C4:
    Type: AWS::EC2::Subnet
~~~~~~~

今度は先程とは比べ物にならないほど大量の出力があると思います。

また最初の少しの部分だけ抜き出してみましたが、今度はVPC周りのリソース定義があるのがわかります。

cdk deploy

synthで問題無さそう(コード上は)なので、思い切ってdeployします。

$ cdk deploy
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬──────────────────────────────────────┬────────┬───────────────────────────────────────┬──────────────────────────────────────┬───────────┐
│   │ Resource                             │ Effect │ Action                                │ Principal                            │ Condition │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${Service/TaskDef/ExecutionRole.Arn} │ Allow  │ sts:AssumeRole                        │ Service:ecs-tasks.amazonaws.com      │           │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${Service/TaskDef/TaskRole.Arn}      │ Allow  │ sts:AssumeRole                        │ Service:ecs-tasks.amazonaws.com      │           │
├───┼──────────────────────────────────────┼────────┼───────────────────────────────────────┼──────────────────────────────────────┼───────────┤
│ + │ ${Service/TaskDef/web/LogGroup.Arn}  │ Allow  │ logs:CreateLogStream                  │ AWS:${Service/TaskDef/ExecutionRole} │           │
│   │                                      │        │ logs:PutLogEvents                     │                                      │           │
└───┴──────────────────────────────────────┴────────┴───────────────────────────────────────┴──────────────────────────────────────┴───────────┘
Security Group Changes
┌───┬──────────────────────────────────────────┬─────┬────────────┬──────────────────────────────────────────┐
│   │ Group                                    │ Dir │ Protocol   │ Peer                                     │
├───┼──────────────────────────────────────────┼─────┼────────────┼──────────────────────────────────────────┤
│ + │ ${Service/LB/SecurityGroup.GroupId}      │ In  │ TCP 80     │ Everyone (IPv4)                          │
│ + │ ${Service/LB/SecurityGroup.GroupId}      │ Out │ TCP 80${Service/Service/SecurityGroup.GroupId} │
├───┼──────────────────────────────────────────┼─────┼────────────┼──────────────────────────────────────────┤
│ + │ ${Service/Service/SecurityGroup.GroupId} │ In  │ TCP 80${Service/LB/SecurityGroup.GroupId}      │
│ + │ ${Service/Service/SecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4)                          │
└───┴──────────────────────────────────────────┴─────┴────────────┴──────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)? 

y を入力します。

CdkFargateStack: deploying...
CdkFargateStack: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (39/39)



 ✅  CdkFargateStack

Outputs:
CdkFargateStack.ServiceLoadBalancerDNSEC5B149E = CdkFa-Servi-STLONX2XXEUQ-1815614499.ap-northeast-1.elb.amazonaws.com
CdkFargateStack.ServiceServiceURL250C0FB6 = http://CdkFa-Servi-STLONX2XXEUQ-1815614499.ap-northeast-1.elb.amazonaws.com

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:643526083873:stack/CdkFargateStack/7c55caa0-1e7b-11eb-8a47-0a62df4444de

しばらく(5分くらい?ちょっと覚えてないですが)待つと完了します。

CloudFormationで見てみる

AWSのWebコンソールで見てみましょう。

cloudformation

これだけのリソースが上記のたったあれだけのコードでできてしまいました。

ついでにアプリケーションの確認も

ALBのDNS名にあるドメインにアクセスしてみましょう。

cdk-app

こんな感じのが見えているかと思います。

ちゃんとできてますね。

cdk destroy

最後は同じようにきれいにしておきます。

$ cdk destroy

まとめ

スタックなどのCloudFormationの概念や、ConstructやscopeといったCDKの概念など、知らないと今後いじっていくには辛そうな部分はあるものの、たとえ知らなくてもこれだけ書けば動くものができてしまうというこの体験はさすがだなぁといった印象です。

同じInfrastructure as Codeと言われる世界でも、Terraformや直接CloudFormationをいじるのと違い、普段開発を行っている言語で書けるという意味では、本当の意味での as Code な気もします。

インフラエンジニアだけが触るようであればTerraformなどでも良いと思うんですが、プロダクトの成長に合わせてもっとダイナミックに変化するインフラを構築していくのであれば、アプリケーションエンジニアが触りやすいコードで定義できるAWS CDKという選択肢はアリなのではないかと思いました。

仕事では大半のインフラがTerraform管理下にあるんですが、今後ちょっとずつCDKに置き換えて行こうかなと思ってます。

参考

Discussion