サンプルを動かして覚えるCloudFormation

  • 7
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

背景

私が初めてCloudFormationに取り組んだとき、いろいろ調べてもなかなかイメージできなかったり、存在するサンプルが大きなテンプレートで理解や実行が大変だったりとなかなか苦労した覚えがあります。
サンプルのテンプレートを少しずつ試して動きを理解できるようにするため、その手順を記録します。

説明すること

  • aws-cliを使用してCloudFormationのstackを作る流れ
    • 使用するコマンド
      • create-stack
      • create-change-set
    • 以下のリソースをCloudFormationにより作成して、webページを参照できる状態にする
      • SecurityGroup
      • LaunchConfiguration
      • ElasticLoadBalancer
      • AutoScalingGroup

説明しないこと

  • CloudFormationのテンプレートの内容
  • CloudFormationのテンプレートの書き方

今回はテンプレートによりスタックを作成したり更新したりすることにフォーカスするため、内容には触れません。
本文中にでてくる設定を詳しく知りたい場合、適宜AWS リソースプロパティタイプのリファレンス - AWS CloudFormationを参照してください。
また、サンプルテンプレート - AWS CloudFormation などにサンプルがたくさんあります。

準備するもの

  • AWSアカウント
    • CloudFormationとEC2の権限があること
  • AWS コマンドラインインターフェイス | AWS
    • awsコマンドを使用できるようにしてください
    • macならbrew update && brew install awscliでも可
  • もしかしたら若干課金が発生するかもしれないという心構え
    • 無料枠に収まるかは使い方次第

また、今回の作業で使用するアクセスキーとリージョンを設定します。
既存の設定と混ざると混乱するので、別途下記のようにプロファイルをわけると良いと思います。
この文章ではsampleプロファイルを使用する前提で進めます。
aws configure --profile sample

CloudFormationによるResourcesの構築

今回はAWSが公開しているテンプレートサンプルのうち、更新ポリシーを使用する Auto Scaling グループを使います。AWS CloudFormation Designerを見ることで下記のような構成図を確認することもできます。

スクリーンショット 2016-04-17 19.35.18.png

今回はこれらのリソースを1つずつ作成していきます。
矢印の示すように依存があるので、それに応じて以下の順で作成します。

  1. SecurityGroup
  2. Launch Configuration
  3. Load Balancer
  4. Auto Scaling Group(以下ASG)

上手く行けば、ASGによって作成された二台のインスタンスがELB経由でアクセスでき、簡単なページが表示できるようになるはずです。

なお、今回の作業履歴はkeqh/cloudformation-tutorialに残しています。

Security Groupの作成

今回の変更点

最初に起動設定に必要となるリソースを作成します。これによりport80を受け入れるsecurity groupを作ります。

テンプレートへの追加

まずは下記のようなtemplateを用意します。

{
  "AWSTemplateFormatVersion": "2010-09-09",

  "Description": "CloudFormation tutorial",

  "Resources": {
    "InstanceSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Enable HTTP access on the configured port",
        "SecurityGroupIngress": [
          { "IpProtocol": "tcp", "FromPort": "80", "ToPort": "80", "CidrIp": "0.0.0.0/0" }
        ]
      }
    }
  }
}

create-stackの実行

このtempalteをもとにawscliを使用してstackを作成します。作成には以下のコマンドを実行します。

$ aws cloudformation create-stack\
  --profile sample --stack-name sample\
  --template-body file://`pwd`/sample.template

指定しているオプションは以下のとおりです。

  • profile
    • aws credentialの設定したprofile
  • stack-name
    • 今回作成するstackの名前。templateごとに一意になるように設定する
  • template-body
    • 今回作成するtemplateを指定する

コマンドを実行し、CloudFormationのコンソールを表示すると以下の様にsecurity groupが作成されたことが確認できます。

01.png

ASGの起動設定の作成

今回の変更点

security groupを作成したので、それを使用する起動設定を作成します。

テンプレートへの追加

