AWS CDKでFargateなECSクラスタを30分で立てる
はじめに
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
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コンソールで確認してみると、確かに出来ているようです。
削除しておこう
このアプリケーションを残しておく意味もないので消しておきましょう。
$ 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
で作っていたのでその名前が入ってますね。
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コンソールで見てみましょう。
これだけのリソースが上記のたったあれだけのコードでできてしまいました。
ついでにアプリケーションの確認も
ALBのDNS名にあるドメインにアクセスしてみましょう。
こんな感じのが見えているかと思います。
ちゃんとできてますね。
cdk destroy
最後は同じようにきれいにしておきます。
$ cdk destroy
まとめ
スタックなどのCloudFormationの概念や、ConstructやscopeといったCDKの概念など、知らないと今後いじっていくには辛そうな部分はあるものの、たとえ知らなくてもこれだけ書けば動くものができてしまうというこの体験はさすがだなぁといった印象です。
同じInfrastructure as Codeと言われる世界でも、Terraformや直接CloudFormationをいじるのと違い、普段開発を行っている言語で書けるという意味では、本当の意味での as Code
な気もします。
インフラエンジニアだけが触るようであればTerraformなどでも良いと思うんですが、プロダクトの成長に合わせてもっとダイナミックに変化するインフラを構築していくのであれば、アプリケーションエンジニアが触りやすいコードで定義できるAWS CDKという選択肢はアリなのではないかと思いました。
仕事では大半のインフラがTerraform管理下にあるんですが、今後ちょっとずつCDKに置き換えて行こうかなと思ってます。
参考
- 公式ワークショップ
- APIリファレンス(aws-ecs-patterns)
- 30行くらいで作るはじめてのインフラ構築
Discussion