記事の趣旨
最近、CDK を少し使う必要があったので、使い方を調べました。
今回は調べた CDK の使い方についてまとめます。一応、無量利用枠に収まる範囲、無課金の範囲でインフラを構築するので、記事に沿って作業してもお金はかからないと思うので、その辺気にされる方の参考になればと思います。
記事の対象者は:
- AWS の基礎的な理解がある
- CDK を使ってみたい
- でもお金は掛けたくない
この記事で作成するインフラ構成
インフラの内容:
- クライアントからのリクエストを ALB が受け付ける
- ALB と ASG を紐づけて、負荷分散する
- ASG のキャパシティ設定により、EC2 インスタンスの数がスケールできる
AWS CDK とは
インフラを作成する前に、 CDK について簡単にご説明。
CDK はプログラミング言語で、AWS のインフラストラクチャを構築するためのツールです。
CDK でどんなリソースを作るにしても、メソッドに渡す引数は基本的に以下の3つです。
- 第一引数:
scope(this)
- 第二引数:
Id
- 第三引数:
props
// 例
new s3.Bucket(this, 'MyFirstBucket', {
versioned: true
});
詳細な解説は、以下の公式ドキュメントをご確認ください。
(Add an Amazon S3 bucket
の見出しの前後に引数について書かれています。)
CDK によって作成されるリソース郡は、スタックと呼ばれ、this
はそのスタックを表しています。(たぶん)
そして、特殊なインフラ構築をしない限り、この引数は 常に this
です。
Id
は、基本的には任意の文字列。
props
は、作成するリソースによって様々なオプションを設定するオブジェクトです。
S3 バケットなら、バージョニングを有効にする versioned: true
とかが設定できます。
以上のように、三つの引数を渡す、ということが統一されているので、作成するリソースに合わせて、引数について悩む必要もなく、他人が書いた CDK でもすぐに理解できます(作成するリソースの AWS サービスの理解がある前提ですが)
AWS CDK を何故使うか
インフラのコード化により、AWS上に何が作成されるのかが分かりやすい(いわゆる Infrastructure as Code)
GUIのAWSコンソールで各種インフラリソースを作成するのは簡単ですが、
作成するリソースが増えるにつれて、システムの全体像を把握するのが難しくなります。
例えば、 以下のような作業をコンソールで簡単にできますが、全体の見通しはあまり良くありません。
- VPC・サブネットを用意して、
- Route53 で DNS を用意して、
- EC2 を複数台立てて、
- ロードバランサーを作成して負荷分散して、
- セキュリティグループでインターネットの通信を制限して、
- RDS でデータベースを作成して・・・etc
コンソール画面ですべて行うと、以下の疑問に対する答えを確認するために、いろいろな画面を行ったり来たりする羽目になります。
- セキュリティグループのインバウンド設定の内容はどんな?
- EC2インスタンスのユーザーデータは、どんな内容にしてたっけ?
- IAMロールは適切に設定されてる?
これでは、うまくシステムが動かない・接続できないなどの問題が起こったときに、デバッグに時間がかかります。
CDK でコード化すると、1箇所で上記の疑問すべてが解決します。
Typescript の型補完の恩恵
AWS のリソースを作成する際、オプション項目をオブジェクトの引数で渡しますが、typescript の補完が効くので何を渡せばいいか、エディタ上で把握できます。
上記のスクショだと、以下のことがわかります。
-
addTargets()
のメソッドの第2引数のオブジェクトに、targets?
が渡せる -
targets?
は ? がついているので、任意の引数である -
targets?
にはIApplicationLoadBalancerTarget
のインタフェースを実装したオブジェクトの配列が渡せる
このようにコードを書きながら、何を設定するべきかが分かるので、バグも起きにくいです。実際、CDKを書いていて、デプロイに失敗するということが今の所ありません!(デプロイに失敗する内容であれば、エディタ上でエラーが表示されるので、デプロイ失敗の前に気づいて修正できる)
実際に使ってみる
typescript で CDK を書いていきます。
作成する内容は、この記事で作成するインフラ構成
の通りです。
前提
環境
検証を行った環境は以下の通りです。
windows 11 の wsl2
❯ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.5 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
macOS
AWS CLI がインストールされていること
❯ aws --version
aws-cli/2.9.2 Python/3.9.11 Linux/5.15.74.2-microsoft-standard-WSL2 exe/x86_64.ubuntu.20 prompt/off
インストールされていない方は、以下を見れば、インストールできます。
ちなみに、windows の wsl を使っている場合、linux のインストール手順に従ってください。(windows のインストール手順ではなく)
aws configure する
aws cli を aws アカウントと紐づけるため、aws configure でセットアップします。
❯ aws configure --profile private
AWS Access Key ID [None]: xxxxxxxxxxxxxxxxxxx 👈適切な値に置き換える
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxx 👈適切な値に置き換える
Default region name [None]: us-east-1 👈適切な値に置き換える
Default output format [None]:
-
AWS Access Key ID
,AWS Secret Access Key
は、ご利用のアカウントのものを入力してください。 - リージョンは適当です。好きな場所で入力してください。
- output format は何も入力しないでOK
正しく設定が行えたかどうかは、以下のファイルを見ることで、確認が可能です。
❯ cat ~/.aws/config
[profile private]
region = us-east-1
❯ cat ~/.aws/credentials
[private]
aws_access_key_id = xxxxxxxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxx
aws cli とアカウントの紐づけを private
という名前で設定したので、cdk deploy --profile private
のようにコマンドのオプションに profile を指定することで、特定の AWS アカウントに対してリソースの作成ができます。
CDK のインストール ~ Bootstrap まで
基本的には、公式の getting started 通りです。
インストール
npm install -g aws-cdk
バージョン確認
cdk --version
2022/11/26 時点 では、以下のバージョンがインストールされる
2.51.1 (build 3d30cdb)
Bootstrap
CDK を利用するために、IAMロールや、S3バケット、ECRリポジトリなどを作成する必要があります。
それら必要なリソースを作成するのがこの cdk bootstrap
コマンドです。
cdk bootstrap --profile private aws://ACCOUNT-NUMBER/REGION
リージョンごとに、cdk bootstrap
する必要があります。 AWS アカウントの番号が、1234567890 で、リージョンが us-east-1 を利用する場合、引数の ACCOUNT-NUMBER
と REGION
を以下のように置き換えます。
cdk bootstrap --profile private aws://1234567890/us-east-1
プロジェクトフォルダの作成
今回用に、プロジェクトフォルダをどこか適当な場所に作ります。
mkdir ~/aws-cdk-load-balanced-application
今回はホームディレクトリ直下に作ってみました。
CDK の初期化
CDK の初期化を行います。
まず、作成したディレクトリに移動し、
cd ~/aws-cdk-load-balanced-application
以下のコマンドで、初期化します。
cdk init app --language typescript
npm install っぽいのが動きます↓
すべて終わると ✅ All done!
に!
CDK インストール直後のフォルダの中身はこんな感じ
tree . -L 1
.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
CDK スタックのデプロイ先となる AWS アカウントとリージョンを設定する
bin/aws-cdk-load-balanced-application.ts
を開きます。
以下の行を修正し、デプロイ先のAWSアカウントの番号(ご利用になっている、実在する AWS アカウントの番号)と、リージョンを指定します。
ここで、このようにアカウントとリージョンを明示することで、複数の AWS アカウントを扱っている場合でも、誤って他の AWS アカウントにデプロイしてしまうといったことが防げます。
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AwsCdkLoadBalancedApplicationStack } from '../lib/aws-cdk-load-balanced-application-stack';
const app = new cdk.App();
new AwsCdkLoadBalancedApplicationStack(app, 'AwsCdkLoadBalancedApplicationStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */
/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
- // env: { account: '123456789012', region: 'us-east-1' },
+ env: { account: '123456789012', region: 'us-east-1' },
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
ちなみに、aws configure を行っていれば、~/.aws/credentials と ~/.aws/config のそれぞれの値を参照して、cdk がデプロイ先の AWS アカウントと、リージョンを implicit (暗示的に) に決定してくれますが、この方法だと、一部のメソッドでエラーとなるので注意が必要です。
例えば Vpc.fromLookup
などは明示的な aws アカウントの指定をしないと以下のエラーになります。
Error: Cannot retrieve value from context provider vpc-provider since account/region are not specified at the stack level. Configure "env" with an account and region when you define your stack.See https://docs.aws.amazon.com/cdk/latest/guide/environments.html for more details.
詳細は、以下のスレッド参照ください。
CDK のコードを書いて、スタックを作っていく
ここから、実際にAWSのリソースを作る作業を行います。
cdk init
したときに、 lib/aws-cdk-load-balanced-application-stack.ts
のファイルが作成されているはずなので、それを開きます。
初期の状態はこんな感じで、必要なリソースを追加していきます。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
export class AwsCdkLoadBalancedApplicationTempStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// The code that defines your stack goes here
// example resource
// const queue = new sqs.Queue(this, 'AwsCdkLoadBalancedApplicationTempQueue', {
// visibilityTimeout: cdk.Duration.seconds(300)
// });
}
}
既存の VPC を利用する
CDK で、リソースの作成もできますが、既存のリソースを参照して、それを利用することも可能です。
VPC は既存のものを使います。
既存の VPC を参照するために、ec2.Vpc.fromLookup()
を使います。
第三引数の props のオブジェクトには、VPC を特定するIDを設定することで、既存のVPC をこの CDK スタックに取り込みます。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
- // import * as sqs from 'aws-cdk-lib/aws-sqs';
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class AwsCdkLoadBalancedApplicationTempStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
-
- // The code that defines your stack goes here
-
- // example resource
- // const queue = new sqs.Queue(this, 'AwsCdkLoadBalancedApplicationTempQueue', {
- // visibilityTimeout: cdk.Duration.seconds(300)
- // });
-
+ const defaultVpc = ec2.Vpc.fromLookup(this, 'defaultVpc', {
+ vpcId: 'vpc-1234567890',
+ });
}
}
※vpc-id は実在する vpc の ID に書き換えてください。vpc が無い場合は、vpc 作成の上、その vpc の ID を指定してください。
Auto Scaling の 起動テンプレート作成
起動テンプレートに必要な内容を一気に追加します。
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
+ import * as iam from 'aws-cdk-lib/aws-iam';
export class AwsCdkLoadBalancedApplicationTempStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const defaultVpc = ec2.Vpc.fromLookup(this, 'defaultVpc', {
vpcId: 'vpc-1234567890',
});
+ const ami = ec2.MachineImage.latestAmazonLinux();
+
+ const instanceType = ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO);
+
+ const securityGroup = new ec2.SecurityGroup(this, 'securityGroup', {
+ vpc: defaultVpc,
+ });
+ securityGroup.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(80), 'Added by a CDK stack');
+
+ const userData = ec2.UserData.forLinux();
+ userData.addCommands('yum update -y', 'yum install -y httpd.x86_64', 'service httpd start', 'echo “Hello World from $(hostname -f)” > /var/www/html/index.html');
+
+ const role = new iam.Role(this, 'roleForSSM', {
+ assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
+ });
+ role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
+
+ const launchTemplate = new ec2.LaunchTemplate(this, 'LaunchTemplate', {
+ machineImage: ami,
+ instanceType: instanceType,
+ securityGroup: securityGroup,
+ userData: userData,
+ role: role,
+ });
}
}
起動テンプレートを作成するためには、ec2.LaunchTemplate()
を利用します。
ec2.LaunchTemplate()
の props の引数に渡すプロパティ達を、前段で書いていきます。
-
ami
は最新の Amazon Linux Image を引っ張ってきます。 -
instanceType
は、無料利用枠の t2.micro を引っ張ってきます。 -
securityGroup
では、- 先程記述した CDK コードによって引っ張ってきた
defaultVpc
をアタッチしたセキュリティグループを作成し、インバウンド(Ingress)ルールに全アドレスからの 80 番ポート(HTTP) への接続を許可する内容を設定しています。
- 先程記述した CDK コードによって引っ張ってきた
-
userData
では、EC2 起動時に実行するコマンドを設定しています。- 内容:
- アパッチサーバーのインストールと起動
- サーブするファイルの作成(「Hello World from IPアドレス」 という文字列)
- 内容:
-
role
は、起動したEC2インスタンスにセッションマネージャーで接続するためのポリシーを設定する内容で、作成。デバッグする場合などに便利かと。EC2のターミナルに接続不要であれば、このロールは不要。
Autoscaling group を追加しスケーラブルにする
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
+import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';
export class AwsCdkLoadBalancedApplicationStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ~~ 省略 ~~
+ const autoscalingGroup = new autoscaling.AutoScalingGroup(this, 'autoscalingGroup', {
+ vpc: defaultVpc,
+ launchTemplate: launchTemplate,
+ });
}
}
autoscaling.AutoScalingGroup
のコンストラクトの引数に、事前に用意していた vpc
と launchTemplate
(起動テンプレート) を渡して、Autoscaling グループを作成します。
これで、オートスケールするインフラの下準備ができました。
また、ここで作る Autoscaling group は次のステップで追加する ALB のバックエンドとして利用します。
ALB を追加し、負荷分散する
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';
+ import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
+ import { Duration } from 'aws-cdk-lib';
export class AwsCdkLoadBalancedApplicationStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ~~ 省略 ~~
+ const alb = new elbv2.ApplicationLoadBalancer(this, 'applicationLoadBalacner', {
+ vpc: defaultVpc,
+ internetFacing: true,
+ });
+
+ const listener = alb.addListener('listner', {
+ port: 80,
+ });
+
+ listener.addTargets('ApplicationFleet', {
+ port: 80,
+ targets: [autoscalingGroup],
+ healthCheck: {
+ path: '/',
+ interval: Duration.minutes(1),
+ },
+ });
+
+ autoscalingGroup.connections.allowFrom(alb, ec2.Port.tcp(80));
}
}
elbv2.ApplicationLoadBalancer
でALBを作成します。
internetFacing
を true にすることで、インターネットと通信可能なALBを設置します。
続いて、80 番のポートでサイトにアクセスできるようにするため、alb.addListener
を使って、80 番ポートのリスナーを ALB にアタッチします。
ALB がクライアントから受け取ったリクエストを中継する先としてのターゲットグループも必要ですが、そちらは、listener.addTargets
で追加します。
中継先は、先程作成した autoscalingGroup
です。
この構成にすることにより、autoscalingGroup
に属する EC2 インスタンスにリクエストが中継されるようになります。
最後に、ALB から Autoscaling グループへの通信を許可するため、connections.allowFrom
を使います。
これにより、セキュリティグループで、ALB からの 80 番ポートへのインバウンド通信が許可されるようになります。
デプロイして、ウェブサイトを動かす
以下のコマンドでデプロイします。
cdk deploy --profile private
無事成功すると、Cloudforamtion のコンソール画面で、以下のように各種リソースが作成されているかと思います。
この中で、ALB のリソースを見つけて、リンクをクリックして ALB の詳細を見に行きます。
ALB の名前をクリックして詳細を開きます。
ALB の詳細画面で、DNS 名があるので、これをブラウザの URL に入力すると、、、
無事サイトにアクセスできました! 🎉
Auto scaling group で、複数の EC2 インスタンスが起動するように設定する
auto scaling グループ自体の追加は、前述の Autoscaling group を追加しスケーラブルにする
で行っていましたが、キャパシティの設定がまだでした。
キャパシティの設定をしない場合、デフォルトの値となり、以下のキャパシティが設定されます。
maxCapacity = desiredCapacity = minCapacity = 1
つまり、1台のEC2インスタンスしか起動せず、autoscaling group を使う意味がないので、これらの値を変えてみます。
// ~~ 省略 ~~
export class AwsCdkLoadBalancedApplicationStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ~~ 省略 ~~
const autoscalingGroup = new autoscaling.AutoScalingGroup(this, 'autoscalingGroup', {
vpc: defaultVpc,
launchTemplate: launchTemplate,
+ minCapacity: 2,
+ maxCapacity: 2,
+ desiredCapacity: 2,
});
// ~~ 省略 ~~
}
}
それぞれのキャパシティの値に 2
を設定したので、autoscaling group として、EC2インスタンスが2台起動するようになるはずです。
実際に、再デプロイします。
デプロイが終わったら、AWSコンソールで2台の EC2 インスタンスが起動されているか確認してみます。
Cloudformation で作成されたリソースの中から、ターゲットグループを見てみると、、、
2台の EC2 インスタンスが起動されていることが確認できました!🎉
続いて、アプリが機能しているか確認します。先程と同様に、ALB のURLを確認し、アクセスすると、
このように画面が表示されます。リロードボタンを連打すると、、、
IPアドレスの内容が変わり、複数のEC2インスタンスに負荷分散されていることが確認できました!🎉🎉
更に、オートスケールするか確認してみます。
キャパシティを 2
に固定しているので、起動している EC2 インスタンスのうち1つを終了してみて、新しいインスタンスが起動するか試します。
EC2 のコンソール画面から、起動しているどちらかの EC2 インスタンスを選択肢、インスタンスの状態
から、終了を実行します。
その後、autoscaling グループを確認すると、、、
1つのインスタンスが終了し、新しくインスタンスが起動していることが確認できました!
スケールも成功しました。🎉🎉🎉
費用の確認
以上の通り、オートスケールと負荷分散に対応したインフラを構築しましたが、費用は以下のとおりです。
とりあえず、検証のためとかであれば、無料利用枠でも十分できそうです。
課金の可能性が一番ありそうなのは ALB でしょうか?
ただ、テストのために数回リクエストするだけなら、課金されなさそうです。
Q: Application Load Balancer で AWS の新規アカウントの無料利用枠は利用できますか?
A: はい。新規の AWS アカウントの無料利用枠では、Application Load Balancer を 750 時間分および 15 LCU ご利用いただけます。この無料利用枠は、AWS の新規のお客様のみが対象であり、AWS にサインアップした日から 12 か月間ご利用いただけます。
片付け
cdk destroy --profile private
これで、CDK スタックの Cloudformation が削除されます。
まとめ
CDKめっちゃ便利でした。
Cloudformation はテンプレート書くの難しいですけど、CDKは補完とか効くし、コードも冗長じゃないから、理解しやすくて良きでした。
もっとCDK使いこなしていきたいです。
参考