はじめに
この記事では、Cloudformationで複数スタックを一括で更新する方法について取り上げます。
AWSリソースのIaCをする際、CloudformationやTerraformなどで管理することが多いと思います。Terraformではterraform apply
で複数のテンプレートをデプロイできる一方、Cloudformationでは公式から複数スタックを一括で更新をするすべが用意されていません。
AWS CLIでは、aws cloudformation deploy
などを使って単一スタックに対してchangesetの作成及び実行、もしくは直接更新をかけることができます。ただ、これを利用して単純に複数のテンプレートをループさせて順々に更新していくと問題が生じます。スタック間で共有している値に変更があったりする場合、依存しているスタックは更新する順番を考慮してchangesetの実行を行う、もしくはスタック変更を反映させる必要があります。
Cloudformationのスタック間で値を共有する方法
スタック間で値を共有する方法としては、Nested stackを利用する方法もありますが、今回はCross-stack referenceのみを考えたいと思います。
Cross-stack referenceとは、共有する値をOutputsに書いて出力することで他テンプレートでも使用可能になります。参照したい場合はFn::ImportValue
とOutputs
で出力した名前を書くことで参照できます。
Export(値やリソースを共有する側)
Outputs:
IAMRole:
Value: !Ref IAMRole
Export:
Name: !Sub "${AWS::StackName}::IAMRole"
Import(値やリソースを参照する側)
Parameters:
ImportStackName:
Type: "String"
Resources:
IAMManagedPolicyCI:
Type: 'AWS::IAM::ManagedPolicy'
Properties:
...
Roles:
- Fn::ImportValue: !Sub '${ImportStackName}::IAMRole'
自作スクリプトを組むときの注意
最初に書いたように、スクリプトを組んで一括でスタックの変更をしようとすると更新の順番を考える必要があります。
例えば、参照元の値が更新される場合は参照元のスタックを更新したあと、その値を使用しているスタックを更新する必要があります。
一方、Outputsの削除と参照しているリソースもしくはスタックを削除する場合は、参照しているスタックを更新したあと、Outputsを含むスタックを更新する必要があります。
また、スタック間で共依存や循環参照の関係があった場合はエラーにする必要があるなど、いい感じに自動反映をしてくれるスクリプトを作成しようとすると考慮しないといけないケースが多いです。
OSSツール
このようにスクリプトを組むのは骨が折れます。依存による更新の順番を考慮した上で変更の一括反映が可能なOSSツールがないか探してみた結果、いくつかはありました。
ツール名 | URL | スター数 |
---|---|---|
Sceptre | https://github.com/Sceptre/sceptre | 1.4k |
stacker | https://github.com/cloudtools/stacker | 695 |
CloudGenesis | https://github.com/LifeWay/CloudGenesis | 35 |
この中でSceptreを今回使って上で書いた注意点が解消されているか試してみたいと思います。
Sceptre
Sceptreを使うためには、CloudformationテンプレートとSceptre用のconfigファイルをテンプレート毎に用意する必要があります。
ディレクトリ構成例としては、以下のようになります。
.
├── config
│ ├── config.yaml
│ └── dev
│ ├── config.yaml
│ └── vpc.yaml
└── templates
└── vpc.yaml
configファイルはリージョンやプロフィールなど共通設定ファイルであり、子の階層で親のconfigファイルと同じ設定があった場合、子の設定ファイルの値で上書きされる仕組みになっています。
テンプレートごとの設定ファイル(config/dev/vpc.yamlなど)では、テンプレートのパス、スタック名などが設定することができ、既存のスタックに使用することも可能です。
使い方としては、pipコマンドもしくはdockerでインストール可能で、あとはsceptreコマンドをconfigとtemplateディレクトリが存在するディレクトリで実行します。
sceptreコマンドは、sceptre create/update/delete
とスタックの操作に関連するものがあるのですが、スタックの作成・更新時はsceptre launch
を使うと、存在しないスタックを新しく作成して、既存のスタックの更新をするので便利です。
上のディレクトリ構成例の場合、以下のコマンドでconfig/dev
ディレクトリ内で設定したテンプレートのデプロイがまとめてできます。
検証
Sceptreの挙動を確認するために、以下のようなディレクトリ構成で検証してみます。
.
├── config
│ ├── config.yaml
│ └── dev
│ ├── base.yaml
│ └── depends.yaml
└── templates
├── base.yaml
└── depends.yaml
ファイルの中身は以下。今回は簡単な検証のためにbase.yaml
でS3バケットを用意して、depends.yaml
でbase.yaml
のS3バケットの名前を使って別のS3バケットを作るのみのテンプレート構成です。
project_code: test-sceptre
region: ap-northeast-1
template:
path: base.yaml
type: file
template:
path: depends.yaml
type: file
parameters:
BaseStackName: !stack_output dev/base.yaml::S3BucketName
AWSTemplateFormatVersion: '2010-09-09'
Resources:
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: "sceptre-test-hoge-bucket"
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: !Sub '${AWS::StackName}::S3BucketName'
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
BaseStackName:
Type: String
Resources:
S3Bucket:
Type: 'AWS::S3::Bucket'
Properties:
BucketName: !Sub "${BaseStackName}-2"
それでは、スタックの作成、更新、削除と試してみたいと思います。
スタック更新に失敗した場合(既に存在している名前でS3バケットを作成した)
他のケースも試してみましたが、Sceptreはスタック依存を考慮してそれぞれデプロイしてくれそうです。
まとめ
今回の検証で、Sceptreを使うことでCloudformationでもスタック間の依存関係を考慮しながら一括で複数のスタックの更新をできることがわかりました。
同様にプログラムを書くこともできるとは思うのですが、車輪の再発明になるのであれば既に公開されているOSSを使ったり、必要に応じてカスタマイズして使っていく方が良いかもしれませんね。