[AWS CloudFormation]クロススタック参照でテンプレートを分割管理する
はじめに
AWS CloudFormationによる環境作成のコード化・自動化は、AWS Well-Architected Frameworkによって推奨されるベストプラクティスのひとつです。しかし、Cloud FormationのテンプレートファイルはJSONもしくはYAMLで記載する必要があり、ある程度複雑な構成を記述しようとすると肥大化して可読性・保守性が低くなりがちです。
本記事では、CloudFormationのOutputsおよびImportValue関数(クロススタック参照)を利用してテンプレートを分割管理する手法を試します。また、実際の運用においてどのような単位で分割するべきか?について、AWSベストプラクティスの提案を紹介します。
なお、本記事の引用元はAWSベストプラクティスおよびAWS Black Beltです。
結論
- テンプレートファイルはOutputsを利用することで他ファイルから参照できる値を保持できる
- Outputsにて保持した値はImportValue関数をもちいて別のテンプレートから参照可能
- テンプレートの分割単位はライフサイクルと依存関係をベースとしたレイヤー単位とする
テンプレートの分割
以下構成をサンプルとしてCloud Formationテンプレートの分割を試していきます。
- VPCを作成する
- パブリックサブネットおよびプライベートを各1つずつ作成する
- パブリックサブネットにEC2インスタンスを作成する
VPCとサブネットの作成
まず、上記の1と2にあたる部分(ネットワーク部分)を作成します。テンプレートのサンプルはこのようになります。
network.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Create VPC & subnet
Resources:
FirstVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
InstanceTenancy: default
Tags:
- Key: Name
Value: CloudFormation-VPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref FirstVPC
Tags:
- Key: Name
Value: CloudFormation-VPC-PublicRT
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref FirstVPC
Tags:
- Key: Name
Value: CloudFormation-VPC-PrivateRT
PublicSubnet1A:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref FirstVPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: CloudFormation-public-subnet-1a
PubSubnet1ARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1A
RouteTableId: !Ref PublicRouteTable
PrivateSubnet1A:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref FirstVPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: Name
Value: CloudFormation-private-subnet-1a
PriSubnet1ARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1A
RouteTableId: !Ref PrivateRouteTable
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: CloudFormation-IGW
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref FirstVPC
InternetGatewayId: !Ref InternetGateway
Route:
Type: AWS::EC2::Route
DependsOn: InternetGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
Outputs:
StackVPC:
Description: VPC's ID
Value: !Ref FirstVPC
Export:
Name: !Sub "${AWS::StackName}-VPCID"
StackPublicSubnet1A:
Description: Public Subnet's ID
Value: !Ref PublicSubnet1A
Export:
Name: !Sub "${AWS::StackName}-PublicSubnet1A"
StackPrivateSubnet1A:
Description: Private Subnet's ID
Value: !Ref PrivateSubnet1A
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnet1A"
ポイントとなるOutputsを抜粋します。
Outputs:
StackVPC:
Description: VPC's ID
Value: !Ref FirstVPC
Export:
Name: !Sub "${AWS::StackName}-VPCID"
<StackName>-VPCID
というキーでVPCのIDをエクスポートしており、このキーを指定することで別のテンプレートから参照できるようになります。
EC2の作成
つぎに、作成したパブリックサブネットにEC2を作成します。以下テンプレートを利用します。
application.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
KeyName:
Description : Name of an existing EC2 KeyPair.
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription : Can contain only ASCII characters.
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
# AMIは要差し替え
ImageId: ami-03f91159819288efa
InstanceType: t2.micro
SubnetId: !ImportValue VPC-PublicSubnet1A
BlockDeviceMappings:
-
DeviceName: /dev/xvda
Ebs:
VolumeType: gp2
VolumeSize: 8
Tags:
- Key: Name
Value: myInstance
KeyName: !Ref KeyName
SecurityGroupIds:
- !GetAtt "InstanceSecurityGroup.GroupId"
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: connect with ssh
VpcId: !ImportValue VPC-VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: connect with ssh
VpcId: !ImportValue VPC-VPCID
SecurityGroupIngress:
-
IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
ポイントはImportValue関数です。<StackName>-VPCID
を指定することで、別テンプレートでOutputsした値を参照することができます。
以上がクロススタック参照を利用したテンプレート分割の基本的な流れとなります。
レイヤー単位による分割
次に、実際の運用においてどのように分割するべきか?という疑問が湧きます。AWSベストプラクティスでは以下の記載があります。
共通のライフサイクルと所有権を持つリソースのグループ化により、所有者は独自のプロセスやスケジュールを使用して、他のリソースに影響を与えることなくリソースのセットを変更できます。
これについて、AWS Black Beltにおいてより具体的な内容が提案されています(いずれも20200826 AWS Black Belt Online Seminar AWS CloudFormationより引用)。
インフラ全体のスタック分割
アプリケーションレイヤ内部のスタック分割
インフラ全体としてはネットワークレイヤ・セキュリティレイヤ・アプリケーションレイヤという区分、またアプリケーションレイヤの中でさらに共通レイヤ・データレイヤ・アプリケーションレイヤといった区分で分割していくようです。
各レイヤのライフサイクル(ネットワークレイヤはアプリケーションレイヤと比較してライフサイクルが長い)および依存関係(ネットワークレイヤは他のレイヤから依存される独立した層)を根拠として分割するので、直感的で管理しやすそうですね。
長大なテンプレートファイルとおさらばして、快適なインフラ自動化をしていきましょう!
Discussion