【AWS】AWS CDKで作成したコードをより管理しやすくするためにやったこと（ファイル分割、ネストスタック）

#はじめに
[先日の記事](https://qiita.com/risa_someya/items/8d87853c00c02fbb8bb1)でCDKを初めて触ってから、IaCへの取り組みが本格化してきた今日この頃です。

私が今までに実装したことがあるのは、一部のAWSサービスの構築をIaCで自動化できるようにするといったものにとどまっておりそこまで複雑なコードは書いていません。ですがシステムを構築するとなるとリソースの数もコードの行数も多くなり、かなり複雑なコードが出来上がることが予想されます。
「あのリソースのコード、どこに書いてあるんだっけ？」とならないように、管理のしやすさという面で活用できそうなコードをご紹介します。

なお、以下で記載する内容は公開日時点の情報となるため、本記事を閲覧いただいた日によっては情報が古くなっている場合もあるかもしれませんがそちらご了承いただければと思います。

#管理しやすくするためにやったこと
##ファイル分割
CDKで作成したコードをデプロイする際、```cdk deploy```コマンドを実行しますが、そのコマンドを実行するときにリソース情報などを参照している元となっているファイルは「app.py」ファイルになります。
CDKを最初にインストールして作成されたデフォルトのコードには、「app.py」ファイル内で「{作業フォルダ}/cdk_workshop_stack.py」ファイルに定義されているクラス「CdkWorkshopStack」を実行するようなコードが記載されています。
[CDKワークショップ](https://summit-online-japan-cdk.workshop.aws/)では「cdk_workshop_stack.py」ファイル内ですべてのリソースを記載するような手順になっていますが、ここでリソースごとにファイルを分割してしまいます。

今回は例としてS3バケットを作成するファイルとSNSトピックを作成するファイルに分割して実装してみようと思います。

まずはファイルを以下の2つ作成します。

```python:{作業フォルダ}/s3_hands_on_stack.py
from aws_cdk import (
    aws_s3 as s3,
    core
)

class S3HandsOnStack(core.Stack):
    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # S3バケットを"S3HandsOnBucket"という名前で作成
        my_bucket = s3.Bucket(self, "S3HandsOnBucket")
```

```python:{作業フォルダ}/sns_hands_on_stack.py
from aws_cdk import (
    aws_sns as sns,
    aws_sns_subscriptions as subs,
    core
)

class SNSHandsOnStack(core.Stack):
    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # SNSトピックを「sns-hands-on-topic」という名前で作成
        sns_topic=sns.Topic(self, "sns-hands-on-topic",
            display_name="sns-hands-on-topic",
            topic_name="sns-hands-on-topic"
        )
        # 受信可能なメールアドレスを以下で設定
        sns_topic.add_subscription(subs.EmailSubscription('メールアドレス'))
```

これでS3バケットの記述については「s3_hands_on_stack.py」にあること、SNSトピックの記述については「sns_hands_on_stack.py」にあることがファイル名からも判断しやすくなりました。
上記2つのファイルが作成できたら、app.pyファイル内でS3とSNSを作成するスタックを定義してあげます。

```python:app.py
#!/usr/bin/env python3

from aws_cdk import core

from cdk_workshop.s3_hands_on_stack import S3HandsOnStack
from cdk_workshop.sns_hands_on_stack import SNSHandsOnStack

app = core.App()
S3HandsOnStack(app, "s3-hands-on-stack")
SNSHandsOnStack(app, "sns-hands-on-stack")

app.synth()
```

app.pyファイルの更新ができた状態で```cdk ls```コマンドを実行すると、CFnスタックが2つ作成されていることが確認できます。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk ls
s3-hands-on-stack
sns-hands-on-stack
```

2つのスタックを一括でデプロイしたい場合は```cdk deploy --all```コマンドを実行、一つずつデプロイしたい場合は```cdk deploy {スタック名}```で実行すればOKです。
今回は一括でデプロイしてみようと思います。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk deploy --all
s3-hands-on-stack
s3-hands-on-stack: deploying...
[0%] start: Publishing dee4ac45431b6e19c1549a6189d52b22edb712c042f589a8e8a1d8655453e476:current_account-current_region
[100%] success: Published dee4ac45431b6e19c1549a6189d52b22edb712c042f589a8e8a1d8655453e476:current_account-current_region
s3-hands-on-stack: creating CloudFormation changeset...
s3-hands-on-stack | 0/3 | 17:13:18 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | s3-hands-on-stack User Initiated
s3-hands-on-stack | 0/3 | 17:13:24 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | s3-hands-on-stack User Initiated
s3-hands-on-stack | 0/3 | 17:13:29 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | S3HandsOnBucket (S3HandsOnBucketABAFD3E3)
s3-hands-on-stack | 0/3 | 17:13:30 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata)
s3-hands-on-stack | 0/3 | 17:13:31 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | S3HandsOnBucket (S3HandsOnBucketABAFD3E3) Resource creation Initiated
s3-hands-on-stack | 0/3 | 17:13:31 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata) Resource creation Initiated
s3-hands-on-stack | 1/3 | 17:13:31 | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata/Default (CDKMetadata)
s3-hands-on-stack | 2/3 | 17:13:51 | CREATE_COMPLETE      | AWS::S3::Bucket    | S3HandsOnBucket (S3HandsOnBucketABAFD3E3)
s3-hands-on-stack | 3/3 | 17:13:53 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | s3-hands-on-stack

 ✅  s3-hands-on-stack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:{account_id}:stack/s3-hands-on-stack/875e33b0-5bec-11ec-b1b2-0e9901704a6b
sns-hands-on-stack
sns-hands-on-stack: deploying...
[0%] start: Publishing 419a1b162cabb353d0ada887b6898c6689cb044348ef8ae647376d56e6441851:current_account-current_region
[100%] success: Published 419a1b162cabb353d0ada887b6898c6689cb044348ef8ae647376d56e6441851:current_account-current_region
sns-hands-on-stack: creating CloudFormation changeset...
sns-hands-on-stack | 0/4 | 17:13:57 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | sns-hands-on-stack User Initiated
sns-hands-on-stack | 0/4 | 17:14:02 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | sns-hands-on-stack User Initiated
sns-hands-on-stack | 0/4 | 17:14:07 | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | sns-hands-on-topic (snshandsontopicA8DE3DBD)
sns-hands-on-stack | 0/4 | 17:14:08 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata/Default (CDKMetadata)
sns-hands-on-stack | 0/4 | 17:14:08 | CREATE_IN_PROGRESS   | AWS::SNS::Topic        | sns-hands-on-topic (snshandsontopicA8DE3DBD) Resource creation Initiated
sns-hands-on-stack | 0/4 | 17:14:10 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata     | CDKMetadata/Default (CDKMetadata) Resource creation Initiated
sns-hands-on-stack | 1/4 | 17:14:10 | CREATE_COMPLETE      | AWS::CDK::Metadata     | CDKMetadata/Default (CDKMetadata)
sns-hands-on-stack | 2/4 | 17:14:19 | CREATE_COMPLETE      | AWS::SNS::Topic        | sns-hands-on-topic (snshandsontopicA8DE3DBD)
sns-hands-on-stack | 2/4 | 17:14:21 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | sns-hands-on-topic/{メールアドレス} (snshandsontopic{メールアドレス}6726254F)
sns-hands-on-stack | 2/4 | 17:14:22 | CREATE_IN_PROGRESS   | AWS::SNS::Subscription | sns-hands-on-topic/{メールアドレス} (snshandsontopic{メールアドレス}6726254F) Resource creation Initiated
sns-hands-on-stack | 3/4 | 17:14:22 | CREATE_COMPLETE      | AWS::SNS::Subscription | sns-hands-on-topic/{メールアドレス} (snshandsontopic{メールアドレス}6726254F)
sns-hands-on-stack | 4/4 | 17:14:24 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | sns-hands-on-stack

 ✅  sns-hands-on-stack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:{account_id}:stack/sns-hands-on-stack/9e796c90-5bec-11ec-aed3-0a41d4e7c861
```

コマンドが正常終了し、コンソール上でもリソースが正常に作成されたことが確認できました。
![キャプチャ1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/87a4d9ba-5664-c73d-9cf5-35f1a84ee4bf.jpeg)
![キャプチャ2-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/6d554593-968c-8247-b149-67b8b425968a.jpeg)
![キャプチャ3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/4b31b555-f53c-fc5c-e219-c486a1d8e02d.jpeg)


##ネストスタック
ファイルが分割されているという点では上記「ファイル分割」の方式と変わりありませんが、こちらでは親スタックを一つ作成し、その中にS3作成用とSNS作成用の子スタックをネストさせる方式をとります。

「ファイル分割」の方で作成したファイルを利用して、以下のようにコードを修正します。

```python:{作業フォルダ}/s3_hands_on_stack.py
from aws_cdk import (
    aws_s3 as s3,
    core
)

class S3HandsOnStack(core.NestedStack):      # ←★「(core.Stack)」から「(core.NestedStack)」に修正★
    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # S3バケットを"S3HandsOnBucket"という名前で作成
        my_bucket = s3.Bucket(self, "S3HandsOnBucket")
```

```python:{作業フォルダ}/sns_hands_on_stack.py
from aws_cdk import (
    aws_sns as sns,
    aws_sns_subscriptions as subs,
    core
)

class SNSHandsOnStack(core.NestedStack):      # ←★「(core.Stack)」から「(core.NestedStack)」に修正★
    def __init__(self, scope: core.Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # SNSトピックを「sns-hands-on-topic」という名前で作成
        sns_topic=sns.Topic(self, "sns-hands-on-topic",
            display_name="sns-hands-on-topic",
            topic_name="sns-hands-on-topic"
        )
        # 受信可能なメールアドレスを以下で設定
        sns_topic.add_subscription(subs.EmailSubscription('メールアドレス'))
```

```python:app.py
#!/usr/bin/env python3

from aws_cdk import core

from cdk_workshop.s3_hands_on_stack import S3HandsOnStack
from cdk_workshop.sns_hands_on_stack import SNSHandsOnStack

# class ParentStack(core.Stack):を追加
class ParentStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # ParentStackにネストされるスタックを以下に記載
        S3HandsOnStack(self, "S3HandsOnStack")
        SNSHandsOnStack(self, "SnsHandsOnStack")

app = core.App()
ParentStack(app, "ParentStack")
app.synth()
```

app.pyファイルの更新ができた状態で```cdk ls```コマンドを実行すると、親スタックのみ確認することができます。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk ls
ParentStack
```

こちらもデプロイしてみます。今回は親スタック一つだけデプロイする形になるので、実行コマンドは```cdk deploy```になります。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk deploy
ParentStack: deploying...
[0%] start: Publishing 2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685e:current
[50%] success: Published 2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685e:current
[50%] start: Publishing a21598701555f082a8d16468298a8f3e8d65212d008090ea838de1e1b1ab93aa:current
[100%] success: Published a21598701555f082a8d16468298a8f3e8d65212d008090ea838de1e1b1ab93aa:current
ParentStack: creating CloudFormation changeset...
ParentStack | 0/4 | 10:50:43 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | ParentStack User Initiated
ParentStack | 0/4 | 10:50:49 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | ParentStack User Initiated
ParentStack | 0/4 | 10:51:23 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | SnsHandsOnStack.NestedStack/SnsHandsOnStack.NestedStackResource (SnsHandsOnStackNestedStackSnsHandsOnStackNestedStackResource891D5D99)
ParentStack | 0/4 | 10:51:23 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | S3HandsOnStack.NestedStack/S3HandsOnStack.NestedStackResource (S3HandsOnStackNestedStackS3HandsOnStackNestedStackResource67B1E460)
ParentStack | 0/4 | 10:51:23 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata         | S3HandsOnStack/CDKMetadata/Default (CDKMetadata)
ParentStack | 0/4 | 10:51:24 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | S3HandsOnStack.NestedStack/S3HandsOnStack.NestedStackResource (S3HandsOnStackNestedStackS3HandsOnStackNestedStackResource67B1E460) Resource creation Initiated
ParentStack | 0/4 | 10:51:24 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | SnsHandsOnStack.NestedStack/SnsHandsOnStack.NestedStackResource (SnsHandsOnStackNestedStackSnsHandsOnStackNestedStackResource891D5D99) Resource creation Initiated
ParentStack | 0/4 | 10:51:25 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata         | S3HandsOnStack/CDKMetadata/Default (CDKMetadata) Resource creation Initiated
ParentStack | 1/4 | 10:51:25 | CREATE_COMPLETE      | AWS::CDK::Metadata         | S3HandsOnStack/CDKMetadata/Default (CDKMetadata)
ParentStack | 2/4 | 10:51:46 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | SnsHandsOnStack.NestedStack/SnsHandsOnStack.NestedStackResource (SnsHandsOnStackNestedStackSnsHandsOnStackNestedStackResource891D5D99)
ParentStack | 3/4 | 10:51:58 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | S3HandsOnStack.NestedStack/S3HandsOnStack.NestedStackResource (S3HandsOnStackNestedStackS3HandsOnStackNestedStackResource67B1E460)
ParentStack | 4/4 | 10:51:59 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | ParentStack

 ✅  ParentStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:{account_id}:stack/ParentStack/bee91d80-5edb-11ec-8994-0af111445981
```

コマンドが正常終了し、コンソール上でもリソースが正常に作成されたことが確認できました。
![キャプチャ4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/07bcb9ee-16ca-2d5a-b53d-c1d47bbc565d.jpeg)
![キャプチャ5-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/3a7d2e51-12e1-cbba-9909-00b7fef2c467.jpeg)
![キャプチャ6.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629857/6a7d73ec-999a-ec95-f009-b38d32dd45d4.jpeg)

##メリットデメリット
上記でご紹介した2つの方法についてのメリットデメリットは以下のようなものがあるかなと思います。
AWS側の制限についてなどは含めず、あくまで現時点での私の主観でのメリデメとご認識いただければと思います。

###ファイル分割した際のメリットデメリット
* メリット
    * コマンドライン上でパラメータの確認がしやすい。
        * →後述する方式と比べてのメリットという意味合いにはなりますが、実際にパラメータを変更してcdk diffした際に、どこを修正したのかがわかりやすく表示されます。
   実際にS3バケット名を変更するようにコード修正したのちcdk diffした際の出力内容は以下の通りです。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk diff
Stack s3-hands-on-stack
Resources
[-] AWS::S3::Bucket S3HandsOnBucketABAFD3E3 orphan
[+] AWS::S3::Bucket S3HandsOnBucket02 S3HandsOnBucket0235D460F7

Stack sns-hands-on-stack
There were no differences
```


* デメリット
    * リソースが増えてスタック数が増えると一括デプロイをしたときに完了までに時間がかかる。
        * →今回のように数個であれば大したことはありませんが、以前約80スタックほど作成して一括デプロイしたら30分くらいかかりました。

###ネストスタックにした際のメリットデメリット
* メリット
    * リソースが増えてスタックが増えても、実行するのは親スタックのみになるので、デプロイ完了までの時間が「ファイル分割」した時よりも短縮できる。
        * →「ファイル分割」のデメリットに記載したように80スタックほどを親スタックにネストして実行したらデプロイ完了まで5分ほどとなりました。

    * システム構築時、領域ごとにスタックを分けて管理することが可能になる。
        * →Public領域とSecure領域というように構築を行う際に、親スタックを2つ用意してそれぞれのリソースをネストさせれば、リソースの名前からのみでなく、スタックからも領域の判断がしやすくなるかと思います。

* デメリット
    * コマンドライン上でパラメータの確認ができない。
        * →cdk diffを実行しても、差分がある子スタックの情報が出力されるのみで、実際にどのパラメータが差分があるのかの確認ができません。
   実際にcdk diffした際の出力内容は以下の通りです。

```
(.venv) C:\Users\user\Documents\cdk-workshop>cdk diff
Stack ParentStack
Parameters
[-] Parameter AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eS3Bucket92B59C08: {"Type":"String","Description":"S3 bucket for asset \"2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685e\""}
[-] Parameter AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eS3VersionKeyE0309B3D: {"Type":"String","Description":"S3 key for asset version \"2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685e\""}
[-] Parameter AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eArtifactHash497BBCE1: {"Type":"String","Description":"Artifact hash for asset \"2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685e\""}
[+] Parameter AssetParameters/cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9/S3Bucket AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9S3Bucket289389DE: {"Type":"String","Description":"S3 bucket for asset \"cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9\""}
[+] Parameter AssetParameters/cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9/S3VersionKey AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9S3VersionKeyAE455EF4: {"Type":"String","Description":"S3 key for asset version \"cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9\""}
[+] Parameter AssetParameters/cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9/ArtifactHash AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9ArtifactHash5DE3CE4D: {"Type":"String","Description":"Artifact hash for asset \"cb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9\""}

Resources
[~] AWS::CloudFormation::Stack S3HandsOnStack.NestedStack/S3HandsOnStack.NestedStackResource S3HandsOnStackNestedStackS3HandsOnStackNestedStackResource67B1E460
 └─ [~] TemplateURL
     └─ [~] .Fn::Join:
         └─ @@ -11,7 +11,7 @@
            [ ] },
            [ ] "/",
            [ ] {
            [-]   "Ref": "AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eS3Bucket92B59C08"
            [+]   "Ref": "AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9S3Bucket289389DE"
            [ ] },
            [ ] "/",
            [ ] {
            @@ -21,7 +21,7 @@
            [ ]   "Fn::Split": [
            [ ]     "||",
            [ ]     {
            [-]       "Ref": "AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eS3VersionKeyE0309B3D"
            [+]       "Ref": "AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9S3VersionKeyAE455EF4"
            [ ]     }
            [ ]   ]
            [ ] }
            @@ -34,7 +34,7 @@
            [ ]   "Fn::Split": [
            [ ]     "||",
            [ ]     {
            [-]       "Ref": "AssetParameters2540441e9d53ae8f811f2c5ae8780b58fbc9f643a3fb7601ed0a764ecfba685eS3VersionKeyE0309B3D"
            [+]       "Ref": "AssetParameterscb77d71ee4d97c35a236462242b7aec65b0e2e918456fa06da3cb67709f4ddc9S3VersionKeyAE455EF4"
            [ ]     }
            [ ]   ]
            [ ] }
```


#おわりに
今回はAWS CDKでコーディングする際の管理方法について書いてみました。
メリットデメリットを出してみましたが、最終的にはシステムのリソース数も踏まえて判断するのもよいかと思います。
CDKは先日v2が公開されましたし、今後もどんどん活用の場が増えていくサービスと感じているので、今後もサービスの動向は追っていきたいです。

#参考
[AWS CDKでネストされたスタックを作ってみる](https://dev.classmethod.jp/articles/aws-cdk-cfn-nest-stack/)

[CDKワークショップ](https://summit-online-japan-cdk.workshop.aws/)