CloudFormation Nested Stack の作り方


CloudFormation テンプレート書いていると、1枚のテンプレートが大きくなり数百行は優に超えてくるので分割したくなります。 分割するとテンプレートの枚数だけデプロイコマンドを叩く必要があるので シェルスクリプトや Makefile を書かないと簡単にデプロイはしにくいです。 テンプレート間の依存関係も考慮した上でデプロイする必要もあります。 そこで Nested Stack でテンプレートを構築し、スタックを作成する方法を紹介します。

Nested Stack とは

分割された CloudFormation Template からスタックを作成・管理するには2通りの方法があります。

今回紹介する Nested Stack とは何でしょうか。 公式ドキュメントより抜粋します。

ネストされたスタックは、他のスタックの一部として作成されたスタックです。ネストされたスタックは、AWS::CloudFormation::Stack リソースを使用して別のスタック内に作成します。

少し分かりづらいですが、親スタックが子スタックを作成するということです。

実際に CloudFormation Nested Stack でスタックを構築してみましょう。

親テンプレートとネットワーク用テンプレート(VPC, Subnet)の2枚を使用します。

テンプレート

.
├── network.yml
└── parent.yml

parent.yml

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: ./network.yml

network.yml

AWSTemplateFormatVersion: "2010-09-09"

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 172.16.0.0/16
      EnableDnsSupport: "false"
      EnableDnsHostnames: "false"
      InstanceTenancy: dedicated

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      CidrBlock: 172.16.0.0/24
      VpcId: !Ref VPC

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      CidrBlock: 172.16.1.0/24
      VpcId: !Ref VPC

スタックの作り方

本来は親テンプレートの Properties.TemplateURL に S3 へ upload した子テンプレートの URL を指定します。 しかし、子テンプレートを S3 に upload してからさらに親テンプレートも変更するとなると大変です。

そこで package サブコマンド を使います。 親テンプレートの Properties.TemplateURL に子テンプレートの相対パスを指定し、 このコマンドオプションに親テンプレートと upload する S3 bucket name を指定し実行すると、

  1. 子テンプレートを S3 bucket へ upload
  2. 親テンプレートの Properties.TemplateURL を upload した子テンプレートの URL に書き換え

となります。親スタックを作成するには、生成された 2. のテンプレートを使用します。

Nested Stack 作成

実際に Nested Stack を作成してみましょう。

.
├── network.yml
└── parent.yml

先と同じこれらのファイルを使います。

$ aws cloudformation package --template-file ./parent.yml --s3-bucket cfn-nested-stack-demo --output-template-file /tmp/response.yml
Uploading to e4821e88e99c0568aa79739fbaa80f2c.template  631 / 631.0  (100.00%)
Successfully packaged artifacts and wrote output template to file /tmp/response.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /tmp/response.yml --stack-name <YOUR STACK NAME>

package サブコマンドを実行すると、子テンプレートが S3 bucket へ upload されます。

$ aws s3 ls s3://cfn-nested-stack-demo/
2020-04-21 12:39:13        631 e4821e88e99c0568aa79739fbaa80f2c.template
$ aws s3 cp s3://cfn-nested-stack-demo/e4821e88e99c0568aa79739fbaa80f2c.template -
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 172.16.0.0/16
      EnableDnsSupport: 'false'
      EnableDnsHostnames: 'false'
      InstanceTenancy: dedicated
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
        - 0
        - Fn::GetAZs: ''
      CidrBlock: 172.16.0.0/24
      VpcId:
        Ref: VPC
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
        Fn::Select:
        - 1
        - Fn::GetAZs: ''
      CidrBlock: 172.16.1.0/24
      VpcId:
        Ref: VPC

作成された親テンプレート /tmp/response.yml は以下のようになります。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.ap-northeast-1.amazonaws.com/cfn-nested-stack-demo/e4821e88e99c0568aa79739fbaa80f2c.template

子テンプレートを指定する TemplateURL が変わっていることを確認できました。

ではこのテンプレートを使って Nested Stack を作成しましょう。

$ aws cloudformation deploy --template-file /tmp/response.yml --stack-name cfn-nested-stack-demo

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - cfn-nested-stack-demo

作成されました。

Nested Stack が作成されると、子スタックは以下のようになります。

  • 親スタック名-子スタックリソース名-ランダム文字列 というスタック名になる
  • AWS Management Console では子テンプレートに ネストされたNESTED というタグが確認できる

management-console-nested-stack

Nested Stack 更新

Nested Stack を更新するには、親スタックを操作します。

ここでは新たに PublicSubnet3 を追加しましょう。

network.yml

diff --git a/network.yml b/network.yml
index 9a3b5a6..5b32343 100644
--- a/network.yml
+++ b/network.yml
@@ -28,3 +28,13 @@ Resources:
           - Fn::GetAZs: ""
       CidrBlock: 172.16.1.0/24
       VpcId: !Ref VPC
