CloudFormationヘルパースクリプトcfn-hupによるメタデータの変更検知

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CloudFormationのスタック更新

CloudFormationは、JSONテンプレートによるスタックを作成することにより、AWS環境の構築を行うことができます。また、既存のスタックに対して更新や削除することができます。今回は、スタック内で動いているEC2とやり取りをするヘルバースクリプトをご紹介したいと思います。

  • cfn-init: リソースメタデータの取得と解釈、パッケージのインストール、ファイルの作成、およびサービスの開始で使用されます。
  • cfn-signal: AWS CloudFormation CreationPolicy または WaitCondition にシグナルを送信するためのシンプルなラッパー。スタック内の他のリソースと、準備ができたアプリケーションを同期させることができます。
  • cfn-get-metadata: リソースに対して定義されたすべてのメタデータ、またはリソースメタデータの特定のキーやサブツリーへのパスを簡単に取得できるようにするラッパースクリプト。
  • cfn-hup: メタデータへの更新を確認し、変更が検出されたときにカスタムフックを実行するデーモン。

Webサーバを構築するスタックの実行

cfn-initなどを用いたテンプレートを用いてWebサーバを構築するスタックを作成します。必要な部分だけに削ったつもりですが長いですねーw。

{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "Web",
  "Resources" : {
    "WebServerInstance" : {
 	    "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "KeyName" : "mykey",
        "ImageId" : "ami-27f90e27",
				"SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
			  "InstanceType" : "t1.micro",
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
             "#!/bin/bash -xe\n",
             "yum update -y aws-cfn-bootstrap\n",

             "# Install the files and packages from the metadata\n",
             "/opt/aws/bin/cfn-init -v ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource WebServerInstance ",
             "         --configsets InstallAndRun ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n",

             "# Signal the status from cfn-init\n",
             "/opt/aws/bin/cfn-signal -e $? ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource WebServerInstance ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}        
      },
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "configSets" : {
            "InstallAndRun" : [ "Install" ]
          },
          "Install" : {
            "packages" : {
              "yum" : {
                "httpd"        : [],
                "php"          : []
              }
            },

            "files" : {
              "/var/www/html/index.php" : {
                "content" : { "Fn::Join" : [ "", [
                  "<html>\n",
                  "  <body>\n",
                  "    <h1>Welcome to the AWS CloudFormation PHP Sample</h1>\n",
                  "  </body>\n",
                  "</html>\n"
                ]]},
                "mode"  : "000600",
                "owner" : "apache",
                "group" : "apache"
              },

              "/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.WebServerInstance.Metadata.AWS::CloudFormation::Init\n",
                  "action=/opt/aws/bin/cfn-init -v ",
                  "         --stack ", { "Ref" : "AWS::StackName" },
                  "         --resource WebServerInstance ",
                  "         --configsets InstallAndRun ",
                  "         --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"]}
              }
            }
          }
        }
      },
      "CreationPolicy" : {
        "ResourceSignal" : {
          "Timeout" : "PT5M"
        }
      }
    },
    "WebServerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access via port 80",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : "0.0.0.0/0"}
        ]
      }      
    }
  },
  "Outputs" : {
    "WebsiteURL" : {
      "Description" : "URL for newly created We stack",
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]] }
    }
  }
}

スタック作成が無事に終わりました。

screenshot 2015-07-08 11.40.25

これらを分解して理解を深めたいと思います。

利用するAWSリソースは2つ

JSONのブロックを閉じると、EC2インスタンスとセキュリティグループを作成していることがわかります。セキュリティグループは、80番と22番ポートを空けています。

  "Resources" : {
    "WebServerInstance" : {...},
    "WebServerSecurityGroup" : {...}
  }

アウトプット

スタックの作成が完了すると、アウトプットとして、WebサーバのURLが出力されるようにしています。

  "Outputs" : {
    "WebsiteURL" : {
      "Description" : "URL for newly created We stack",
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]] }
    }
  }

