[初学者向け]CloudFormationに挑戦しようと思い立った人へ向けCloudFomationドキュメントとの向き合い方
Cloudformationテンプレートってどうやって作るの?
という方へ向けての記事です。
「あくまでも私はこんな風にやっています。」になりますので正解ではありませんが、それでもよろしければ是非お付き合いいただければと思います。
※普段固めの表現を好んでしてしまいがちの為、今回はわかりやすさ重視で崩した口調でざざっと書き上げております。
最初にテンプレってこんな感じという例を載せてみます
CloudFormationで2AZにパブリック・プロテクテッド・プライベートのサブネットとルート・GWを作成。
一応テンプレで作成されるものの構成図も添えてみます。
この上によく配置するリソースの一例として、
図の上段のパブリックサブネットにNatGatewayやELB、場合によっては踏み台用のEC2を配置、
中段のプロテクテッドサブネットにEC2やECSなどを、
下段のプライベートサブネットにDBインスタンスを配置するなどよく見る構成なのではと思います。
後で載せるテンプレートの中には、構成図には描画されているもの(NatGatewayなど)と、そうでないもの(EIPなど)がありますので、構成図とテンプレを見比べてみてください。
大体の方は何から何まで書く事はせず省略する傾向にありますが、中には稀ですが意図して全て丁寧に書く場合もあるかと思います。
テンプレート
CloudFormationはJSONとYAMLで記載可能ですが、コメントが書けたり、閉じ括弧の存在に悩まされないで済むなどYAMLの方が圧倒的にメリットが多い為、YAMLで記載します。
(100人いたら99人はYAMLだと思っていいくらいです。)
ちなみにどちらでテンプレートを作成したとしても、ネット上で「JSON YAML変換」と検索してヒットしたツールを使ったり、AWSサービス上でもCloudFormationデザイナーと呼ばれる機能が存在するのでそこでどちらにも変換する事が可能です。
最初に上の構成図に対応したテンプレ例を畳んでおきます。
今回はこれを全部作る訳ではありませんので
ざっと眺めて「ふーん」と思ったらそっ閉じしていただいて構いません。
完成.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Create Vpc and public subnets, protected subnets, and private subnets in 2 availability zones.
# ============== メタデータ ==============
Metadata: # 一段下のパラメータの入力を求めるコンソール画面の並び順などを統制。
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: Stack(CloudFormation)
Parameters:
- PJPrefix
-
Label:
default: VPC
Parameters:
- VPCCidrBlock
-
Label:
default: PublicSubnet
Parameters:
- PublicSubnet1CidrBlock
- PublicSubnet2CidrBlock
-
Label:
default: ProtectedSubnet
Parameters:
- ProtectedSubnet1CidrBlock
- ProtectedSubnet2CidrBlock
-
Label:
default: PrivateSubnet
Parameters:
- PrivateSubnet1CidrBlock
- PrivateSubnet2CidrBlock
# ============== パラメータ ==============
Parameters: # スタック作成者に入力を求める項目を作成。
PJPrefix: # リソース名などの接頭辞となる文字列の入力を求める。
Type: String
Default: testprefix
MinLength: 1
MaxLength: 10
AllowedPattern: "[a-z][a-z0-9]*"
ConstraintDescription: Invalid input value for the PJPrefix.
VPCCidrBlock: # VPCのCIDR範囲。
Type: String
Default: 10.1.0.0/16
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid VPCCidrBlock.
PublicSubnet1CidrBlock: # パブリックサブネット1のCIDR範囲。
Type: String
Default: 10.1.0.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid PublicSubnet1CidrBlock.
PublicSubnet2CidrBlock: # パブリックサブネット2のCIDR範囲。
Type: String
Default: 10.1.1.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid PublicSubnet2CidrBlock.
ProtectedSubnet1CidrBlock: # プロテクテッドサブネット1のCIDR範囲。
Type: String
Default: 10.1.2.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid ProtectedSubnet1CidrBlock.
ProtectedSubnet2CidrBlock: # プロテクテッドサブネット2のCIDR範囲。
Type: String
Default: 10.1.3.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid ProtectedSubnet2CidrBlock.
PrivateSubnet1CidrBlock: # プライベートサブネット1のCIDR範囲。
Type: String
Default: 10.1.4.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid PrivateSubnet1CidrBlock.
PrivateSubnet2CidrBlock: # プライベートサブネット2のCIDR範囲。
Type: String
Default: 10.1.5.0/24
MinLength: 9
MaxLength: 18
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: must be a valid PrivateSubnet2CidrBlock.
# ============== リソース ==============
Resources: # 作成するリソース
# -------------- VPC --------------
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-VPC
#-------------- インターネットゲートウェイ --------------
# IGW
IGW:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-IGW
# アタッチメント
IGWAttach: # どこのVPCにアタッチするか。
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref IGW
VpcId: !Ref VPC
# -------------- EIP --------------
NGW1EIP: # NatGateway用のEIP1。
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-NGW1EIP
NGW2EIP: # NatGateway用のEIP2。
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-NGW2EIP
# -------------- サブネット --------------
# パブリック
PubSN1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PublicSubnet1CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ] # そのリージョン内のサブネットの0番目を指定。
MapPublicIpOnLaunch: true # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか。
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-PubSN1
PubSN2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PublicSubnet2CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs ]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-PubSN2
# プロテクテッド
ProSN1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref ProtectedSubnet1CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ProSN1
ProSN2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref ProtectedSubnet2CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ProSN2
# プライベート
PriSN1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet1CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-PriSN1
PriSN2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref PrivateSubnet2CidrBlock
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs ]
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-PriSN2
# -------------- ルートテーブル --------------
# ルートテーブル
PubRT:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-PubRT
ProRT1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ProRT1
ProRT2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-ProRT2
# ルート
PubRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PubRT
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref IGW
ProRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref ProRT1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NGW1
ProRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref ProRT2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NGW2
# アソシエーション
PubSN1Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PubSN1
RouteTableId: !Ref PubRT
PubSN2Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PubSN2
RouteTableId: !Ref PubRT
ProSN1Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ProSN1
RouteTableId: !Ref ProRT1
ProSN2Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref ProSN2
RouteTableId: !Ref ProRT2
# -------------- NATゲートウェイ --------------
NGW1: # NatGatewayは2AZのパブリックサブネットそれぞれに必要。
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NGW1EIP.AllocationId
SubnetId: !Ref PubSN1
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-NGW1
NGW2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NGW2EIP.AllocationId
SubnetId: !Ref PubSN2
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-NGW2
実際どんな風に作っているか(手順と説明)
①ファイル自体を作る
最初は自分の好きなエディタを開きます。
中身は空のファイルに名前をつけて保存してください。
「.yaml」か「.yml」であれば拡張子以前は英語でも日本語でも構いません。
「test.yaml」
「test.yml」
「テスト.yaml」
「テスト.yml」
などなんでもOKです。私は短く済むので.ymlにしています。
②最初の宣言文
次に実際に中身を書いていきますが、最初どこから手をつけていいかわからないと思います。
最初はこれです。
AWSTemplateFormatVersion: 2010-09-09
「さぁ、CloudFormationテンプレート作っちゃうよ」という宣言をするノリで毎回これを一番上に書きます。
「ヴァージョン」とありますがこの日付は私の知る限りかなり昔から変更されていません。
2010-09-09じゃないテンプレートを見た事がありません。
そして大体過去に自分が作ったテンプレか、公式ドキュメントやネット上のどこかからコピペです。
丁寧に手打ちしたりはしません。
③テンプレ自体の説明分
次は何をしたらいいでしょうか?
AWSTemplateFormatVersion: 2010-09-09
Description: Create Vpc and public subnets, protected subnets, and private subnets in 2 availability zones.
先ほどの宣言の下に「Description:(説明)」を生やしてみました。
なくてもOKです。このテンプレのファイルをローカルで見返した時や、実際にこのテンプレを使ってリソースをデプロイした後、UPしたテンプレの中身になんて書いてあったかをマネジメントコンソールから確認出来るのですが、その時に「あぁ、こういうプロジェクトのこういうリソースを纏めてつくったのね。わかり」となる為の項目です。
繰り返しになりますが省いてもなんら問題ありません。
④リソース自体
次は実際のリソース(=モノ)を作っていきましょう。
先ほどのテンプレ例の各ブロック一番上位(インデント無しで一番左にピッタリくっ付いている部分)だけを取り出すと以下のようになります。
AWSTemplateFormatVersion: 2010-09-09
Description: #ほにゃらら
Metadata:
#ほにゃらら
Parameters:
#ほにゃらら
Resources:
#ほにゃらら
というブロックで書いてありました。
実際にはこの他にConditionsやOutputsなどのセクションも存在するのですが、覚える事が沢山あるとよくないので今回は触れません。
※補足ですが上記の「#ほにゃらら」のように「#」から書き始めたテキストはコメントとして扱えます。
一番大事なのは一番下の「Resources: 」です。
リソースと一言に言っても様々なものが存在します。
EC2やRDSのような実体として意識されていそうなものから、VPCやサブネットのような、初学者にとって概念に近く感じられるもの。ルートテーブルやセキュリティグループなどルールに近いもの。「これとこれを紐づけておくれ」という「〇〇Association」のようなものまで幅広いです。
(難しく考える必要はないですが)
メチャクチャ大雑把にいうとAWSのサービスや設定全部をここで作りあげます。
他の「Metadata:」 と「Parameters:」は後で触れるので一旦頭から消してしまいましょう。
一旦これを見てもらいます。
AWSTemplateFormatVersion: 2010-09-09
Description: hogehoge
Resources: # 作成するリソース
# -------------- VPC --------------
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-VPC
最初に注目するのが、
VPC:
と書いてある部分ですが、これは「論理名」といいます。
日本語文字とかでなく、他のリソースと被っていなければなんでもいいです。(数字スタートが可能かや文字数に制限があるかなどはドキュメントを参照ください。)
例えば
AWSTemplateFormatVersion: 2010-09-09
Description: hogehoge
Resources: # 作成するリソース
# -------------- VPC --------------
hogehugapiyo123456DAYO:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-VPC
でも構いません。
大事なのが次の行です。
Type: AWS::EC2::VPC
ここが「何作るの?」を決定する部分です。
以下でその一覧が確認出来ます。
これからこのテンプレートリファレンスとは長いお付き合いをする事になります。
このドキュメントがないとCloudformationテンプレートを作るのは不可能と言っても過言ではないです。
例えばRDSを作りたい場合、Ctrl+Fで「Relational」とブラウザ上で検索してみてください。
↓
こんな風にHITしますのでクリックしてみましょう。
↓
ずらっとRDSに関係するリソースタイプが表示されます。
↓
この中からクラスターを作成したいなら「AWS::RDS::DBCluster」を、単独のDBインスタンスを作成したいよ、という場合は「AWS::RDS::DBInstance」を先ほどの「Type:」の後に続けて貼り付ければ良いという訳です。
そしてドキュメントは英語で表示されますが、ブラウザに日本語のアドオンなどを入れて日本語に翻訳して読みましょう。(英語の方が読みやすい方はそのままでOK)
コピーしてテンプレに持っていきたい部分はうまい事英語のまま翻訳されないようドキュメントはなっていますので心配不要です。
ドキュメントには以下のように「フィルタービュー」という箇所が存在します。
JSONかYAMLかその両方かを選択出来ますが、毎回「YAML」を選択しましょう。
こうする事でJSONで書かれたテンプレート例をHiddenにしてくれて、スッキリします。
少し下にスクロールすると
「構文」という項目の下に「YAML」という箇所が登場します。
簡単に言うと、設定可能な細かい詳細項目が全部ここにあります。
この各項目を「プロパティ」というので
これを設定する際、
Properties:
として以下にそれぞれのプロパティと、それをどうしたいか書いていく訳ですね。
その隣には赤字で「文字列だよ」とか「True/False」だよという「型」
が書いてあります。
AWSに詳しくなってどの項目が何を表すかに理解が及ぶようになれば
「そうだねここはNameだから文字列だろうね」
とか
「これがいるかいらないかの二択だからBoolean」だね
とか自然と見なくてもアタリがつくようになりますが、ここに明示されているという事を一旦覚えておきましょう。
これもいちいち手書きで書く事はまずありません。このドキュメント内や、先人のありがたいブログからコピペです。
そうしてできたのが以下です。
hogehugapiyo123456DAYO:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCidrBlock
InstanceTenancy: default
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${PJPrefix}-VPC
なんだかプロパティがRDSに比べて少ないと思ったかもしれませんが、実際にはVPCにだって沢山の設定可能なプロパティがあります。
ただ、「設定しなきゃダメよ!VPC作るのにこれがないなんてあり得ない!」というプロパティと、
「付け足したかったらあなたのお好きに」というプロパティ、その他「AをTrueにしているならBも設定しなきゃダメよ」というプロパティがあります。
さっきのRDSのドキュメントを開きっぱなしかと思いますので、少し下にスクロールしてみると、
こんな風にそれぞれのプロパティの説明と、必須かどうか、タイプ(型)がちゃんと記載されています。
場合によっては許容値として「hoge | huga | piyo」だけ設定出来るよなどと書いてある場合もあります。
中には
このように「タイプ: DBInstanceRoleのリスト」等として青色のリンクをクリック出来るようになっている場合がありますが、この場合は階層化されているわけです。
クリックするとまた新たなページが登場しました。
要するに、以下のように新階層が登場する度に掘り下げて(一段右にインデントを増やして)設定していくわけです。
中には今回のように「リスト」形式になっている為、先頭に「- 」をつけて複数指定出来る書式などありますが、その辺はエラーを何度か経験している内に慣れます。2、3度リソース作成を失敗する経験だけで感覚掴めますのでご心配不要です。
JSON形式が指定されている場合は、先ほど書いたようにJSONで書いたり、拾ってきたものをYAMLに変換するツールを使って貼付するだけです。
hogeRdsInstance:
Type: AWS::RDS::DBInstance
Properties:
AssociatedRoles:
- FeatureName: String
RoleArn: String
先ほどのVPCリソースの中にこんな風に書いてある部分があったかと思います。
CidrBlock: !Ref VPCCidrBlock
やっていくと「何これ実際の値じゃないじゃん」という事に気が付くと思います。
詳しくは割愛しますが、「直接書くのめんどいからVPCCidrBlockっていう論理名(或いは後述するパラメーター)のとこからそれっぽい値参照(Reference)して埋めといてよ!宜しく」という書き方になります。
こんな魔法がいくつかあります。組み込み関数といいます。
他にも「自分のAWSのアカウントID覚えてなーい(見にいくのだるい)」という時に使える「AWS::AccountId」などの擬似パラメーターというのもあります。
この辺は慣れてきたら多用してください。
後はサブネットをつくろうと思ったら「一体どこのVPCに作りたいねん」などと逐一聞かれ一生懸命埋めている内にAWSのリソース同士の親子関係というか依存関係性というか色々な事がわかっていきます。
⑤ParametersとMetadata
先ほど後回しにしていた部分です。
ざっくり言うと、Parametersは「テンプレにベタ書きしたくない値」や、一つのテンプレを使い回す想定且つ、作成都度別の値を埋めたい等の目的がある時に
「テンプレを実行する人に手入力してよって事にしといちゃおう」という為のもの。
Metadataはそれを「マネコンに表示させる時バラバラだと汚いから順番を決めておこう」というだけの項目です。
最初のテンプレを実行すると、パラメーター入力画面はこのように表示されます。
以上でした。
雑な説明にはなりますが、案外こんな感じの方が読んでて気疲れしないのかもと思いざっと書き殴りました。Cloudformationに興味を持たれた方の一助になれば幸いです。
Discussion
参考になりました〜
今度、知り合いと一緒に勉強する時、一緒に見ながら取り組んでみます。
とんでもないです〜✨何か不明点あれば一緒に考えますので是非お声がけください^^
ありがとうございます!!