設定が長いですが、大部分はもとのサンプルのままなので、とりあえず試したい方は適当に読み流してください。サンプルから変更した部分は以下の二点です。

  • ImageId
    • mappingを減らすため、ImageIdは直接指定
  • InstanceType
    • Parameterの説明を省くため、t2.nanoで固定
    "LaunchConfig": {
      "Type": "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "ImageId": "ami-c229c0a2",
        "InstanceType": "t2.nano",

        "SecurityGroups": [ { "Ref": "InstanceSecurityGroup" } ],
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
             "#!/bin/bash -xe\n",
             "yum update -y aws-cfn-bootstrap\n",

             "/opt/aws/bin/cfn-init -v ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource LaunchConfig ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n",

             "/opt/aws/bin/cfn-signal -e $? ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource WebServerGroup ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}
      },
      "Metadata" : {
        "Comment" : "Install a simple application",
        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "httpd" : []
              }
            },

            "files" : {
              "/var/www/html/index.html" : {
                "content" : { "Fn::Join" : ["\n", [
                  "<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
                ]]},
                "mode"    : "000644",
                "owner"   : "root",
                "group"   : "root"
              },

              "/etc/cfn/cfn-hup.conf" : {
                "content" : { "Fn::Join" : ["", [
                  "[main]\n",
                  "stack=", { "Ref" : "AWS::StackId" }, "\n",
                  "region=", { "Ref" : "AWS::Region" }, "\n"
                ]]},
                "mode"    : "000400",
                "owner"   : "root",
                "group"   : "root"
              },

              "/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
                "content": { "Fn::Join" : ["", [
                  "[cfn-auto-reloader-hook]\n",
                  "triggers=post.update\n",
                  "path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\n",
                  "action=/opt/aws/bin/cfn-init -v ",
                  "         --stack ", { "Ref" : "AWS::StackName" },
                  "         --resource LaunchConfig ",
                  "         --region ", { "Ref" : "AWS::Region" }, "\n",
                  "runas=root\n"
                ]]}
              }
            },

            "services" : {
              "sysvinit" : {
                "httpd"    : { "enabled" : "true", "ensureRunning" : "true" },
                "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
                              "files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
              }
            }
          }
        }
      }
    },

create-change-setによる更新の確認

今回はすでに存在するstackを更新するので、create-change-setを使用することでどのような変更が発生するのか確認してから更新します。
create-stackから以下のオプションが増えているので、これを入力します。

  • change-set-name
    • change-setで一意になるように名前をつける
    • 使用できる文字は[-a-zA-Z0-9]+
$ aws cloudformation create-change-set\
  --profile sample --stack-name sample\
  --template-body file://$PWD/sample.template\
  --change-set-name add-launch-config

実行したあと、AWSのweb consoleでCloudFormation > Stack: sample > Change set detail: add-launch-configにアクセスすると以下の様な画面が表示されます。
ここでresourceの更新が確認でき、今回はLaunchConfigが追加されることが確認できます。

02.png

この時点ではまだstackは更新されていないため、画面右上にあるExecuteボタンを押して更新を実行します。

おまけ: create-change-setして作成したchange-setをブラウザ開くスクリプト

毎回コマンドを実行してブラウザを開くのが大変だったので、create_change_set.shというスクリプトをやっつけで作りました。これはcreate-change-setを実行し、それによって生成されたChangeSetIdをもとにweb consoleを開くものです。
./create_change_set.sh <change-set-name>と実行することで、create-change-setを実行した上で作成されたchange-setのページを開きます。

もしこちらを使用擦る場合、以下に注意してください

  • スクリプト内のopenはmacでしか使用できないため、windowsやlinuxの方はテキトウにurlをブラウザで開くコマンドに置き換えてください
  • リージョンが決め打ちなので、Tokyoを使っている方はap-northeast-1に変更してください

ELBの作成

今回の変更点

EC2instanceの起動設定は作成したので、次はASGにattachするELBを作成します。

テンプレートへの追加

以下をResourcesに追加します。

    "ElasticLoadBalancer": {
      "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties": {
        "AvailabilityZones": { "Fn::GetAZs": "" },
        "CrossZone" : "true",
        "Listeners": [ {
          "LoadBalancerPort": "80",
          "InstancePort": "80",
          "Protocol": "HTTP"
        } ],
        "HealthCheck": {
          "Target": "HTTP:80/",
          "HealthyThreshold": "3",
          "UnhealthyThreshold": "5",
          "Interval": "30",
          "Timeout": "5"
        }
      }
    }

実行と確認

追加したらcreate-change-setを実行し、前回と同じようにchange-setの作成・確認・実行します。

$ aws cloudformation create-change-set\
  --profile sample --stack-name sample\
  --template-body file://$PWD/sample.template\
  --change-set-name add-load-balancer

# もしくは
$ ./create_change_set.sh add-load-balancer

ASGの作成

今回の変更点

ELBを追加しましたが、まだEC2instanceが存在していないので動いていません。
最後にASGを作成し、前回用意した起動設定をもとにinstanceを作成してもらい、ASGにattachされたELB経由でHTTPアクセスできるようにします。