Webサーバの設定

AMIの指定、セキュリティグループの指定、インスタンスタイプの指定など基本的な項目の設定をしています。また、UserDataの指定、メタデータの指定などを行なっています。

    "WebServerInstance" : {
 	    "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "KeyName" : "mykey",
        "ImageId" : "ami-27f90e27",
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "InstanceType" : "t1.micro",
        "UserData"       : {...}
      },
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "configSets" : {
            "InstallAndRun" : [ "Install" ]
          },
          "Install" : {...}
        }
      },
      "CreationPolicy" : {
        "ResourceSignal" : {
          "Timeout" : "PT5M"
        }
      }
    },

CreationPolicyの指定

CreationPolicyのブロックでは、シグナルのタイムアウトとして5分を設定しています。これは、インスタンス作成の一連のプロセスが5分で終わらないければ失敗とみなしてロールバックが走ります。

UserDataの指定

UserDataは、EC2インスタンス起動時にOS内部に与えることができるパラメータです。ここでは、yumのアップデート、cfn-initの呼び出し、cfn-signalの呼び出しを行なっています。cfn-initでは初期設定などを指示し、configsetsの引数にInstallAndRunという名前を指定しています。これはこの後に続くメタデータで詳しく書かれています。cfn-signalでは、CreationPolicy または WaitConditionにシグナルを送信します。

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

             "# Install the files and packages from the metadata\n",
             "/opt/aws/bin/cfn-init -v ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource WebServerInstance ",
             "         --configsets InstallAndRun ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n",

             "# Signal the status from cfn-init\n",
             "/opt/aws/bin/cfn-signal -e $? ",
             "         --stack ", { "Ref" : "AWS::StackName" },
             "         --resource WebServerInstance ",
             "         --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}

メタデータの指定

パッケージのインストールを行なっています。また、ファイルの作成、サービスの起動などを行なっています。

      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "configSets" : {
            "InstallAndRun" : [ "Install" ]
          },
          "Install" : {
            "packages" : {
              "yum" : {
                "httpd"        : [],
                "php"          : []
              }
            },

            "files" : {
              "/var/www/html/index.php" : {
                "content" : { "Fn::Join" : [ "", [
                  "<html>\n",
                  "  <body>\n",
                  "    <h1>Welcome to the AWS CloudFormation PHP Sample</h1>\n",
                  "  </body>\n",
                  "</html>\n"
                ]]},
                "mode"  : "000600",
                "owner" : "apache",
                "group" : "apache"
              },

            ...

            "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"]}
              }
            }
          }
        }
      },

cfn-hup

メタデータの中では、cfn-hup.confやhooks.d配下のファイルなどを作成しています。これは、メタデータへの更新を確認し、変更が検出されたときにカスタムフックを実行するデーモンです。ここでは、トリガーとしてスタックの更新イベントを検知しています。検知すると、hooks.d配下の設定ファイルにあるアクションが実行されます。

              "/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.WebServerInstance.Metadata.AWS::CloudFormation::Init\n",
                  "action=/opt/aws/bin/cfn-init -v ",
                  "         --stack ", { "Ref" : "AWS::StackName" },
                  "         --resource WebServerInstance ",
                  "         --configsets InstallAndRun ",
                  "         --region ", { "Ref" : "AWS::Region" }, "\n",
                  "runas=root\n"
                ]]}
              }
            },

まとめ

テンプレートの全容を掴んでいただけましたでしょうか?CloudFormationヘルパースクリプトは、スタックの作成・更新・削除などのイベントに合わせてEC2インスタンス内部で処理を実行することができることがわかりました。また、メタデータを指定することで、イベント毎に発生させる処理の中身も指定出来る事がわかりました。スタックをアップデートするたびにyumをアップデートするとかキャッシュをクリアするとか、応用ができそうですね!

参考資料

CloudFormation ヘルパースクリプトリファレンス