+
+  PublicSubnet3:
+    Type: AWS::EC2::Subnet
+    Properties:
+      AvailabilityZone:
+        Fn::Select:
+          - 2
+          - Fn::GetAZs: ""
+      CidrBlock: 172.16.2.0/24
+      VpcId: !Ref VPC

同じように package, deploy サブコマンドを実行します。

$ aws cloudformation package --template-file ./parent.yml --s3-bucket cfn-nested-stack-demo --output-template-file /tmp/response.yml
Uploading to 7051d70fce833c9fca4fa573b9cfc96d.template  833 / 833.0  (100.00%)
Successfully packaged artifacts and wrote output template to file /tmp/response.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /tmp/response.yml --stack-name <YOUR STACK NAME>
$ aws cloudformation deploy --template-file /tmp/response.yml --stack-name cfn-nested-stack-demo

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - cfn-nested-stack-demo

更新できました。

AWS 公式では Nested Stack を更新・削除する場合親スタックを操作することが推奨されています。 子スタックを操作するとどうなるのでしょうか。

マネコンから削除します

delete nested child stack

done deleted nested child stack

子スタックのみ削除され、親スタックが残った状態になりました。 また、子スタックで管理されていた VPC, Subnet も同様削除されました。

ここでは省略しますが、この状態で親スタックのみを削除することもできました。

さて、この半端に残ったスタックを更新するとどうなるのでしょうか。

今度は PublicSubnet2, PublicSubnet3 を削除してみます。

diff --git a/network.yml b/network.yml
index 5b32343..addc850 100644
--- a/network.yml
+++ b/network.yml
@@ -18,23 +18,3 @@ Resources:
           - Fn::GetAZs: ""
       CidrBlock: 172.16.0.0/24
       VpcId: !Ref VPC
-
-  PublicSubnet2:
-    Type: AWS::EC2::Subnet
-    Properties:
-      AvailabilityZone:
-        Fn::Select:
-          - 1
-          - Fn::GetAZs: ""
-      CidrBlock: 172.16.1.0/24
-      VpcId: !Ref VPC
-
-  PublicSubnet3:
-    Type: AWS::EC2::Subnet
-    Properties:
-      AvailabilityZone:
-        Fn::Select:
-          - 2
-          - Fn::GetAZs: ""
-      CidrBlock: 172.16.2.0/24
-      VpcId: !Ref VPC
$ aws cloudformation package --template-file ./parent.yml --s3-bucket cfn-nested-stack-demo --output-template-file /tmp/response.yml
Uploading to c1d0483d57bc44d918b4e545b37ee3f6.template  429 / 429.0  (100.00%)
Successfully packaged artifacts and wrote output template to file /tmp/response.yml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /tmp/response.yml --stack-name <YOUR STACK NAME>
$ aws cloudformation deploy --template-file /tmp/response.yml --stack-name cfn-nested-stack-demo

Waiting for changeset to be created..
Waiting for stack create/update to complete

Failed to create/update the stack. Run the following command
to fetch the list of events leading up to the failure
aws cloudformation describe-stack-events --stack-name cfn-nested-stack-demo

failed update nested stack

failed update nested stack event

スタック更新に失敗しました。

子スタックを削除した場合は Nested Stack 自体削除するしかなさそうです。 Nested Stack を用いて AWS リソースの作成・変更等を運用していくのであれば、公式推奨通りに親スタックのみを操作するのがよいでしょう

Nested Stack の使いどころ

Cross Stack Reference に比べると分割されたスタックをまとめて管理できデプロイも簡単になるので、 Nested Stack を進んで使っていこう!とも思ったのですがデメリットもあります。

  1. 更新時の影響範囲が広い

    基本的に Nested Stack 更新時は Nested 管理下の子スタックにも更新が掛かります

  2. 子スタックの Change Set が見れない

    子スタック(AWS::CloudFormation::Stack リソースタイプ)が Modify されるのはわかるのですが、 詳細までは見れません

たくさんのリソースを管理したい、CI/CD Pipeline に CloudFormation Stack 更新を乗せたい、 ということあれば、Nested Stack で管理しないことをお勧めします (個人的には変更差分の詳細が見れないのが辛い…)。

上記デメリットを許容できるのであれば Nested Stack で構築するのもありだと思います。

さいごに

今回は CloudFormation Nested Stack の作り方・デメリット等紹介しました。 package, deploy サブコマンドを使用して CloudFormation Nested Stack を作成・更新したり、 子スタックのみを削除した際の挙動を確認しました。

CloudFormation Nested Stack を使用する際には、まずは運用まで回せるのかを検証することをお勧めします。