この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
こんにちは、中山です。
みなさんCloudFormationをYAMLで書いてますか。JSONで書いている場合はすぐに以下のエントリを見てYAMLに変換しましょう。
2017年3月11日追記
CloudFormationをJSONからYAMLに変更したい場合は、AWSがGitHubで公開しているaws-cfn-template-flipもご検討ください。短縮記法への変換もサポートされています。
私もバリバリYAMLで書いてるのですが、ちょっとだけツライ部分があります。それは利用するリソースが増えてくると行数がどんどん増えていく問題です。単純に行数が増えるだけでもテンプレートを読み解く時に萎えますが、YAMLの場合インデントが文法上重要な意味を持ちます。行数が多いとディスプレイに入り切らずに何個インデントを付ければいいのか結構迷うことが多いです。
CloudFormationは残念ながら現時点でループ機能をサポートしていません。そのため、例えばEC2インスタンスを2つ作ろうとすると愚直に2つ AWS::EC2::Instance
リソースを定義する必要があります。また、最近のアップデートでクロススタック参照が入ったので、リソース毎にファイルを分割することも可能ですが、いちいち参照させる/する変数をエクスポート/インポートするの結構シンドいと感じています。Terraformつか(ry
こういった問題点があるのでどうしたものかと考えていたところ、解決策とまではいかないですが、有効な対策を見つけたのでブログのネタにしてみます。それは「組み込み関数の短縮記法」です。YAMLサポートに合わせて組み込み関数(例えば Fn::FindInMap
など)を !FuncName
という短縮記法で記述できるようになりました。
これだけ見ると Fn::
を !
と書けるだけ?と勘違いするかと思いますが、違います。そんなふうに考えていた時期が俺にもありました案件です。この記法は要するに関数を「埋め込む」記法だと考えるとよいと思います。関数の戻り値を変数のように埋め込めるので、全て1行で記述可能です。
また、関数の引数に Ref
や Fn::GetAtt
を利用して属性を参照することはよくあると思います。通常の記述方法ではコロンを利用する必要があるので、1度改行させる必要があります。そこも短縮記法を利用すれば1行で書けるので行数の短縮が捗るという訳です。
あらいいですね、という訳で実際に試してみましょう。
使ってみる
今回はCloudFormationで以下の構成を短縮記法と通常の記述方法で書いた場合に、どの程度行数が短くなるのかを検証してみてその威力をお伝えしてみようと思います。
まずは通常の記述方法で書いた場合のCloudFormationテンプレートです。179行あります。
---
AWSTemplateFormatVersion: '2010-09-09'
Description: YAML Long form
Parameters:
NameTagPrefix:
Type: String
Default: test
Description: Prefix of Name tags.
KeyPair:
Description: KeyPair Name
Type: AWS::EC2::KeyPair::KeyName
Mappings:
StackConfig:
VPC:
CIDR: 10.0.0.0/16
PublicSubnet:
CIDR: 10.0.0.0/24
EC2:
InstanceType: t2.nano
ImageId: ami-1a15c77b
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock:
Fn::FindInMap:
- StackConfig
- VPC
- CIDR
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- vpc
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId:
Ref: VPC
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- public-route-table
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId:
Ref: PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
VpcId:
Ref: VPC
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
CidrBlock:
Fn::FindInMap:
- StackConfig
- PublicSubnet
- CIDR
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- public-subnet
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet
RouteTableId:
Ref: PublicRouteTable
EC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable ssh access to the instances
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- sg
EC2:
Type: AWS::EC2::Instance
Properties:
InstanceType:
Fn::FindInMap:
- StackConfig
- EC2
- InstanceType
KeyName:
Ref: KeyPair
ImageId:
Fn::FindInMap:
- StackConfig
- EC2
- ImageId
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
GroupSet:
- Ref: EC2SG
SubnetId:
Ref: PublicSubnet
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install nginx -y
service nginx start
Tags:
- Key: Name
Value:
Fn::Join:
- "-"
-
- Ref: NameTagPrefix
- ec2
Outputs:
PublicSubnet:
Value:
Fn::GetAtt:
- EC2
- PublicIp
続いて短縮記法で記述したテンプレートです。117行で書けました。変更点をハイライトしておきます。
---
AWSTemplateFormatVersion: '2010-09-09'
Description: YAML Short form
Parameters:
NameTagPrefix:
Type: String
Default: test
Description: Prefix of Name tags.
KeyPair:
Description: KeyPair Name
Type: AWS::EC2::KeyPair::KeyName
Mappings:
StackConfig:
VPC:
CIDR: 10.0.0.0/16
PublicSubnet:
CIDR: 10.0.0.0/24
EC2:
InstanceType: t2.nano
ImageId: ami-1a15c77b
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !FindInMap [ StackConfig, VPC, CIDR ]
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, vpc ] ]
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, igw ] ]
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
DependsOn: AttachGateway
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, public-route-table ] ]
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet:
Type: AWS::EC2::Subnet
DependsOn: AttachGateway
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select
- 0
- Fn::GetAZs: !Ref "AWS::Region"
CidrBlock: !FindInMap [ StackConfig, PublicSubnet, CIDR ]
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, public-subnet ] ]
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
EC2SG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Enable ssh access to the instances
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, sg ] ]
EC2:
Type: AWS::EC2::Instance
Properties:
InstanceType: !FindInMap [ StackConfig, EC2, InstanceType ]
KeyName: !Ref KeyPair
ImageId: !FindInMap [ StackConfig, EC2, ImageId ]
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
GroupSet:
- !Ref EC2SG
SubnetId: !Ref PublicSubnet
UserData: !Base64 |
#!/bin/bash
yum update -y
yum install nginx -y
service nginx start
Tags:
- Key: Name
Value: !Join [ "-", [ !Ref NameTagPrefix, ec2 ] ]
Outputs:
PublicSubnet:
Value: !GetAtt [ EC2, PublicIp ]
比較すると62行も短縮できました(厳密に言うと単に別のYAML記法を使っている箇所もありますが)。やはりタグの記述が効いてますね。まぁ違いを強調するために「分かりやすい」形にしてはいますが。
ハマリポイント
便利な記述なので Fn::
と書いていた部分を全てこの短縮記法に変えたいところですが、1つ注意点があります。それは短縮記法で書いた関数の直後の引数に、同じように短縮記法で書いた関数を渡すとエラーとなるという点です。文章で説明すると難しいので、具体例で解説してみます。
まずNGな例を出します。 AWS::EC2::Subnet
リソースでAZを指定している箇所です。
AvailabilityZone: !Select [ 0, [ !GetAZs !Ref "AWS::Region" ] ]
Fn::GetAZs
を短縮記法で記述し、その直後の引数で Ref
を短縮記法で記述して呼び出しているためエラーとなります。エラー内容は以下のようなものです。
An error occurred (ValidationError) when calling the CreateStack operation: Template format error: YAML not well-formed. (line 65, column 48)
では、どう記述するかというとこちらのドキュメントにあるように、どちらかの関数を通常の記法で記述する必要があります。具体的には以下2つの方法で記述すればOKです。
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: "AWS::Region"
AvailabilityZone: !Select
- 0
- Fn::GetAZs: !Ref "AWS::Region"
最初の方のコードでは Ref
を1つ分改行させる必要があります。通常の記述方法でコロンを含んでいるためです。無駄に1行増えるので2つ目の書き方の方がよいと思います。
まとめ
いかがだったでしょうか。
目立たないアップデートかもしれませんが、YAMLでテンプレートを書いていくとこの記法は本当に便利です。ハマりポイントもありますが積極的に使っていくことをオススメします。
本エントリがみなさんの参考になれば幸いです。