AWS

cdk bootstrapが何者なのか正面から向き合う

nogusan

こんにちは!

昨年11月、37歳にしてやっと普通運転免許を取得してドライブにどハマりし、週2~3回のペースで首都高ドライブに挑んでいるnogusanです。

技術的な記事を執筆するのは今回が初めてなのですが、「初の技術記事にふさわしい内容はなんだろう?」と考えた結果、日々業務で触るAWS CDK(以下CDK)の「初めてコマンド」ともいえるcdk bootstrapについて調べてみることにしました!

概要

cdk bootstrapは、CDKを使用してデプロイを行うにあたり必要になるリソースをデプロイするためのコマンドです。

このコマンドを実行すると、デフォルトでCDKToolkitという名前のCloudFormation(以下Cfn)スタックが対象アカウントにデプロイされます。

当スタックには、S3やECR、IAMロールなど、CDKを使用してリソースをデプロイする際に、バックグラウンドで必要な基盤であるリソース群を1つのスタックとして自動的にデプロイしてくれます。

ブートストラップテンプレートbootstrap-template.yamlにCDKToolkitの中身が記述されており、後述の方法で中身を確認すると、実体はあくまでもCfnスタックであることを再確認できます。

CDKToolkitではどんなリソースが作られるのか

まずはCDKToolkitでどのようなリソースが作られるのかを確認していきます。

1.構成図

CDKToolkitで作成されるリソースとそれを取り巻く環境について、構成図を作成しました。

2.リソース一覧

図中のCDKToolkitのピンクの太枠線内にあるリソースがブートストラップによって作成されるリソース群です。

それでは、各リソースはどのような役割を担っているのでしょうか?下表にそれぞれを解説します。

論理IDタイプ説明
CdkBootstrapVersionAWS::SSM::Parameterブートストラップのバージョン番号を格納したSSMパラメータ。ブートストラップテンプレートはCDKの開発チームによって定期的に更新されるため、更新後は再度cdk bootstrapすることでバージョンアップすることが推奨される
CloudFormationExecutionRoleAWS::IAM::RoleCfnがスタックの作成・削除・更新を行うためのロール。Cfnのサービスプリンシパルに対してAdministrator権限を渡している
ContainerAssetsRepositoryAWS::ECR::RepositoryECSなどを使用する場合、作成したDockerイメージが格納されるECR。ECSを使用しない場合は特に何も格納されない
DeploymentActionRoleAWS::IAM::RoleCDKコマンドを実行する際に使用されるロール。信頼関係にはデフォルトでアカウントのルートユーザが指定されており、S3やCfnに対する各種権限が許可されている
FilePublishingRoleAWS::IAM::RoleStatgingBucketに各種ファイルをアップロードするためのロール。信頼関係にはデフォルトでアカウントのルートユーザが指定されている
FilePublishingRoleDefaultPolicyAWS::IAM::PolicyFilePublishingRoleにアタッチするロール。S3に関する各種権限と、KMSの権限が付与されている
ImagePublishingRoleAWS::IAM::RoleContainerAssetsRepositoryにDockerイメージをPushするためのロール。信頼関係にはデフォルトでアカウントのルートユーザが指定されている
ImagePublishingRoleDefaultPolicyAWS::IAM::PolicyImagePublishingRoleにアタッチするロール。ECRに関する各種権限が付与されている
LookupRoleAWS::IAM::RolefromLookup()などのメソッドを実行するためのロール。信頼関係にはデフォルトでアカウントのルートユーザが指定されている。また、ReadOnlyAccessポリシーがアタッチされている
StagingBucketAWS::S3::BucketCDKでデプロイされる際に生成される、Cfnテンプレート等のアセットが格納されるバケット。CfnはこのバケットにあるCfnテンプレート等を参照することでデプロイを行う。Cfnテンプレート以外では、Zipファイル化されたLambdaコード等が格納される
StagingBucketPolicyAWS::S3::BucketPolicyStagingBucketにSSL通信以外を禁止するためのポリシー
cdk bootstrapで作成されるリソース一覧

ブートストラップテンプレートの中身を覗いてみる

CDKToolkitのイメージがだいぶ湧いてきましたね!

今度は、冒頭で述べたCDKToolkitの「実体はあくまでもCfnスタックであること」を体感するために、ブートストラップテンプレートの中身を下記コマンドで確認してみます。

# CDKプロジェクトディレクトリの直下にいることを確認
$ pwd 
/Users/nogusan/cdk-boostrap-revealing

