こんにちは。構築担当の下地です。
最近はAWSの書籍も色々出版されて、体系化された情報が手に入りやすくなったと感じています。
つい色々本を買うのですが、読むのが追いつかず積み本が増えてしまいますね汗
さて、今日はCloudFormationのテンプレート分割についての記事になります。
CloudFormationとCrossStackReferenceを利用して基本的な構成を作ってみました。
CloudFormationについて
AWSでリソースを作成する際に、一番身近な方法としてマネジメントコンソール(以下マネコン)からポチポチと作ることができます。
大規模な構成でもマネコンから作成することもできますが、沢山のリソースを一つずつ手動で作成するのは大変です。
また手動で作成すると、設定ミスが起きる可能性もあります。
そこでCloudFormationを利用します。
CloudFormationは、作成したいリソースをテンプレートという定義ファイルに記載し、実行することでAWS各種リソースを作成できます。
あらかじめ記述してあるコード通りにリソースが作成されるため、設定時にミスが起きることもありませんし、そのコードを何度でも利用できます。
テンプレートの巨大化と対策
CloudFormationはとても便利ですが、作りたい構成が大きくなってくると、テンプレートに記述する内容が増えてどんどんテンプレートが大きくなってしまいます。
コードの規模が大きくなると、単一ファイルでは手間と時間がかかり、変更時の影響範囲も大きくなるため管理が大変です。
これはCloudFormationに限らず、Ansibleなどの構成管理ツールでも同じことが言えます。
そこでCloudFormationの運用ベストプラクティスとして、テンプレートを分割して管理することができます。
その実装がCrossStackReferenceです。
テンプレート分割の方針
テンプレートを分けるといっても、いくつか分割の考え方があります。
例えば、
- リソース同士の依存関係で分割する
- リソースのライフサイクル(更新頻度と寿命)によって分割する
- リソースを管理する部署別に分割する
などです。
今回はこの中から「リソース同士の依存関係で分割」という方針でテンプレートを書いてみます。
具体的には、
- ネットワークレイヤ:VPC,サブネット,インターネットゲートウェイ,ルートテーブル,など
- セキュリティレイヤ:セキュリティグループ,IAMユーザ,IAMポリシー,など
- アプリケーションレイヤ:EC2,AMI,EBS,など
と言う感じで分けました。
構成図
今回は以下の構成を構築してみます。
テンプレート
テンプレートはそれぞれのレイヤごとに、
Network01.yml
Security01.yml
Application01.yml
の3つに分割して記述し、連携させて最終的に先ほどの構成図のように配置されるようにしました。
それでは1つずつテンプレートを見ていきます。
Network01.yml
このテンプレートでは、VPC、InternetGateway、Subnetを2つ、RouteTableを2つ作成します。
ほか2つのテンプレートの受け皿となる一番基礎の部分です。
また他テンプレートとの連携のためにVPCとSubnetの情報を出力します。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
BlogTestVpc01:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: BlogTestVpc01
BlogTestIGW01:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: BlogTestIGW01
BlogTestGatewayAttachment01:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref BlogTestIGW01
VpcId: !Ref BlogTestVpc01
BlogTestSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref BlogTestVpc01
CidrBlock: 10.0.0.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: BlogTestSubnet01
BlogTestSubnet02:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref BlogTestVpc01
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1c
Tags:
- Key: Name
Value: BlogTestSubnet02
BlogTestRouteTable01:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref BlogTestVpc01
Tags:
- Key: Name
Value: BlogTestRouteTable01
BlogTestRouteTable02:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref BlogTestVpc01
Tags:
- Key: Name
Value: BlogTestRouteTable01
BlogTestRouteTableAssociation01:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref BlogTestRouteTable01
SubnetId: !Ref BlogTestSubnet01
BlogTestRouteTableAssociation02:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref BlogTestRouteTable02
SubnetId: !Ref BlogTestSubnet02
BlogTestRouteForIGW01:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref BlogTestRouteTable01
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref BlogTestIGW01
BlogTestRouteForIGW02:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref BlogTestRouteTable02
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref BlogTestIGW01
Outputs:
BlogTestVpc01:
Value: !Ref BlogTestVpc01
Export:
Name: aws-network-basis:BlogTestVpc01
BlogTestSubnet01:
Value: !Ref BlogTestSubnet01
Export:
Name: aws-network-basis:BlogTestSubnet01
BlogTestSubnet02:
Value: !Ref BlogTestSubnet02
Export:
Name: aws-network-basis:BlogTestSubnet02
Security01.yml
次にセキュリティレイヤーのテンプレートです。
今回はIAMユーザなどは作成せず、インフラの要素のみを記述しました。
作成されるのは、ALB・Web01・Web02それぞれのSecurityGroupです。
Webサーバ用のセキュリティグループは、自分の拠点IP(伏字にしています)からのSSH接続と、ALB用のSecurityGroupからのみ接続できるようにしています。
こちらも他テンプレートとの連携のため、各SecurityGroupの情報を出力します。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
BlogTestSecurityGroupForWeb01:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: "BlogTestSecurityGroupForWeb01"
GroupDescription: "BlogTestSecurityGroupForWeb01"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref BlogTestSecurityGroupForALB01
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: xxx.xxx.xxx.xxx/32
Tags:
- Key: Name
Value: BlogTestSecurityGroupForWeb01
VpcId: !ImportValue aws-network-basis:BlogTestVpc01
BlogTestSecurityGroupForWeb02:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: "BlogTestSecurityGroupForWeb02"
GroupDescription: "BlogTestSecurityGroupForWeb02"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref BlogTestSecurityGroupForALB01
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: xxx.xxx.xxx.xxx/32
Tags:
- Key: Name
Value: BlogTestSecurityGroupForWeb02
VpcId: !ImportValue aws-network-basis:BlogTestVpc01
BlogTestSecurityGroupForALB01:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: "BlogTestSecurityGroupForALB01"
GroupDescription: "BlogTestSecurityGroupForALB01"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: BlogTestSecurityGroupForALB01
VpcId: !ImportValue aws-network-basis:BlogTestVpc01
Outputs:
BlogTestSecurityGroupForWeb01:
Value: !Ref BlogTestSecurityGroupForWeb01
Export:
Name: aws-network-basis:BlogTestSecurityGroupForWeb01
BlogTestSecurityGroupForWeb02:
Value: !Ref BlogTestSecurityGroupForWeb02
Export:
Name: aws-network-basis:BlogTestSecurityGroupForWeb02
BlogTestSecurityGroupForALB01:
Value: !Ref BlogTestSecurityGroupForALB01
Export:
Name: aws-network-basis:BlogTestSecurityGroupForALB01
Application01.yml
このレイヤでは、ウェブサーバ用のEC2インスタンスであるWeb01・Web02及び、それらにトラフィックを振り分けるApplicationLoadBalancer(以下ALB)を作成します。
ALBで利用するTargetGroupとListnerも合わせて作ります。
EC2の作成に利用するAMIとKeypairは前もって作成しておいたものを利用しました。
EC2用のAMIにはApacheをインストールしておき、ドキュメントルートに簡単なテストファイルを設置しています。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
BlogTestWeb01:
Type: AWS::EC2::Instance
Properties:
NetworkInterfaces:
- SubnetId: !ImportValue aws-network-basis:BlogTestSubnet01
GroupSet:
- !ImportValue aws-network-basis:BlogTestSecurityGroupForWeb01
DeviceIndex: 0
ImageId: ami-0ad3b9d3225df3e1a
InstanceType: t2.micro
Tags:
- Key: 'Name'
Value: 'BlogTestWeb01'
KeyName: ShimojiKey
BlogTestWeb02:
Type: AWS::EC2::Instance
Properties:
NetworkInterfaces:
- SubnetId: !ImportValue aws-network-basis:BlogTestSubnet02
GroupSet:
- !ImportValue aws-network-basis:BlogTestSecurityGroupForWeb02
DeviceIndex: 0
ImageId: ami-0ad3b9d3225df3e1a
InstanceType: t2.micro
Tags:
- Key: 'Name'
Value: 'BlogTestWeb02'
KeyName: ShimojiKey
BlogTestALB01:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: BlogTestALB01
IpAddressType: ipv4
Scheme: internet-facing
SecurityGroups:
- !ImportValue aws-network-basis:BlogTestSecurityGroupForALB01
Tags:
- Key: Name
Value: BlogTestALB01
Subnets:
- !ImportValue aws-network-basis:BlogTestSubnet01
- !ImportValue aws-network-basis:BlogTestSubnet02
BlogTestTargetGroup01:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !ImportValue aws-network-basis:BlogTestVpc01
Name: BlogTestTargetGroup01
Protocol: HTTP
Port: 80
HealthCheckProtocol: HTTP
HealthCheckPath: /
HealthCheckPort: traffic-port
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
HealthCheckTimeoutSeconds: 5
HealthCheckIntervalSeconds: 10
Matcher:
HttpCode: 200
Targets:
- Id:
Ref: BlogTestWeb01
Port: 80
- Id:
Ref: BlogTestWeb02
Port: 80
BlogTestALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn:
!Ref BlogTestTargetGroup01
LoadBalancerArn:
!Ref BlogTestALB01
Port: 80
Protocol: HTTP
テンプレートの実行
テンプレートを階層的に作成してるので、一番下の階層から1つずつCloudFormationを実行してStackを作成します。
今回は最終的に3つStackができます。
実行順番は、Network01.yml → Security01.yml → Application01.yml です。
それぞれ1つテンプレート実行してStack作るのを3回繰り返すと、全ての構成が連携してできあがります。
また、それぞれのテンプレートはCloud9を利用して記述したので、Cloud9からAWS CLIを利用してCloudFormationを実行してStackを作成します。
それではまずNetwork01.ymlの実行です。Stack名を「BlogTestStackNetwork01」としました。
実行後にCloudFormationを見てみると
ちゃんとStackができています。
(画像は旧型のマネコンですが、こちらの方が使いやすい気がする・・)
こんな感じでSecurity01.yml、Application01.ymlの順番にStackを作成します。
それぞれStack名は「BlogTestStackSecurity01」「BlogTestStackApplication01」としました。
全部作ると以下の感じでStackが3つできます。
動作確認
作成されたALBに自分のPCからアクセスしてみます。
するとAMIで設定しておいたテストページが表示されました。
ALBに関連付けられるEC2も正常稼動を確認できます。
またこれらテンプレートを流した後に気が付いたのですが、EC2にグローバルIPが付かない為に自分の拠点IPをSecurityGroupで開けてもアクセスできませんでした。
そこで、Apacheのログチェックのために一旦EIPを付けて確認したところ、自分の拠点IPからのテストページへのアクセスログとALBからのヘルスチェックログが確認できました。
また、ApacheのログではそのままだとALBのアドレスが記録されるため、自分のクライアントのIPを表示したい場合は、以下の設定が必要になります。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/elb-capture-client-ip-addresses/
まとめ
CrossStackReferenceを利用することで、テンプレートの粒度を小さくして管理することが可能になります。
SecurityGroupなど、更新頻度の高い要素を含むテンプレートは積極的に分割して管理することで、ChangeSetなどで変更するときもやりやすいかと思います。
また今回のコード作成及びCloudFormation操作はすべてCloud9環境から行いました。
Cloud9は使いやすいのでオススメです。