AWS上にインフラを構築するときにCloudFormationというサービスを利用すると
再利用しやすくて便利です。
僕はいつもCloudFormationを使って構築しているので、自分がよく使う
ものはgithubで管理したりしてます。
1. そもそもどういう用途なのか
例えばAWS上にWebサービス向けインフラを構築したいとしたとき
- EC2でWebサーバを立てる
- ポートは80(443)を開ける
- ストレージはSSDにして容量を例えば30GBにする
- RDSでデータベースサーバを立てる
- Webサーバ経由のみアクセスを許可する
- MySQL 5.6を使う
- S3にファイルサーバを立てる
- デフォルトの公開権限を設定する
- ElasticsearchServiceで検索用サーバを立てる
- Elasticsearchのバージョンは5.1
- Webサーバ経由のみアクセスを立てる
こんな感じの手順が必要になると思います。
ロードバランサを入れたい、キャッシュサーバを立てたいとか
やりたくなってくると更に手順は増えてきます。
これをサービスを開発するごとに、サーバを増やすごとに
同じ手順を踏まないといけないのは、面倒だし、手順が共有されていないと
上手く全体が動かない、なんてことが起こります。
これを設定ファイルによって、定義し実行できるのがCloudformationに
なります。
CloudFormation自体を利用するのに料金はかかりません。
(もちろん、CloudFormationによって構築されたEC2のインスタンス等には
通常通りの料金が加算されます。)
2. 簡単な設定ファイルを書いてみる
設定ファイルはjson
もしくはyaml
で記述することができます。
僕はいつもyamlで書いてますので、ここでもyaml
で例を挙げていきます。
1 2 3 4 5 6 |
Resources: HelloBucket: Type: AWS::S3::Bucket Properties: AccessControl: PublicRead |
CloudFormation公式のチュートリアルと同じものです。
S3のバケットを作成するための最小の設定ファイルです。
Resources
の下には複数のハッシュを含めることができます。
以下の例では2つのバケットを生成します。
1 2 3 4 5 6 7 8 9 10 |
Resources: HelloBucket1: Type: AWS::S3::Bucket Properties: AccessControl: PublicRead HelloBucket2: Type: AWS::S3::Bucket Properties: AccessControl: PublicRead |
※もちろん、S3バケットとEC2インスタンスを、みたいにも書けます。
それぞれのリソースタイプの書き方は、下記の参考リンクを参照してください。
最初はAWSマネジメントコンソールからポチポチやったほうが
早いですが、慣れてくる+設定ファイルを使いまわせるようになってくると
早くなります。
ものすごくざっくり書くと
Type
に作りたいリソースタイプ(S3のバケットとか、EC2のインスタンスとか)Properties
に、設定項目を書く(S3のアクセス権限とか、EC2のインスタンスタイプとか)
という感じです。
Type
には何が指定可能なのか、というと
以下が一覧になります
上記では省略していますが、下記のようにDescription
で、
設定ファイルが何をしているか記述しておくことができます。
1 2 3 4 5 6 7 8 9 |
Description: Storage Resources: HelloBucket: Type: AWS::S3::Bucket Properties: AccessControl: PublicRead |
Description
以外にどのようなセクションが利用できるのか
は以下に一覧があります。
よく使うのはParameters
とOutputs
で、
こちらに関しては後ほど。
3. 外部から設定を動的に指定できるように
上記だけでも、まぁ便利は便利ですが、1つのスタックを外部から変更
できないので、使いまわすのには不便です。
例えば最初に例に出した
- EC2でWebサーバを立てる
- ポートは80(443)を開ける
- ストレージはSSDにして容量を例えば30GBにする
- RDSでデータベースサーバを立てる
- Webサーバ経由のみアクセスを許可する
- MySQL 5.6を使う
- S3にファイルサーバを立てる
- デフォルトの公開権限を設定する
- ElasticsearchServiceで検索用サーバを立てる
- Elasticsearchのバージョンは5.1
- Webサーバ経由のみアクセスを立てる
これをサービスAとして、新しくサービスBを立ち上げることになり、
インフラ構成はほとんどサービスAと同様、でもEC2のストレージのサイズを
100GBにしないといけない
ということがあったときに、全部コピーしてストレージサイズだけ
書き換えるのはしんどいです。
そんなときに使えるのが前述したParameters
です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Resources: MyEC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: "ami-79fd7eee" KeyName: "testkey" InstanceType: "m1.small" BlockDeviceMappings: - DeviceName: "/dev/sdm" Ebs: VolumeType: "io1" Iops: "200" DeleteOnTermination: "false" VolumeSize: "30" - DeviceName: "/dev/sdk" NoDevice: {} |
これは20GBのEBSをもったm1.small
インスタンスタイプのEC2インスタンスを
定義した設定です。100GBにしたいときに、もともとあったこのファイル
をコピーしてVolumeSize
だけを書き換えるのはしんどいです。
そこでVolumeSize
をParameters
によって変更できるようにします。
それが以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Parameters: EBSVolumeSize: Type: Number Default: 30 MinValue: 8 Resources: MyEC2Instance: Type: "AWS::EC2::Instance" Properties: ImageId: "ami-79fd7eee" KeyName: "testkey" InstanceType: "m1.small" BlockDeviceMappings: - DeviceName: "/dev/sdm" Ebs: VolumeType: "io1" Iops: "200" DeleteOnTermination: "false" VolumeSize: !Ref EBSVolumeSize - DeviceName: "/dev/sdk" NoDevice: {} |
こうすることで、この設定ファイルを利用すると、実行時に
CloudFormationの画面から当該の値を指定することができます。
これをたくさんパラメータにしていけば、設定ファイルを一回書けば
使いまわすことができるようになりますね。
4. 複数設定ファイルを組み合わせて利用したい
スタックを使いまわすことができるようになったのはいいですが、
ここでもう一つ問題が起こります。
また最初の例を出してみます。
- EC2でWebサーバを立てる
- ポートは80(443)を開ける
- ストレージはSSDにして容量を例えば30GBにする
- RDSでデータベースサーバを立てる
- Webサーバ経由のみアクセスを許可する
- MySQL 5.6を使う
- S3にファイルサーバを立てる
- デフォルトの公開権限を設定する
- ElasticsearchServiceで検索用サーバを立てる
- Elasticsearchのバージョンは5.1
- Webサーバ経由のみアクセスを立てる
これをサービスAとして、今度はサービスCを作りたいとしてます。
サービスCの要件が
- RDSは使いません
- その代わりにDynamoDB使いたいです
だとします。こうなると、さっきパラメータで指定できるように
書き換えた設定ファイルも使えません。
(RDSをなくせないし、DynamoDBを追加もできない。)
これに対応するために、サービスAのスタックを複数ファイルに分割して
必要に応じて呼び出すように変更します。
イメージとしては今まで1つのファイルに全ての設定を書いていたものを
- EC2インスタンスの設定ファイル
- RDSの設定ファイル
- S3の設定ファイル
- Elasticsearchの設定ファイル
- 1-4を呼び出して組み合わせる設定ファイル
に変えていきます。
本来であれば、全て具体的に書きたいんですが、すっごく長くなると思うので
上記項目の中から3と5だけ取り上げます。
以下がS3の設定ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# storage.yaml Parameters: BucketName: Type: String MinLength: 5 Resources: HelloBucket: Type: AWS::S3::Bucket Properties: AccessControl: "PublicRead" BucketName: !Ref BucketName |
そしてこれが呼び出す側の設定ファイル
1 2 3 4 5 6 7 8 9 |
Resources: S3BucketStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: "https://s3-ap-northeast-1.amazonaws.com/hogehoge/storage.yaml" TimeoutInMinutes: 60 Parameters: BucketName: "samplebucket" |
CloudFormationのリソースタイプは結構な種類があるので覚える必要は
ありませんが、AWS::CloudFormation::Stack
だけは覚えてほしい!
これは外部に置かれているCloudFormation用の設定ファイルを参照して
パラメータを渡し、自分のスタックとします。
これによって複数のミニマルな設定ファイルを組み合わせて
アプリケーション全体のスタックを定義することができます。
上記のサービスCの例なら、新しくDynamoDBのスタックの設定ファイルを
記述して、RDSと置き換えれば可能です。
最後に、よくあるのが
- あるVPCネットワークを作成する
- サブネットAを作成する
- サブネットBを作成する
こういうケースでは、サブネットAとサブネットBはVPCネットワーク
の情報が必要になります。
サブネットが2つ程度であればいいですが、複数(または1つ)作りたいときが
あると思います。
そういうときには、Outputs
セクションを用います。
(この例では最小限のものだけしか出してないので、ルートテーブルとかは別途記述する必要があります。)
まずはVPCを作成する設定ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Parameters: CidrBlock: Type: String Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref CidrBlock EnableDnsSupport: true EnableDnsHostnames: tru Outputs: VPC: Value: !Ref VPC |
次にサブネットを作成する設定ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Parameters: VpcId: Type: AWS::EC2::VPC::Id AvailabilityZone: Type: AWS::EC2::AvailabilityZone::Name VPCSubnetCidrBlock: Type: String Default: "10.0.0.0/24" Resources: VPCSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VpcId AvailabilityZone: !Ref AvailabilityZone CidrBlock: !Ref VPCSubnetCidrBlock |
実際にVPCとサブネットを作成する設定ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Resources: MyVPC: Type: AWS::CloudFormation::Stack Properties: TemplateURL: "https://s3-ap-northeast-1.amazonaws.com/hogehoge/vpc.yaml" TimeoutInMinutes: 60 Parameters: CidrBlock: "10.0.0.0/16" MySubnet1: Type: AWS::CloudFormation::Stack Properties: TemplateURL: "https://s3-ap-northeast-1.amazonaws.com/hogehoge/subnet.yaml" TimeoutInMinutes: 60 Parameters: VpcId: !GetAtt MyVPC.VPC AvailabilityZone: "ap-northeast-1a" VPCSubnetCidrBlock: "10.0.0.0/24" MySubnet2: Type: AWS::CloudFormation::Stack Properties: TemplateURL: "https://s3-ap-northeast-1.amazonaws.com/hogehoge/subnet.yaml" TimeoutInMinutes: 60 Parameters: VpcId: !GetAtt MyVPC.VPC AvailabilityZone: "ap-northeast-1a" VPCSubnetCidrBlock: "10.0.1.0/24" |
VPC作成設定ファイルでVPCのIDをOutputsに出すことによって、利用側がその値を参照できるようになってます。
Ref
とGetAtt
は
この辺を参照してみてください。
5. はまったので注意してほしいところ
ParameterをList<?>型にしているスタックに対してyamlのリストを
渡せません(2017-07-03現在)
この場合はカンマ区切りの文字列にする必要があります。
組み込み関数のJoin
を使うことで対応することができます。
1 2 3 4 5 6 |
VPCSubnets: Fn::Join: - ',' - - !GetAtt Subnet1Stack.Outputs.Subnet - !GetAtt Subnet2Stack.Outputs.Subnet |
こんな感じ