# ブートストラップテンプレートの中身を確認
# 2通りのコマンドがありますが、2つ目のほうがテンプレートファイルの在処がわかり、実体を体感しやすいかもしれませんね。
$ npx cdk bootstrap --show-template
$ cat ./node_modules/aws-cdk/lib/api/bootstrap/bootstrap-template.yaml

上記のコマンドを実行すると、下記のようにCDKToolkitの正体であるブートストラップのテンプレートファイルを確認できました!(長いため一部のみ抜粋して表示します)

先ほどお示しした一覧表の中にあったStagingBucketがCfnテンプレートとして定義されていますね。

Resources:
    # 一部省略
    StagingBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        Fn::If:
          - HasCustomFileAssetsBucketName
          - Fn::Sub: "${FileAssetsBucketName}"
          - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region}
      AccessControl: Private
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID:
                Fn::If:
                  - CreateNewKey
                  - Fn::Sub: "${FileAssetsBucketEncryptionKey.Arn}"
                  - Fn::If:
                    - UseAwsManagedKey
                    - Ref: AWS::NoValue
                    - Fn::Sub: "${FileAssetsBucketKmsKeyId}"
        # これ以降も省略

StagingBucketに保存されるファイルを覗いてみる

CDKToolkitで作成されるStagingBucketには、Cfnテンプレートなどデプロイ時に使用する様々なアセットが格納されます。

こちらを体感するために、以下の手順で実際に検証していきたいと思います。

  1. CDKでサンプルのS3バケットをデプロイする
  2. StagingBucketに格納されるテンプレートの中身を覗き見る
  3. Cfnコンソール画面で表示されるテンプレートを目視確認し、手順②のファイルと照らし合わせる

【手順①】CDKでサンプルのS3バケットをデプロイする

まずはS3バケット生成用のCDKコードを書きます。今回はTypeScriptで記述します。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

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

    // S3 バケットを作成
    new s3.Bucket(this, 'SampleBucket', {
      bucketName: 'nogusan-sample-bucket',
    });
  }
}

const app = new cdk.App();
new S3SampleStack(app, 'S3SampleStack');

デプロイします。

npx cdk deploy S3SampleStack

✨  Synthesis time: 3.52s

S3SampleStack: start: Building bee2ac2695adc44e1a951e90fdaf020f5ebd61163862402307d569f1418934cf:current_account-current_region
S3SampleStack: success: Built bee2ac2695adc44e1a951e90fdaf020f5ebd61163862402307d569f1418934cf:current_account-current_region
S3SampleStack: start: Publishing bee2ac2695adc44e1a951e90fdaf020f5ebd61163862402307d569f1418934cf:current_account-current_region
S3SampleStack: success: Published bee2ac2695adc44e1a951e90fdaf020f5ebd61163862402307d569f1418934cf:current_account-current_region
S3SampleStack: deploying... [1/1]
S3SampleStack: creating CloudFormation changeset...
S3SampleStack | 0/3 | 18:19:56 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | S3SampleStack User Initiated
S3SampleStack | 0/3 | 18:20:02 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | S3SampleStack User Initiated
S3SampleStack | 0/3 | 18:20:04 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | SampleBucket (SampleBucket7F6F8160) 
S3SampleStack | 0/3 | 18:20:04 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata) 
S3SampleStack | 0/3 | 18:20:05 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata) Resource creation Initiated
S3SampleStack | 1/3 | 18:20:05 | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata) 
S3SampleStack | 1/3 | 18:20:06 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | SampleBucket (SampleBucket7F6F8160) Resource creation Initiated
S3SampleStack | 2/3 | 18:20:19 | CREATE_COMPLETE      | AWS::S3::Bucket    | SampleBucket (SampleBucket7F6F8160) 
S3SampleStack | 3/3 | 18:20:20 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | S3SampleStack 

 ✅  S3SampleStack

✨  Deployment time: 28.76s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/S3SampleStack/05339d10-eb7e-11ef-abe7-0e65a27aa8bd

✨  Total time: 32.28s

【手順②】StagingBucketに格納されるテンプレートの中身を覗き見る

サンプルS3バケットのデプロイに成功しました。StagingBucket(物理IDではcdk-hnb659fds-assets-${accountID}-ap-northeast-1)を確認すると、何やらCDKデプロイに伴って生成されたと思われるファイルが入っています。

このファイルの中身を確認してみましょう。今回はaws s3 cpコマンドを使用して、ターミナルの標準出力にファイルの中身を出直して確認してみます。

$ aws s3 cp s3://cdk-hnb659fds-assets-XXXXXXXXXXXX-ap-northeast-1/bee2ac2695adc44e1a951e90fdaf020f5ebd61163862402307d569f1418934cf.json - | jq

