あらすじ
弊社ではインフラ構築に一部 Terraform を利用しています。しかし Terraform はアプリエンジニアがちょっとした環境を用意するには手軽さに欠けます。そういうわけでか私含め弊社のアプリエンジニアは AWS のウェブコンソールでリソース作成をすることが多いのが現状です。
しかし IaC 化されていないと構成の再現性が保証できませんし、スクラップアンドビルドが手軽に行えなえず構成の改善がしにくくなります。
そこで AWS CDK を試してみたところ学習コストも簡単に扱えそうだったので社内で布教しようと思いました。1
What is AWS CDK
概要
AWS CDK とは AWS Cloud Development Kit の略で、プログラムング言語を用いて AWS リソースの定義や作成を行うことのできるフレームワークです。
AWS にはリソースの定義を管理しリソースの生成破棄を自動化してくれる CloudFormation というサービスがあります。CloudFormation ではリソースを CloudFormation テンプレートと呼ばれる JSON または YAML ファイルによって定義します。
CDK は雑に言ってしまえば CloudFormation テンプレートを作成しリソースの生成破棄を CloudFormation に委譲する道具です。
CDK を用いると CloudFormation テンプレートに相当するものをプログラミング言語における構造体やオブジェクトとして表現できます。
このときデフォルトの設定値を省略できるので CloudFormation テンプレートを直接作成するより記述量が少なくなる点が CDK のメリットの一つです。
構成
CDK のプロジェクトは以下のように App, Stack, Construct という単位で構成されます。
それぞれの概念について簡単に説明します。
AWS CDK 公式ドキュメント より引用
App
ひとつの CDK のプロジェクト (と理解しています)。
一つ以上の Stack を含みます。
Stack
リソースのデプロイの単位です。
一つ以上の Construct を含みます。
Construct
AWS のリソースの単位です。
EC2 インスタンスや S3 バケットなどがそうです。
実際に CDK を触る
ECS の Fargate サービスとして nginx コンテナを動作させることにします。
ここでは VPC と ALB も新規に作成しますが既存のものを使い回すこともできます。
前提条件
- npm 10.13.0 or later
- Go 1.18 or later
- AWS アカウントと適切なロール
CDK のインストール
詳しくは公式のページ参照 (https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
npm install -g aws-cdk色々準備
適当なディレクトリを作成して進めます。ここでは cdk-sample としました。
mkdir cdk-sample
cd cdk-sample
cdk init --language go必要なライブラリをとってきます。
go getブートストラップ
CDK を利用するための IAM ロールやファイルを格納するための S3 バケットなどが作成されます。
cdk bootstrapコードを書く
./
├── .git
├── .gitignore
├── README.md
├── cdk-sample.go
├── cdk-sample_test.go
├── cdk.context.json
├── cdk.json
├── cdk.out
├── go.mod
└── go.sumcdk-sample.go を以下のように編集します。
package main
import (
"github.com/aws/aws-cdk-go/awscdk/v2"
ec2 "github.com/aws/aws-cdk-go/awscdk/v2/awsec2"
ecs "github.com/aws/aws-cdk-go/awscdk/v2/awsecs"
elb "github.com/aws/aws-cdk-go/awscdk/v2/awselasticloadbalancingv2"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
)
type CdkSampleStackProps struct {
awscdk.StackProps
}
func NewCdkSampleStack(scope constructs.Construct, id string, props *CdkSampleStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
// Stack の作成 (今回は 1 つだけ Stack を作成します)
stack := awscdk.NewStack(scope, &id, &sprops)
// 以下 Stack に Construct を作成していきます作成
// VPC の作成
vpc := ec2.NewVpc(stack, jsii.String("sampleVpc"), &ec2.VpcProps{
VpcName: jsii.String("sample-vpc"),
MaxAzs: jsii.Number(3),
SubnetConfiguration: &[]*ec2.SubnetConfiguration{
// public subnet を一つ作成
{
Name: jsii.String("sampleVpcPublicSubnet"),
SubnetType: ec2.SubnetType_PUBLIC,
CidrMask: jsii.Number(24),
},
},
})
// ECS クラスタの作成
cluster := ecs.NewCluster(stack, jsii.String("sampleEcsCluster"), &ecs.ClusterProps{
ClusterName: jsii.String("sample-cluster"),
Vpc: vpc,
})
// タスク定義の作成
taskDef := ecs.NewFargateTaskDefinition(stack, jsii.String("sampleNginxTask"), nil)
taskDef.AddContainer(jsii.String("sampleNginxContainer"), &ecs.ContainerDefinitionOptions{
Image: ecs.ContainerImage_FromRegistry(jsii.String("nginx"), nil),
PortMappings: &[]*ecs.PortMapping{{
ContainerPort: jsii.Number(80),
Protocol: ecs.Protocol_TCP,
}},
})
// ECS サービスの作成
service := ecs.NewFargateService(stack, jsii.String("sampleFargateService"), &ecs.FargateServiceProps{
Cluster: cluster,
TaskDefinition: taskDef,
AssignPublicIp: jsii.Bool(true),
})
// ALB の作成
alb := elb.NewApplicationLoadBalancer(stack, jsii.String("sampleAlb"), &elb.ApplicationLoadBalancerProps{
LoadBalancerName: jsii.String("sample-alb"),
Vpc: vpc,
InternetFacing: jsii.Bool(true),
})
listener := alb.AddListener(jsii.String("sampleListener"), &elb.BaseApplicationListenerProps{
Port: jsii.Number(80),
})
// ECS サービスと ALB のリスナーを紐付ける
service.RegisterLoadBalancerTargets(&ecs.EcsTarget{
ContainerName: service.TaskDefinition().DefaultContainer().ContainerName(),
ContainerPort: service.TaskDefinition().DefaultContainer().ContainerPort(),
NewTargetGroupId: jsii.String("sampleTagetGroup"),
Listener: ecs.ListenerConfig_ApplicationListener(listener, &elb.AddApplicationTargetsProps{
Protocol: elb.ApplicationProtocol_HTTP,
}),
})
return stack
}
func main() {
defer jsii.Close()
// App の作成
app := awscdk.NewApp(nil)
NewCdkSampleStack(app, "CdkSampleStack", &CdkSampleStackProps{
awscdk.StackProps{
Env: &awscdk.Environment{
Account: jsii.String("<AWSアカウントID>"),
Region: jsii.String("<リージョン>"),
},
},
})
app.Synth(nil)
}各々の構造体や関数の定義は CDK の公式ドキュメントを参照ください。
例えば VPC の設定を持つ ec2.VpcProps という構造体のドキュメントは以下です。設定しなかったもののどのような値がデフォルト値として設定されるか確認してみてください。
https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ec2.VpcProps.html
作成されるテンプレートの確認
以下のコマンドで CDK が作成する CloudFormation テンプレートを確認できます。
cdk synthデプロイ
実際にリソースを生成します。
cdk deploy動作確認
作成した ALB のドメイン名を確認して Nginx のデフォルトページが表示されるか確認してみましょう。
ALB には sample-alb と名前を付けたので、次のコマンドでドメイン名等の情報を確認できます。
aws elbv2 describe-load-balancers --names "sample-alb"破棄
リソースを破棄します。
cdk destroyまとめ
かなり簡単にリソースが作成できることがわかったのではないでしょうか。デフォルトの設定値で良ければ記述量もかなり少なく済ませることができますし、リソースの生成破棄も簡単です。開発環境目的では必要なときだけリソースが動いていればよいという状況は多いですが、そうったときには特に便利ではないかと思います。
参考記事
- https://docs.aws.amazon.com/cdk/v2/guide/home.html
- AWS 公式
- https://zenn.dev/yamatatsu/books/aws-cdk-documentation-jp/viewer/17-concepts-bootstrapping
- https://qiita.com/matsunao722/items/f18114c37f1667d171c4
- 2022-08-09 に CDK for Terraform が GA になりました。こっちも触ってみたいです。https://aws.amazon.com/blogs/opensource/announcing-cdk-for-terraform-on-aws/↩
