ニクニクドットミー

カッコいいおっさんを目指すエンジニアの厳かなブログ

AWS CDKに入門したので所感など

AWS CDKに入門したいなーとぼんやり思いつつ時間が経ってしまったけど触る理由が出来たのでハンズオンを試しつつ、学んだことをメモしておこうと思う。
筆者の背景としては、AWSのIaCといえばTerraformで生きてきた身なのでコードでIaCをしていく感覚がわからず、正直食わず嫌いなところがあった。
それでも触ってみようと思った理由はAWS CDKを触るちゃんとした機会が出てきたことと「CDKはいいぞ!」という声も聞くようになったので触ってみようと思った。

AWS CDKとは?

CFn(CloudFormation)を抽象化しプログラミング言語でよしなに書けるツール
抽象化することでコード量を少なくできるメリットがある
言語はTypescriptやGolang, Pythonなどが選択可能
CDKをデプロイすると裏ではCFnが実行されるという感じ

環境について

訳があってv1を利用しているが2022/06/01以降からv1はメンテナンスモードになっているのでいまから触る方はv2を使っていきましょう。

FYI: AWS CDKのv1はいつまでサポートされるのか調べてみた

yarn cdk --version
yarn run v1.22.19
$ cdk --version
1.153.1 (build 9a7e599)

ハンズオンの資料はAWS公式のものを利用
AWS CDK の使用を開始する

cdk initをすると以下のようなディレクトリ、ファイルが生成される。

重要なファイルのいくつかとそれらの用途は次のとおりです。
bin/cdk-project.ts - これは CDK アプリケーションへのエントリポイントです。これにより、lib/* で定義したすべてのスタックがロード/作成されます。
lib/cdk-project-stack.ts - ここでメインの CDK アプリケーションスタックが定義されます。リソースとそのプロパティはこちらに含めることができます。
package.json - ここで、プロジェクトの依存関係、およびいくつかの追加情報とビルドスクリプト (npm build、npm test、npm watch) を定義します。
cdk.json - このファイルは、アプリケーションの実行方法、および CDK とプロジェクトに関連する追加設定とパラメータをツールキットに指示します。

ここでcdk.jsonの内容を確認
一部手を加えているところもあるが、"app": "npx ts-node --prefer-ts-exts bin/sample-ts.ts"とあり、これがcdkのコマンドを実行したときに呼び出されるコマンドの実体という理解。

bin/sample-ts.tsがエントリーポイントとなり、これを基点としてlib/*以下のコードが実行されるという流れと理解

{
  "app": "npx ts-node --prefer-ts-exts bin/sample-ts.ts",
  "watch": {
    "include": ["**"],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:enableStackNameDuplicates": true,
    "aws-cdk:enableDiffNoFail": true,
    "@aws-cdk/core:stackRelativeExports": true,
    "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
    "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
    "@aws-cdk/aws-kms:defaultKeyPolicies": true,
    "@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
    "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-efs:defaultEncryptionAtRest": true,
    "@aws-cdk/aws-lambda:recognizeVersionProps": true,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
    "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
    "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
    "@aws-cdk/core:checkSecretUsage": true,
    "@aws-cdk/aws-iam:minimizePolicies": true,
    "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
    "staging": {
      "systemName": "staging",
      "envType": "stg",
      "instanceType": "t3.micro"
    },
    "production": {
      "systemName": "production",
      "envType": "prd",
      "instanceType": "m5.large"
    }
  }
}

cdkのコマンドについて

ckd ls

ckdに含まれるスタック一覧を確認するコマンド

cdk synth

cdkのコードをCFnテンプレートとして吐き出す(cdk.outディレクトリ)

cdk diff

ckdアプリのバージョンとすでにデプロイされているバージョンとを比較し、変更点を一覧で出力するコマンド

cdk deploy

cdkをデプロイするコマンド すべてのスタックをデプロイしたい場合は"*" or "--all" オプションをつける

cdk destroy

デプロイしたスタックを削除するコマンド 特定のスタックのみを削除することも可能

cdk bootstrap

初回だけ実行するコマンド
CFnのテンプレートを置くためのS3を作成してくれる

よく使いそうなコマンドとしてはcdk diff,cdk synth,cdk deploy,cdk destroyなのかな

サンプルコードとContextについて

以下はハンズオンで生成したvpcを作成するコード(vpcとpublic subnetを2つ) 一部手を加えているので説明する

import * as cdk from "@aws-cdk/core";
import { Vpc, SubnetType } from "@aws-cdk/aws-ec2";

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

    const stage = this.node.tryGetContext("stage");
    const contextParam = this.node.tryGetContext(stage);

    const vpc = new Vpc(this, "MainVpc", {
      maxAzs: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "public-subnet",
          subnetType: SubnetType.PUBLIC,
        },
      ],
      vpcName: `${contextParam.systemName}-${contextParam.envType}-vpc`,
    });
  }
}

以下のコードでContextから受け取った値を取得する処理を実行している

    const stage = this.node.tryGetContext("stage");
    const contextParam = this.node.tryGetContext(stage);

Contextとは「CFnのパラメーターのようにCDKのコードに外部から値を注入することができる」機能のこと
Contextについては以下の記事が参考になりました
FYI: 実践!AWS CDK #4 Context

このサンプルコードではcdk.jsonに定義されている以下の値を取得して、vpcの名前をContextオプションで渡された引数によって切り替えるというコードになっている

"staging": {
      "systemName": "staging",
      "envType": "stg",
      "instanceType": "t3.micro"
    },
    "production": {
      "systemName": "production",
      "envType": "prd",
      "instanceType": "m5.large"
    }

実行コマンド
yarn cdk synth -c stage=staging

Resources:
  MainVpc919A5E7E:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: staging-stg-vpc
    Metadata:
      aws:cdk:path: SampleTsStack/MainVpc/Resource

yarn cdk synth -c stage=production

Resources:
  MainVpc919A5E7E:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: production-prd-vpc
    Metadata:
      aws:cdk:path: SampleTsStack/MainVpc/Resource

vpcのNameタグがContext(-c)の引数によって切り替わったことがわかる
Contextを利用することで外部から値を注入できるのでリソースの名前やその他振る舞いをコードで制御することが出来る

普段個人のアカウントでAWS環境をセットアップするときに使っているTerraformのコードを貼っておく
素で書くともうちょっと長くなると思うが楽をしたくてvpc moduleを利用している
HCLが苦手な方もいると思うがぱっと見た感じなにをしているか想像は付きやすいとは思う

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.11.0"

  name = "develop"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway     = true
  enable_vpn_gateway     = false
  single_nat_gateway     = true
  one_nat_gateway_per_az = false
  reuse_nat_ips          = false

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

所感

CDKで柔軟にIaCできるのは楽しいなというのが一番の感想
またテストコードも書けるのでSnapshot Testを用意してあげることで保守も頑張れそうな気がした
TerraformにはWorkspacesがあるのでContextはそれとちょっと似ているかなーと思うが、Terraformで環境毎の制御をするには記述方法に限界がある。そのため、CDKの方がより柔軟に制御ができそう(ただし、あまり複雑なことをするとコードの視認性が悪くなりそうなのでバランスが大事になると思う)

ファイル分割やContextを他のスタックでも使い回す方法など試してみたいことが山程あるのでしばらくはcdkにどっぷり浸かって楽しんでいこうと思う
あとCDKの情報をキャッチアップするにあたってTwitterのコミュニティに参加してみた
v1を使っているけどv2でちゃんとキャッチアップしようと思っている(migration方法も調べておく)

FYI

See you next time:)