{
  "Resources": {
    "SampleBucket7F6F8160": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "BucketName": "nogusan-sample-bucket"
      },
      "UpdateReplacePolicy": "Retain",
      "DeletionPolicy": "Retain",
      "Metadata": {
        "aws:cdk:path": "S3SampleStack/SampleBucket/Resource"
      }
    },
    # これ以降の出力は省略
}

Resoucesキーの中身がCfnテンプレートの中身のようですね🤔

テンプレートファイルはローカルにも保存される

こちらのファイル、実はローカルのCDKプロジェクトの中にも生成されるものです。

cdk.outディレクトリの中に、デプロイしたスタック名のファイルが生成されます

今回の場合、私のローカルではcdk.out/S3SampleStack.template.jsonというファイルが生成されておりました。こちらがStagingBucketにアップロードされ、デプロイ時にはCfnからこのファイルが参照されているようです。

【手順③】Cfnコンソール画面で表示されるテンプレートを目視確認し、手順②のファイルと照らし合わせる

今度は、CloudFormationマネジメントコンソール画面で、デプロイしたS3SampleStackのテンプレートが使用されていることを確かめます。

見事に、手順②で確認したファイルと一致していますね!

スクリーンショットでは途中まで表示していますが、その後のJSONも一致しておりました。

素朴な疑問:物理IDにつくhnb659fdsとは何か

最後に素朴な疑問について確認します。

StagingBucketや他のCDKToolkit内のリソースに、物理IDとしてつくリソース名にhnb659fdsという文字列があり、これがランダムに生成される文字列なのかどうか気になったので調べてみました。私の環境では、作成されたS3バケットの名称はcdk-hnb659fds-assets-${accountID}-ap-northeast-1となっていました。

論理ID・物理ID

表「cdk bootstrapで作成されるリソース一覧」では、リソースの名前として論理IDを示しました。実際にリソースにつく名称として物理IDもあり、それぞれ定義は以下の通りです。

  • 物理ID:Cfnテンプレートの中でリソースを識別するための名称。テンプレート内で一意である必要がある
  • 論理ID:S3バケット名など、リソースに実際につけられる名称

cdk bootstrapによって作成されるS3バケットStagingBucketにつけられる物理IDは、ブートストラップテンプレートで下記のように指定されています(一部抜粋)。

Parameters:
    Qualifier:
    Description: An identifier to distinguish multiple bootstrap stacks in the same environment
    Default: hnb659fds
Resouces:
    StagingBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        Fn::If:
          - HasCustomFileAssetsBucketName
          - Fn::Sub: "${FileAssetsBucketName}"
          - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region}

ParametersセクションにQualifierとしてhnb659fdsがデフォルト指定されていました!

AWSのデベロッパーズガイドを確認してみると、この文字列に特に意味は無いことがわかりました。

参考
CDK スタック合成をカスタマイズする
CDK スタック合成をカスタマイズする

リソース名の中の文字列 hnb659fds は、修飾子と呼ばれます。デフォルト値に特別な意味はありません。

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/customize-synth.html

この修飾子と呼ばれる文字列はカスタマイズが可能です。修飾子をカスタマイズしてcdk bootstrapすることで、同一アカウント・同一リージョンに複数のブートストラップを実行することも可能です。

まとめ

ポイント
  • cdk bootstrapは、CDKでAWSリソースをデプロイする際、バックグラウンドで必要になるリソース群をデプロイするコマンド
  • ブートストラップを実行すると、デフォルトではCDKToolkitというスタックが作成され、そのスタックに必要なリソース群がデプロイされる
  • StagingBucketにCfnテンプレートなどのアセットが格納され、Cfnはそれを参照してスタックをデプロイする。Cfnテンプレートのアセットは、ローカルではCDKプロジェクトディレクトリのcdk.outディレクトリに出力される
  • CDKToolkit配下のリソースの物理IDに付与されるhnb659fdsは修飾子と呼ばれ、同一アカウント内でのブートストラップを一意に保つために存在する。この文字列そのものに特別な意味は無く、カスタマイズすることも可能

今までCDKプロジェクトを開始するときに漫然と使用していたcdk bootstrapについて深掘りすることで、自分の中での理解度をグッと高め、調べる過程で知らなかった機能を知見として得ることができました。

普段何気なく使用しているコマンドや機能、ツールについて、ときには「その正体がなんなのか」と正面から向き合い、調べていくのもよいかと思いました!

参考資料

参考
AWS CDK ブートストラッピング
AWS CDK ブートストラッピング

AUTHOR
nogusan
nogusan
記事URLをコピーしました