15
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS 無料利用枠 で始める CDK 入門(絶対にお金を払いたくない人向け)

Last updated at Posted at 2022-12-04

image.png

記事の趣旨

最近、CDK を少し使う必要があったので、使い方を調べました。

今回は調べた CDK の使い方についてまとめます。一応、無量利用枠に収まる範囲、無課金の範囲でインフラを構築するので、記事に沿って作業してもお金はかからないと思うので、その辺気にされる方の参考になればと思います。

記事の対象者は:

  • AWS の基礎的な理解がある
  • CDK を使ってみたい
  • でもお金は掛けたくない

この記事で作成するインフラ構成

image.png

インフラの内容:

  • クライアントからのリクエストを 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

コンソール画面ですべて行うと、以下の疑問に対する答えを確認するために、いろいろな画面を行ったり来たりする羽目になります。

  1. セキュリティグループのインバウンド設定の内容はどんな?
  2. EC2インスタンスのユーザーデータは、どんな内容にしてたっけ?
  3. IAMロールは適切に設定されてる?

これでは、うまくシステムが動かない・接続できないなどの問題が起こったときに、デバッグに時間がかかります。

CDK でコード化すると、1箇所で上記の疑問すべてが解決します。

image.png

Typescript の型補完の恩恵

AWS のリソースを作成する際、オプション項目をオブジェクトの引数で渡しますが、typescript の補完が効くので何を渡せばいいか、エディタ上で把握できます。

image.png

上記のスクショだと、以下のことがわかります。

  • 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

image.png

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-NUMBERREGION を以下のように置き換えます。

コマンド
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 っぽいのが動きます↓

image.png

すべて終わると ✅ All done! に!

image.png

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 アカウントにデプロイしてしまうといったことが防げます。

bin/aws-cdk-load-balanced-application.ts
#!/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 のファイルが作成されているはずなので、それを開きます。

初期の状態はこんな感じで、必要なリソースを追加していきます。

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 スタックに取り込みます。

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';
+ 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 の 起動テンプレート作成

起動テンプレートに必要な内容を一気に追加します。

lib/aws-cdk-load-balanced-application-stack.ts
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) への接続を許可する内容を設定しています。
  • userData では、EC2 起動時に実行するコマンドを設定しています。
    • 内容:
      • アパッチサーバーのインストールと起動
      • サーブするファイルの作成(「Hello World from IPアドレス」 という文字列)
  • role は、起動したEC2インスタンスにセッションマネージャーで接続するためのポリシーを設定する内容で、作成。デバッグする場合などに便利かと。EC2のターミナルに接続不要であれば、このロールは不要。

Autoscaling group を追加しスケーラブルにする

lib/aws-cdk-load-balanced-application-stack.ts
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 のコンストラクトの引数に、事前に用意していた vpclaunchTemplate(起動テンプレート) を渡して、Autoscaling グループを作成します。

これで、オートスケールするインフラの下準備ができました。
また、ここで作る Autoscaling group は次のステップで追加する ALB のバックエンドとして利用します。

ALB を追加し、負荷分散する

lib/aws-cdk-load-balanced-application-stack.ts
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 のコンソール画面で、以下のように各種リソースが作成されているかと思います。

image.png

この中で、ALB のリソースを見つけて、リンクをクリックして ALB の詳細を見に行きます。

image.png

ALB の名前をクリックして詳細を開きます。

image.png

ALB の詳細画面で、DNS 名があるので、これをブラウザの URL に入力すると、、、

image.png

無事サイトにアクセスできました! 🎉

Auto scaling group で、複数の EC2 インスタンスが起動するように設定する

auto scaling グループ自体の追加は、前述の Autoscaling group を追加しスケーラブルにする で行っていましたが、キャパシティの設定がまだでした。

キャパシティの設定をしない場合、デフォルトの値となり、以下のキャパシティが設定されます。

maxCapacity = desiredCapacity = minCapacity = 1

つまり、1台のEC2インスタンスしか起動せず、autoscaling group を使う意味がないので、これらの値を変えてみます。

lib/aws-cdk-load-balanced-application-stack.ts
// ~~ 省略 ~~

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台起動するようになるはずです。

実際に、再デプロイします。

image.png

image.png

デプロイが終わったら、AWSコンソールで2台の EC2 インスタンスが起動されているか確認してみます。
Cloudformation で作成されたリソースの中から、ターゲットグループを見てみると、、、

image.png

2台の EC2 インスタンスが起動されていることが確認できました!🎉

続いて、アプリが機能しているか確認します。先程と同様に、ALB のURLを確認し、アクセスすると、

image.png

このように画面が表示されます。リロードボタンを連打すると、、、

image.png

IPアドレスの内容が変わり、複数のEC2インスタンスに負荷分散されていることが確認できました!🎉🎉

更に、オートスケールするか確認してみます。
キャパシティを 2 に固定しているので、起動している EC2 インスタンスのうち1つを終了してみて、新しいインスタンスが起動するか試します。

EC2 のコンソール画面から、起動しているどちらかの EC2 インスタンスを選択肢、インスタンスの状態から、終了を実行します。

image.png

その後、autoscaling グループを確認すると、、、

image.png

1つのインスタンスが終了し、新しくインスタンスが起動していることが確認できました!
スケールも成功しました。🎉🎉🎉

費用の確認

以上の通り、オートスケールと負荷分散に対応したインフラを構築しましたが、費用は以下のとおりです。

image.png

とりあえず、検証のためとかであれば、無料利用枠でも十分できそうです。

課金の可能性が一番ありそうなのは 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使いこなしていきたいです。

参考

15
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?