Open39

[番外編] CDK奮闘記

wakakawakaka

CDKではリソースごとにタグを付与することが難しい。L2コンストラクトでは複数リソースを抽象化してまとめているけど、それによってL2コンストラクトにタグを付けるとVpcだけではなく、SubnetやRouteTableなどL2コンストラクトに含まれるすべてのリソースに対してタグが付与されてしまう。これは自動命名でも同様で、Stack/Constructなどのような名前(Nameタグ)がすべてのリソースに適用されてしまう。


wakakawakaka

前提として、Stack/Construct/Constructなどのような/で区切れたものをCfn側がNameタグとして採用する。コンストラクトツリーは以下のようになっており、ツリー構造に従ってNameタグが作成されてしまう。RouteTableなどはL2のPublicSubnetコンストラクト内のリソースなのでSubnetと同様のNameタグが付与されてしまう。

wakakawakaka

継承クラス図はこんな感じ。
L1コンストラクト:CfnResourceを継承したコンストラクト
L2コンストラクト:Resourceを継承したコンストラクト

wakakawakaka

そもそもAWSコンソール上において各リソースに定義されるリソース名が「Nameタグによるもの」か「固有の名前か」で揺らいでいることも大きな問題となっている。SubnetやRouteTableなどのリソースは「Nameタグによるもの」だが、Lambda関数などは「固有の名前」となっており、名前の形式が異なる。

wakakawakaka

固有の名前(ECSとか)の場合にはL2にも名前定義するパラメータが与えられている気がする。

wakakawakaka

ほぼすべてのリソースに名前を適切に付与しようと思ったら、以下のどれかかな。個人的には2。
なお、高レベルと低レベルとの違いは子ノードとして多くのL2コンストラクトを持っているか否か(まったく厳密な定義ではなく、自分の中だけでの定義)。多くのL2コンストラクトを持っていると何回もエスケープハッチで潜らないといけないからコードが複雑になってしまう。

  1. 高レベルL2コンストラクト(ec2.Vpc)+エスケープハッチ
  2. 低レベルL2コンストラクト(ec2.Subnet)+エスケープハッチ
  3. L1コンストラクト

https://blog.mmmcorp.co.jp/2024/08/21/cdk-escape-hatch/

wakakawakaka

この手があったか!
L2 ConstructであるVpcインスタンスのプロパティ(属性)であるpublicSubnetsを使用してforループを実装することで短いコードで複数のSubnetまで潜ることが可能となる。

// escape hatch to attach Name tag to the public subnets
    for (const subnet of this.vpc.publicSubnets) {
      const publicSubnet = subnet.node.defaultChild as ec2.CfnSubnet;
      publicSubnet.addPropertyOverride('Tags', [
        {
          Key: 'Name',
          Value: `${this.vpc.node.id}-Public-${subnet.availabilityZone}`,
        },
      ]);
    } 
wakakawakaka

ただ、これを実現するためには各L2コンストラクトのコンストラクトツリーを正確に把握する必要があるよな...どのリソースがどのコンストラクトに含まれているかということを。これをチーム全体に浸透されるのは相当な難しさがあると思うんだよな...
Terraformと比較して、良いと思う部分は必要なリソースをとりあえずは作成してくれるところ。良くも悪くもあまりAWS詳しくない人でもデプロイはできてしまう。ただ、詳細な設計までしようと思ったら逆に相当詳しくないと整然とした設計は難しいかな。

wakakawakaka

やっぱりリソース全体の設計をキチッと決める形式とCDKは絶望的に相性が悪い。
ガバクラでCDKが使用されている以上、ここはCDKに合わせて変わっていくしかないと思う。

wakakawakaka

基本的には全体に命名する事はせずに、運用時に参照するリソースに対してのみ命名する
アプリの実行基盤のリソース確認:ECS, EC2, RDS, Lambda, CloudWatch Metrics
ログの確認:CloudWatch Logs, S3
CI/CDの状況確認:CodePipeline, CodeBuild, CodeDeploy

wakakawakaka

環境分割

  1. /bin/sample.tsにおいて、Stackを環境ごとに明示的に分割する
    npx cdk lsとしたときに構築しようとしているStackが全て表示される点は分かりやすくていい。
    ただ、CDKが推奨しているのはContext形式。理由は後で調べる。

https://note.com/asahi_ictrad/n/nf10a4c89c1bf


https://chariosan.com/2022/01/10/4-methods-to-configure-multiple-environments-in-the-aws-cdk/

https://github.com/mazyu36/cdk_sample_project

wakakawakaka

触る必要があるファイルはconfigだけであって欲しいよね。

wakakawakaka

命名のことを考えたら、Subnetは別コンストラクトで作成がいいかもね

wakakawakaka

開発環境ではALBがいるサブネット以外はシングルAZになると思うから、そういった個別の条件分岐にもこうすれば対応できる。

wakakawakaka

L1Constructからテンプレートを生成する流れ

シンセサイズすると、各 Stack は自分の下のツリーをさがして、CfnElement を継承しているコンストラクトを集めます。 つまり、テンプレートの断片を出力できるコンストラクト全般を集めます。 そうして集めた断片を整理し、テンプレートとして出力します。

このとき CfnResource や L1 の CfnBucket など、細かい具体的なクラスを気にする必要はありません。 CfnElement を継承したクラスであれば_toCloudFormationメソッドで断片を返す決まりになっているからです。

wakakawakaka

新しく出てきたものがいくつかありますが、VpcId 欄の !Ref はとても重要な組み込み関数です。
どのVPC内にサブネットを作成するかを VPC ID で指定しますが、CloudFormation で VPC が作られないと ID は分かりませんよね。そんな時は、同テンプレート内で設定した Logical ID (リソースの論理ID)を !Ref で参照してあげることで、VPC ID が自動的に当てはまるのです。

https://zenn.dev/tmasuyama1114/articles/aws-cloudformation-basics

wakakawakaka

別リソースで自動で作成されて、個別に作る必要がないパターンもあるから厄介
大きいリソースから作成するべきなのかな

wakakawakaka

IGrantableは「許可を受ける側」が実装するインターフェイスか

wakakawakaka

命名がどうなるのかはまとめておこうかな。

wakakawakaka

CloudFormationにおけるリソース名(論理ID)は「ConstructツリーPATH+8桁のハッシュ値」となる(利用できるのは英数字(A-Za-z0-9)のみなので、PATHにおける" / "は削除されている。また、Stack名はPATHから除外される)。
各リソースの名前が明示的に定義されなかった場合には、論理IDをベースにリソース名が作成される。論理ID→リソース名を作成する部分は、各AWSリソースごとに独自のロジックで決まる。