テンプレートへの追加

    "WebServerGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "CreationPolicy" : {
        "ResourceSignal" : {
          "Timeout" : "PT15M",
          "Count"   : "2"
        }
      },
      "UpdatePolicy" : {
        "AutoScalingRollingUpdate" : {
          "MaxBatchSize" : "1",
          "MinInstancesInService" : "1",
          "PauseTime" : "PT15M",
          "WaitOnResourceSignals": "true"
        }
      },
      "Properties": {
        "AvailabilityZones": { "Fn::GetAZs": "" },
        "LaunchConfigurationName": { "Ref": "LaunchConfig" },
        "MinSize": "2",
        "MaxSize": "4",
        "LoadBalancerNames": [ { "Ref": "ElasticLoadBalancer" } ]
      }
    }

今回はELBのURLを確認するため、Outputsに以下を加えます。
stackの更新が成功した後、ページの表示を確認するURLを生成してもらいます。

  "Outputs": {
    "URL": {
      "Description": "URL of the website",
      "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "ElasticLoadBalancer", "DNSName" ] } ] ] }
    }
  }

実行と確認

追加したらcreate-change-setを実行し、前回と同じようにchange-setの作成・確認・実行します。

$ aws cloudformation create-change-set\
  --profile sample --stack-name sample\
  --template-body file://$PWD/sample.template\
  --change-set-name add-auto-scaling-group

# もしくは
$ ./create_change_set.sh add-auto-scaling-group

今回はEC2のinstanceの構築が発生するため、stackの更新の完了に少し時間がかかります。また、無料枠の残り次第では課金が発生するので注意しましょう。
無事完了したあと、consoleのOutputsを確認するとURLが表示されています。これはさきほどテンプレートで指定したOutputsによるものです。

03.png

URLにアクセスすると以下の様なメッセージが表示されたページが開けるはずです。

04.png

無事に表示されれば今回のテンプレートは完成です。お疲れ様でした。

appendix: Modify Instance Type

今回の変更点

ここまででhttpdを起動し、ELB経由でページを表示することができましたが、せっかくなのでEC2のinstance typeをt2.nanoからt2.microに変更してみます。

  "Resources": {
    "LaunchConfig": {
      "Properties": {
-        "InstanceType": "t2.nano",
+        "InstanceType": "t2.micro",
         ...
      }
    }
  }       

変更したらcreate-change-setを実行します。

$ aws cloudformation create-change-set\
  --profile sample --stack-name sample\
  --template-body file://$PWD/sample.template\
  --change-set-name modify-web-instance-type

# もしくは
$ ./create_change_set.sh modify-web-instance-type

すると、以下のように変更点が確認できます。

Change_Set_Detail_modify.png

これを実行したあと、ASGの設定によってinstanceが置き換わっていきます。EC2のconsoleでinstances群を眺めていると動きがわかって面白いかもしれません。

06.png

後始末

このままstackを放置しているとどんどん課金されてしまうため、満足したら削除しましょう。
stackを削除することで、これにより作成されたresourcesも削除されます。

スクリーンショット 2016-04-17 20.16.59.png

最後に

今回はCloudFormationでstackを作るところにフォーカスして手順を記録しました。CloudFormationは一度サンプルなりを実行してスタックやリソースの作成を自分自身で確認すると理解が捗ると思います。
ここからはドキュメントや他のサンプルを参照し、いろいろ試すことでより理解が進むと思います。

次のステップ

おまけ: テンプレートのdiffを確認する

change-setはresoucesの追加・更新・削除がわかるものの、更新のとき何が変わるのかまではわかりません。
実際にstackを更新するとき、何が更新されるかできれば詳しく知りたいところです。
gitなどを使っている場合は差分を確認すればいいかと思われますが、実際に適用されたテンプレートをAWSから持ってきて比較するほうが確実かと思います。なので、

  1. aws cloudformation get-templateで適用済みのtemplateを取得し
  2. 手元のテンプレートとのdiffを表示する

スクリプトを突貫工事で作りました。実行にはnodejsondiffpatchが必要です。

$ brew install node
$ npm install jsondiffpatch
$ node diff.js
diff.js
var fs = require('fs');
var jsondiffpatch = require('jsondiffpatch');
var child_process = require('child_process');

var patcher = jsondiffpatch.create({
  objectHash: function(obj, index) {
    if (typeof obj._id !== 'undefined') {
      return obj._id;
    }
    if (typeof obj.id !== 'undefined') {
      return obj.id;
    }
    if (typeof obj.name !== 'undefined') {
      return obj.name;
    }
    return '$$index:' + index;
  }
});

var f1 = `/tmp/current.json`;
var f2 = "./sample.template";

function diff() {
  var left = JSON.parse(fs.readFileSync(f1));
  var right = JSON.parse(fs.readFileSync(f2));
  var delta = patcher.diff(left.TemplateBody, right);
  jsondiffpatch.console.log(delta);
}

child_process.exec(`aws cloudformation get-template --profile sample --stack-name sample > ${f1}`, function (err, stdout, stderr) {
  diff();
});

links