この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
よく訓練されたアップル信者、都元です。CloudFormationカコイイ…。ということでもう一本いきます。
CloudFormationとは
要するに「S3バケット作って、VPC作って、サブネットはこんな感じでー、インスタンスここにつくってー、で、出来上がったらWebサーバのURL教えてくれる?」っていうような、AWSを利用したシステム全体のレシピを処理してくれるサービスです。無料です。(但し、生成したサーバ等の費用はお客様がご負担ください)
このようなレシピのことを、CloudFormation用語で「テンプレート」と言います。そして、CloudFormationではEC2インスタンスやSecurityGroup、VPC、EIPをはじめ、LoadBalancer、AutoScalingGroup、IAMユーザに至るまで、様々なものを作成できます。これらの要素を「リソース」と呼びます。テンプレートを使って作られる一連のリソースのまとまりのことを「スタック」と呼びます。
つまり、テンプレートというのは基本的にリソースの定義(組み立て方)を列挙したもの、ということになります。
テンプレートの基本「リソース」
ここで小さいテンプレートの例を見てみましょう。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"SSHGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Enable SSH access via port 22",
"SecurityGroupIngress": [{
"IpProtocol" : "tcp",
"CidrIp" : "0.0.0.0/0",
"FromPort" : "22", "ToPort" : "22"
}]
}
},
"WebGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Enable HTTP access via port 80",
"SecurityGroupIngress": [{
"IpProtocol" : "tcp",
"CidrIp" : "0.0.0.0/0",
"FromPort" : "80", "ToPort" : "80"
}]
}
},
"WebServer" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"ImageId" : "ami-54cf5c3d",
"InstanceType" : "t1.micro",
"SecurityGroupIds" : [
{ "Ref" : "SSHGroup" },
{ "Ref" : "WebGroup" }
]
}
},
"WebServerEip": {
"Type": "AWS::EC2::EIP",
"Properties": {
"InstanceId": { "Ref": "WebServer" }
}
}
}
}
テンプレートに最低限必要な要素は、AWSTemplateFormatVersion(今の所固定)とResourcesというリソース定義のリストだけです。ここでは以下の4つのリソースを定義しています。ちなみに、作成したSecurityGroupとEIPはWebServerに関連づくように指定してあります。
- SSHGroup … 22/tcpを全IPに対して解放するSecurityGroup
- WebGroup … 80/tcpを全IPに対して解放するSecurityGroup
- WebServer … Webサーバとして使うEC2-Classicインスタンス
- WebServerEip … WebServer用ElasticIP
SSHGroupやWebServerEip等は、自分で命名するリソースの名前です。テンプレートの中で一意である必要があります。悲しきかな、リソースの名前は[a-zA-Z0-9]+しか使えないようです。最初、ハイフンとかアンスコを駆使した名前でテンプレートを作ってしまい、地獄を見ました。使えればいいのになぁ。あと、リソース名は大文字から始めるのが慣例のようです。
リソースの定義順は任意で構いません。CloudFormationが依存関係を計算し、適切な順番でリソースを作成してくれます。逆に言えば、リソースの作成順序を制御することはできません。
ランタイムの設定ができる「パラメータ」
さて、上で示したテンプレートでスタックを作ったとしても、WebServerにSSHアクセスはできません。鍵の指定をしていませんから…。しかし、テンプレートは汎用的であることに価値があるので、テンプレートの中に鍵名をベタ書きしてしまうのも残念な感じです。という時に使うのがパラメータです。JSONを以下のように変えてみましょう。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
"KeyName" : {
"Description" : "Name of an existing EC2 KeyPair to enable SSH access to the web server",
"Type" : "String"
}
},
"Resources": {
(略)
"WebServer" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"ImageId" : "ami-54cf5c3d",
"InstanceType" : "t1.micro",
"KeyName" : { "Ref" : "KeyName" },
"SecurityGroupIds" : [
{ "Ref" : "SSHGroup" },
{ "Ref" : "WebGroup" }
]
}
},
(略)
}
}
Resourcesと並んでParametersというものを定義しています。ここに定義したパラメータは、スタック作成時に動的に指定し、各リソースのパラメータに利用することができます。Refという関数を用いて、パラメータの値を参照できるようになっています。
もう一つのランタイム設定「マッピング」
よし、鍵指定に関して汎用的に作れたぞ、というところで思い出してください。AMIというのはリージョン毎にIDが異なります。WebServerに指定したami-54cf5c3dはus-east-1のAMIです。つまりこのテンプレートは現状us-east-1でしか上手くスタックを作り上げられません。ここを何とかしたい。ただ、ユーザにAMIのIDを入力させるとか、ないわー。
という時に使えるのがマッピングです。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
(略)
},
"Mappings" : {
"RegionMap" : {
"us-east-1" : {"AMI" : "ami-54cf5c3d"},
"us-west-1" : {"AMI" : "ami-b63210f3"},
"us-west-2" : {"AMI" : "ami-8e27adbe"},
"eu-west-1" : {"AMI" : "ami-3c5f5748"},
"ap-southeast-1" : {"AMI" : "ami-ba7538e8"},
"ap-southeast-2" : {"AMI" : "ami-b6df4e8c"},
"ap-northeast-1" : {"AMI" : "ami-5d7dfa5c"},
"sa-east-1" : {"AMI" : "ami-89c81394"}
}
},
"Resources": {
(略)
"ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
(略)
}
}
挙動は想像つきますね。AWS::Regionは疑似パラメータと呼び、CloudFormationが自動的に設定するパラメータです。ここに、CloudFormationが実行されたリージョン名が入っているので、そのリージョンに対応するAMIのIDを引き当てる、というのがFn::FindInMap関数の役割です。
スタックを作成した結果分かったことを教えて欲しい「アウトプット」
スタックを生成してみないと分からない「結果」というものがあります。例えば、Webサーバに割り当てられたIPアドレス値や、場合によってはWebサーバの立ち上がったAZや、SecurityGroupのID等も知りたいことがあります。
まぁ、CloudFormationで生成されるリソース群は、(タグに色々メタデータが付いているものの)普通のリソースなので、個別のページを見て回ったり、APIを叩けば情報は手に入るのですが、欲しい結果をまとめて表示してくれると便利ですね。
そこで、テンプレートに以下のような記述を追加します。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Parameters" : {
(略)
},
"Mappings" : {
(略)
},
"Resources" : {
(略)
},
"Outputs" : {
"WebServerIpAddress" : {
"Value" : { "Ref" : "WebServerEip" },
"Description" : "IP Address of WebServer"
},
"WebServerAvailabilityZone" : {
"Value" : { "Fn::GetAtt" : [ "WebServer", "AvailabilityZone" ]},
"Description" : "AvailabilityZone of WebServer"
},
"SSHToWebServer" : {
"Value" : { "Fn::Join" :["", [
"ssh -i /path/to/", { "Ref" : "KeyName" }, ".pem",
" ec2-user@", { "Ref" : "WebServerEip" }
]] },
"Description" : "SSH command to connect WebServer"
}
}
}
WebServerIpAddressには、WebServerEipのIPアドレスを出力します。WebServerAvailabilityZoneには、WebServerのAvailabilityZone属性(Fn::GetAtt関数によって取得)を出力します。SSHToWebServerには、SSH接続するためのコマンド例を出力します。コマンド例はFn::Join関数によって複数の文字列を連結しながら組み立てています。
その結果、スタックが完成した後、CloudFormationのOutputsというタブで、この情報が確認できるようになります。
そういや、このサーバってWebサーバですよね
httpdインストールしましょうか?
"WebServer" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"ImageId" : { "Fn::FindInMap" : [ "RegionMap", { "Ref" : "AWS::Region" }, "AMI" ]},
"InstanceType" : "t1.micro",
"KeyName" : { "Ref" : "KeyName" },
"SecurityGroupIds" : [
{ "Ref" : "SSHGroup" },
{ "Ref" : "WebGroup" }
],
"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
"#!/bin/bash\n",
"yum -y update\n",
"yum -y install httpd\n",
"chkconfig httpd on\n",
"service httpd start\n"
]]}}
}
},
このように、User Dataも当然のように指定できます。User Dataの指定方法"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [〜]]}}というのはイディオムなので覚えておきましょう。
cfn-initという仕組みを使えば、もう少しスマートに色々できますが、それはまた別のお話。
参考
以下のページに、アマゾン謹製のCloudFormationテンプレートが転がっています。自分でテンプレートを書く際には、色々参考になると思います。