CloudFormation(以後CFn)で大規模なシステムを記述するときは、複数のテンプレートに分割して書いています。
分割するということは他のスタックの値を使用することがあるのですが、Cross Stack Referenceをしたくなかったので色々調べた結果Stackerにたどり着いた。
他のスタックに値を渡す方法 (復習?)
Cross Stack Reference
OutputsでExportし、読み込みたいテンプレートで!ImportValueする
...
Outputs:
VpcId:
Value: !Ref Vpc
Export:
Name: VpcId
...
HogeFuga:
Type: ...
Properties:
VpcId: !ImportValue VpcId
CFnの標準機能だけで実現可能
しかし、誰かがリソースをImportValueしていると、Export側のスタックが更新できない -> ボツの理由
Nested Stack
他のスタックの値を親スタックから参照して、子スタックに渡す感じ。
ただ、一部スタックのみ更新ができない(関係ないスタックも処理が走ってしまう)&更新セットが子スタックは作成されないので使わなかった
AWS CDK
AWS クラウド開発キット
TypeScriptなどで記述できる。
複数のスタックを利用したインフラも作成可能。
しかし、内部ではCross Stack Referenceを使っていたのでボツに
ShellScriptとAWS CLIで頑張る
今まではこれでやっていた。
そこそこいい感じに分割は可能。
しかし、テンプレートが増えるごとにShellScriptが増えて管理できなくなってきた。
stacker
stacker GitHub
複数のStack設定をyamlで記述して、コマンド一発で展開可能なツール
Cross Stack Referenceを使用せずに、パラメータの受け渡しが可能っぽい。
テンプレート自体もtroposphereで作成したものが使えるので、ロジックが記述可能
troposphereのスターが3.9kあるのに、stackerは579しかないのはなんでだろうね。。。
ディレクトリ構成
.
├── Pipfile ... Pipenv使ってるので
├── Pipfile.lock
├── blueprints ... troposphere + stacker blueprintを組み合わせたスクリプトを配置
│ ├── __init__.py
│ ├── instance.py
│ └── vpc.py
├── conf ... 環境ごとの設定ファイル。デプロイ時に指定可能
│ └── dev.env
├── stacker.yaml ... メインの設定ファイル
└── tasks.py ... TaskRunnerにInvokeを使用しているので (べつにMakefileでもいいと思う)
stacker.yaml
---
namespace: ${namespace} # CFnスタックの名前に使われる。実際の値はconf/dev.envに記載
stacker_bucket: stacker-tpl-${namespace} # CFn TemplateがUploadされるS3 Bucket名
sys_path: .
stacks:
- name: vpc # 実際に作成されるスタック名は「${namespace}-vpc」になります
class_path: blueprints.vpc.Vpc # BluePrintのClass名までを記載
variables:
CidrBlock: '10.0.0.0/16'
# List表記にしたものはBlueprintでfor...inにして使う
PublicSubnets:
- '10.0.0.0/24'
- '10.0.1.0/24'
PrivateSubnets:
- '10.0.128.0/24'
- '10.0.129.0/24'
- name: instance
class_path: blueprints.instance.Instance
variables:
SubnetId: ${output vpc::PublicSubnet0} # 他のStackのOutputを取得する
ImageId: ami-0c3fd0f5d33134a76
blueprints/vpc.py
troposphere_mateはtroposphereに型定義などが追加されてIDEの入力支援が色々使えるやつです。
from stacker.blueprints.base import Blueprint
from troposphere import Ref
from troposphere_mate import Output
import troposphere_mate.ec2 as ec2
class Vpc(Blueprint):
# stacker.yamlのvariablesに記載する内容の定義
VARIABLES = {
"CidrBlock": {
"type": str,
"description": "Vpc CidrBlock"
},
"PublicSubnets": {
"type": list,
"description": "Public Subnet CidrBlock List"
},
"PrivateSubnets": {
"type": list,
"description": "Private Subnet CidrBlock List"
}
}
def create_template(self):
var = self.get_variables()
self.template.description = "VPC Stack"
vpc = self.template.add_resource(
ec2.VPC(
'Vpc',
CidrBlock=var['CidrBlock'],
EnableDnsHostnames=True,
EnableDnsSupport=True
)
)
self.template.add_output(Output('VpcId', Ref(vpc)))
internet_gateway = self.template.add_resource(ec2.InternetGateway('Igw'))
self.template.add_resource(
ec2.VPCGatewayAttachment(
'VpcGwAttachment',
VpcId=Ref(vpc),
InternetGatewayId=Ref(internet_gateway)
)
)
default_route_table = self.template.add_resource(
ec2.RouteTable(
'DefaultRouteTable',
VpcId=Ref(vpc)
)
)
self.template.add_resource(
ec2.Route(
'DefaultRoute',
RouteTableId=Ref(default_route_table),
DestinationCidrBlock='0.0.0.0/0',
GatewayId=Ref(internet_gateway)
)
)
# Create Public Subnet
for k, v in enumerate(var['PublicSubnets']): # stacker.yamlでList表記にしていたものを使用
public_subnet = self.template.add_resource(
ec2.Subnet(
f'PublicSubnet{k}',
CidrBlock=v,
VpcId=Ref(vpc)
)
)
self.template.add_output(Output(f'PublicSubnet{k}', Ref(public_subnet)))
self.template.add_resource(
ec2.SubnetRouteTableAssociation(
f'PublicSubnetRouteTableAssociation{k}',
RouteTableId=Ref(default_route_table),
SubnetId=Ref(public_subnet)
)
)
# Create Private Subnet
for k, v in enumerate(var['PrivateSubnets']):
private_subnet = self.template.add_resource(
ec2.Subnet(
f'PrivateSubnet{k}',
CidrBlock=v,
VpcId=Ref(vpc)
)
)
self.template.add_output(Output(f'PrivateSubnet{k}', Ref(private_subnet)))
デプロイ
$ stacker build --region ap-northeast-1 conf/dev.env stacker.yaml
削除
# 削除対象を確認
$ stacker destroy --region ap-northeast-1 conf/dev.env stacker.yaml
[2019-12-18T15:36:30] Using default AWS provider mode
[2019-12-18T15:36:30] Plan "Destroy stacks":
[2019-12-18T15:36:30] - step: 1: target: "instance", action: "_destroy_stack"
[2019-12-18T15:36:30] - step: 2: target: "vpc", action: "_destroy_stack"
[2019-12-18T15:36:30] To execute this plan, run with "--force" flag.
# 削除実行
$ stacker destroy --region ap-northeast-1 conf/dev.env stacker.yaml --force
まとめ
複数のスタックの設定を一箇所で管理可能で、環境ごとの差分は.envとして管理ができるので非常に楽。
他スタックの値のImportがImportValueではなく、aws cliで取得した値を貼り付けているような感じなので参照先のリソースの更新も可能。
troposphereで記述できるため、開発環境だけ展開するインスタンスなども設定可能