めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

OpenStack HeatがCloud-Init / cfntoolsと連携する様子を覗きこむ(その1)

Heatとは?

HeatはAWSのCloudFormation的な仕組みをOpenStack上で実現するコンポーネントです。単一インスタンスの自動セットアップであれば、カスタマイズスクリプト(UserData)に任意のスクリプトを突っ込んで、そのスクリプトからPuppetなりChefなりを呼び出してやればOKです。次は、私がよく使うパターンで、GitHubからPuppetマニフェストをダウンロードして適用します。最小構成のOSだけが入ったインスタンス(JEOS)に下記のカスタマイズスクリプトを渡せば、勝手にPostgreSQLのサーバーが出来上がります。

#!/bin/sh -x

GitRepository=https://github.com/enakai00/pgsql_puppet.git
ConfigTag=f19

yum -y install puppet git
RepoName=${GitRepository##*/}
RepoName=${RepoName%.git}
mkdir -p /tmp/gittmp
cd /tmp/gittmp
git clone $GitRepository
cd $RepoName
git checkout $ConfigTag
export FACTER_manifest_dir="/tmp/gittmp/$RepoName"
puppet apply main.pp

しかしながら、DBサーバーを起動した後に、Webサーバー(Webアプリケーションサーバー)を立ちあげて、DBサーバーのIPとDB接続情報(DB接続ユーザー/パスワードなど)を渡して、DBサーバーへの接続設定を行うなど、複数のインスタンスを連携した自動セットアップは、これだけではできません。

ひとつのやり方として、PythonのClient Libraryを使用して、Pythonでコードを書くという方法があります。流れとしてはこんな感じのコードになるはずです。

(1) 上記のカスタマイズスクリプトを渡してDBサーバーを起動する。
(2) DBサーバー内のゲストOSで、DBのセットアップが終わるのを待つ。
(3) DBサーバーに割り当てられたIPを取得する。
(4) Webサーバー構築用のカスタマイズスクリプトに、(3)で取得したIPを埋め込む。
(5) (4)で用意したカスタマイズスクリプトを渡してWebサーバーを起動する。

しかし!

このやり方の場合、いくつか面倒な点がでてきます。たとえば、(2)の部分で、どうやったら、ゲストOS内部の様子が外部のスクリプトから判別できるのでしょうか? ゲストOS内部からスクリプトにセットアップ完了を通知する仕組みが別途必要になります。

そこで、上記のような処理をもうちょっとパターン化して実行できる、フレームワーク的なものをつくっちゃえ、というのが、Heat(というか、AWSのCloudFormation)の発想です。

実例

それでは、まずは、(1)〜(3)までの処理をHeatで実施する例を紹介します。おもむろに、下記のテンプレートファイルを用意します。これは、典型的なサンプルとして公開されているもので、筆者が作ったものではありません。

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

  "Description" : "A Database instance running a local MySQL server",

  "Parameters" : {

    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
      "Type" : "String"
    },

    "InstanceType" : {
      "Description" : "Database server EC2 instance type",
      "Type" : "String",
      "AllowedValues" : [ "m1.tiny", "m1.small", "m1.medium", "m1.large", "m1.xlarge" ],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    },

    "DBName": {
      "Description" : "The database name",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "64",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },

    "DBUsername": {
      "NoEcho": "true",
      "Description" : "The database admin account username",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "16",
      "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*",
      "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters."
    },

    "DBPassword": {
      "NoEcho": "true",
      "Description" : "The database admin account password",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },

    "DBRootPassword": {
      "NoEcho": "true",
      "Description" : "Root password for MySQL",
      "Type": "String",
      "MinLength": "1",
      "MaxLength": "41",
      "AllowedPattern" : "[a-zA-Z0-9]*",
      "ConstraintDescription" : "must contain only alphanumeric characters."
    },
    "LinuxDistribution": {
      "Description" : "Distribution of choice",
      "Type": "String",
      "AllowedValues" : [ "F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3" ],
      "Default": "F17"
    }
  },

  "Mappings" : {
    "AWSInstanceType2Arch" : {
      "m1.tiny"    : { "Arch" : "32" },
      "m1.small"    : { "Arch" : "64" },
      "m1.medium"    : { "Arch" : "64" },
      "m1.large"   : { "Arch" : "64" },
      "m1.xlarge"   : { "Arch" : "64" }
    },
    "DistroArch2AMI": {
      "F18"      : { "32" : "F18-i386-cfntools", "64" : "F18-x86_64-cfntools" },
      "F17"      : { "32" : "F17-i386-cfntools", "64" : "F17-x86_64-cfntools" },
      "U10"      : { "32" : "U10-i386-cfntools", "64" : "U10-x86_64-cfntools" },
      "RHEL-6.1" : { "32" : "rhel61-i386-cfntools", "64" : "rhel61-x86_64-cfntools" },
      "RHEL-6.2" : { "32" : "rhel62-i386-cfntools", "64" : "rhel62-x86_64-cfntools" },
      "RHEL-6.3" : { "32" : "rhel63-i386-cfntools", "64" : "rhel63-x86_64-cfntools" }
    }
  },

  "Resources" : {
    "MySqlDatabaseServer": {
      "Type": "AWS::EC2::Instance",
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "mysql"        : [],
                "mysql-server" : []
              }
            },
            "services" : {
              "systemd" : {
                "mysqld"   : { "enabled" : "true", "ensureRunning" : "true" }
              }
            }
          }
        }
      },
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "DistroArch2AMI", { "Ref" : "LinuxDistribution" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -v\n",

          "# Helper function\n",
          "function error_exit\n",
          "{\n",
          "  /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "MySqlWaitHandle" }, "'\n",
          "  exit 1\n",
          "}\n",

          "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackName" },
          " -r MySqlDatabaseServer ",
          " --region ", { "Ref" : "AWS::Region" },
          " || error_exit 'Failed to run cfn-init'\n",

          "# Setup MySQL root password and create a user\n",
          "mysqladmin -u root password '", { "Ref" : "DBRootPassword" }, "'\n",
          "cat << EOF | mysql -u root --password='", { "Ref" : "DBRootPassword" }, "'\n",
          "CREATE DATABASE ", { "Ref" : "DBName" }, ";\n",
          "GRANT ALL PRIVILEGES ON ", { "Ref" : "DBName" }, ".* TO \"", { "Ref" : "DBUsername" }, "\"@\"%\"\n",
          "IDENTIFIED BY \"", { "Ref" : "DBPassword" }, "\";\n",
          "FLUSH PRIVILEGES;\n",
          "EXIT\n",
          "EOF\n",
          "# All is well so signal success\n",
          "/opt/aws/bin/cfn-signal -e 0 -r \"MySQL Database setup complete\" '",
          { "Ref" : "MySqlWaitHandle" }, "'\n"
        ]]}}
      }
    },

    "MySqlWaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },

    "MySqlWaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "DependsOn" : "MySqlDatabaseServer",
      "Properties" : {
        "Handle" : {"Ref" : "MySqlWaitHandle"},
        "Timeout" : "6000"
      }
    }

  },

  "Outputs" : {
    "PublicIp": {
      "Value": { "Fn::GetAtt" : [ "MySqlDatabaseServer", "PublicIp" ] },
      "Description": "Database server IP"
    }
  }
}

ええ。長いです。こんな物を用意するぐらいなら、とっととPythonでスクリプト書いた方がよっぽど早い気もします。

・・・・・。

というと話が終わってしまうので、もうちょっと、テンプレートの本質だけを抜き出してコメントを付けたものを記載します。「JSONにコメントはない!」とか固いことは言わないで下さい。

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

  "Description" : "A Database instance running a local MySQL server",

  "Parameters" : {
    // ここは、起動時に指定可能なパラメータを宣言しているだけです。
  },

  "Mappings" : {
    // ここは、後から参照するためのハッシュテーブルを用意しているだけです。マクロ定義みたいなもんと思って下さい。
  },

  "Resources" : {
    "MySqlDatabaseServer": {   // ここが重要。DBサーバーのインスタンスを作る指示が書かれています。
      "Type": "AWS::EC2::Instance",
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
               // ここには、ゲストOSに仕込んである自動化ツール「cfn-init」に渡すパラメータを定義します。
        }
      },
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "DistroArch2AMI", { "Ref" : "LinuxDistribution" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
                // ここに、いわゆるカスタマイズスクリプト(UserData)を書き込みます。
        ]]}}
      }
    },

  // この下は、(2)を実現するための仕掛けです。
    "MySqlWaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },

    // この宣言があると、ゲストOSから上記のハンドラ「MySqlWaitHandle」に通知があるまでじっと待ちます。
    // ゲストOSに対しては、カスタマイズスクリプトの中で、セットアップが終わったら、cfn-signalコマンドで通知を送るように仕込んでおきます。
   // この「MySqlWaitCondition」に依存関係を設定したリソースは、ゲストOSの構築が完了した後に、その構成が開始します。
    "MySqlWaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "DependsOn" : "MySqlDatabaseServer",
      "Properties" : {
        "Handle" : {"Ref" : "MySqlWaitHandle"},
        "Timeout" : "6000"
      }
    }

  },

  // これは、(3)を実現する仕掛けです。ここで起動したインスタンスのIPアドレスを取得して、他のテンプレートからも参照できるようになります。
  "Outputs" : {
    "PublicIp": {
      "Value": { "Fn::GetAtt" : [ "MySqlDatabaseServer", "PublicIp" ] },
      "Description": "Database server IP"
    }
  }
}

次に、このあたりを参考にして、Heatが使える環境を用意します。いや、RDO便利です。簡単に用意できてしまいます。

そして、次のコマンドでテンプレートから環境構築を行います。Heat/CloudFormationでは、テンプレートから作った環境一式を「スタック」と呼びます。いつものkeystonerc_*で、管理者権限を持った状態で実行してください。

# heat stack-create mysql1 --template-file MySQL_Single_Instance.template --parameters="DBUsername=wp;DBPassword=wp;KeyName=userkey;LinuxDistribution=F17;DBRootPassword=passw0rd;DBName=mydb;InstanceType=m1.small"

Heat Engineのログを見ると、MySqlWaitConditionで、ゲストOSから完了通知がくるのを待っているのが分かります。

/var/log/heat/engine.log

2013-12-26 10:17:48.707 6890 INFO heat.engine.resource [-] creating Instance "MySqlDatabaseServer"
2013-12-26 10:19:16.348 6890 INFO heat.engine.resource [-] creating WaitCondition "MySqlWaitCondition"
2013-12-26 10:19:16.524 6890 DEBUG heat.engine.resources.wait_condition [-] Polling for WaitCondition completion, sleeping for 10 seconds, timeout 6000 handle_create /usr/lib/python2.7/site-packages/heat/engine/resources/wait_condition.py:258
2013-12-26 10:19:26.530 6890 DEBUG heat.engine.resources.wait_condition [-] Polling for WaitCondition completion, sleeping for 10 seconds, timeout 6000 handle_create /usr/lib/python2.7/site-packages/heat/engine/resources/wait_condition.py:258
2013-12-26 10:19:36.535 6890 DEBUG heat.engine.resources.wait_condition [-] Polling for WaitCondition completion, sleeping for 10 seconds, timeout 6000 handle_create /usr/lib/python2.7/site-packages/heat/engine/resources/wait_condition.py:258
2013-12-26 10:19:46.540 6890 DEBUG heat.engine.resources.wait_condition [-] Polling for WaitCondition completion, sleeping for 10 seconds, timeout 6000 handle_create /usr/lib/python2.7/site-packages/heat/engine/resources/wait_condition.py:258
...
2013-12-26 10:36:22.732 6890 DEBUG heat.openstack.common.rpc.amqp [-] UNIQUE_ID is 0d4f345bcfca4410b5caf42c8298c9d3. _add_unique_id /usr/lib/python2.7/site-packages/heat/openstack/common/rpc/amqp.py:337
2013-12-26 10:36:22.741 6890 DEBUG heat.openstack.common.rpc.amqp [-] UNIQUE_ID is 33e807eeafa64a7baae289b1ab9d1e4c. _add_unique_id /usr/lib/python2.7/site-packages/heat/openstack/common/rpc/amqp.py:337
2013-12-26 10:36:27.076 6890 DEBUG heat.engine.resources.wait_condition [-] WaitCondition MySqlWaitCondition SUCCESS handle_create /usr/lib/python2.7/site-packages/heat/engine/resources/wait_condition.py:266

上記のようにログに「SUCCESS」が見えたら、スタックの構築完了を確認します。

# heat stack-list
+--------------------------------------+--------+-----------------+----------------------+
| ID                                   | Name   | Status          | Created              |
+--------------------------------------+--------+-----------------+----------------------+
| c4294177-3521-4c1c-b60d-223714104fe1 | mysql1 | CREATE_COMPLETE | 2013-12-26T01:17:47Z |
+--------------------------------------+--------+-----------------+----------------------+

スタックの詳細情報を見ると、「outputs」として、テンプレートで要求したOutput情報(IPアドレス)が得られています。

# heat stack-show mysql1
+----------------------+-------------------------------------------------------------------------------------------------------------------------------+
| Property             | Value                                                                                                                         |
+----------------------+-------------------------------------------------------------------------------------------------------------------------------+
| capabilities         | []                                                                                                                            |
| creation_time        | 2013-12-26T01:17:47Z                                                                                                          |
| description          | A Database instance running a local MySQL server                                                                              |
| disable_rollback     | False                                                                                                                         |
| id                   | c4294177-3521-4c1c-b60d-223714104fe1                                                                                          |
| links                | http://localhost:8004/v1/0693aaa4c0184441b6e790f23a0242b7/stacks/mysql1/c4294177-3521-4c1c-b60d-223714104fe1                  |
| notification_topics  | []                                                                                                                            |
| outputs              | [                                                                                                                             |
|                      |   {                                                                                                                           |
|                      |     "output_value": "192.168.101.3",                                                                                          |
|                      |     "description": "Database server IP",                                                                                      |
|                      |     "output_key": "PublicIp"                                                                                                  |
|                      |   }                                                                                                                           |
|                      | ]                                                                                                                             |
| parameters           | {                                                                                                                             |
|                      |   "DBUsername": "******",                                                                                                     |
|                      |   "LinuxDistribution": "F17",                                                                                                 |
|                      |   "AWS::StackName": "mysql1",                                                                                                 |
|                      |   "DBRootPassword": "******",                                                                                                 |
|                      |   "AWS::StackId": "arn:openstack:heat::0693aaa4c0184441b6e790f23a0242b7:stacks/mysql1/c4294177-3521-4c1c-b60d-223714104fe1",  |
|                      |   "KeyName": "userkey",                                                                                                       |
|                      |   "DBName": "mydb",                                                                                                           |
|                      |   "DBPassword": "******",                                                                                                     |
|                      |   "AWS::Region": "ap-southeast-1",                                                                                            |
|                      |   "InstanceType": "m1.small"                                                                                                  |
|                      | }                                                                                                                             |
| stack_name           | mysql1                                                                                                                        |
| stack_status         | CREATE_COMPLETE                                                                                                               |
| stack_status_reason  | Stack successfully created                                                                                                    |
| template_description | A Database instance running a local MySQL server                                                                              |
| timeout_mins         | 60                                                                                                                            |
| updated_time         | 2013-12-26T01:36:27Z                                                                                                          |
+----------------------+-------------------------------------------------------------------------------------------------------------------------------+

出来上がったインスタンスにFloating IPを振って、ログインすると、たしかにmysqlが動いています。

[ec2-user@mysql1 ~]$ systemctl status mysqld.service
mysqld.service - MySQL database server
	  Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled)
	  Active: active (running) since Wed, 25 Dec 2013 20:36:12 -0500; 42min ago
	 Process: 1195 ExecStartPost=/usr/libexec/mysqld-wait-ready $MAINPID (code=exited, status=0/SUCCESS)
	 Process: 1110 ExecStartPre=/usr/libexec/mysqld-prepare-db-dir %n (code=exited, status=0/SUCCESS)
	Main PID: 1194 (mysqld_safe)
	  CGroup: name=systemd:/system/mysqld.service
		  ├ 1194 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
		  └ 1383 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mysqld...

Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

カスタマイズスクリプトの中身

そして、ここからが本題なんですが、実際のところ、このゲストOSには、どんなカスタマイズスクリプトが渡されて、どんな方法でmysqlのセットアップを行なっているでしょうか?

おもむろに、curlコマンドでメタデータをぶっこ抜きます。

[ec2-user@mysql1 ~]$ curl http://169.254.169.254/latest/user-data
Content-Type: multipart/mixed; boundary="===============8755901773364978326=="
MIME-Version: 1.0

--===============8755901773364978326==
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config"

user: ec2-user

cloud_config_modules:
 - locale
 - set_hostname
 - timezone
 - update_etc_hosts
 - update_hostname

# Capture all subprocess output into a logfile
# Useful for troubleshooting cloud-init issues
output: {all: '| tee -a /var/log/cloud-init-output.log'}

--===============8755901773364978326==
Content-Type: text/cloud-boothook; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="boothook.sh"

#!/bin/bash
setenforce 0
useradd -m ec2-user
echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers

# Do not remove - the cloud boothook should always return success
exit 0

--===============8755901773364978326==
Content-Type: text/part-handler; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="part-handler.py"

#part-handler

import datetime
import errno
import os

def list_types():
    return(["text/x-cfninitdata"])

def handle_part(data, ctype, filename, payload):
    if ctype == "__begin__":
        try:
            os.makedirs('/var/lib/heat-cfntools', 0700)
        except OSError as e:
            if e.errno != errno.EEXIST:
                raise
        return

    if ctype == "__end__":
        return

    with open('/var/log/part-handler.log', 'a') as log:
        timestamp = datetime.datetime.now()
        log.write('%s filename:%s, ctype:%s\n' % (timestamp, filename, ctype))

    if ctype == 'text/x-cfninitdata':
        with open('/var/lib/heat-cfntools/%s' % filename, 'w') as f:
            f.write(payload)

        # TODO(sdake) hopefully temporary until users move to heat-cfntools-1.3
        with open('/var/lib/cloud/data/%s' % filename, 'w') as f:
            f.write(payload)

--===============8755901773364978326==
Content-Type: text/x-cfninitdata; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cfn-userdata"

#!/bin/bash -v
# Helper function
function error_exit
{
  /opt/aws/bin/cfn-signal -e 1 -r "$1" 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fmysql1%2Fc4294177-3521-4c1c-b60d-223714104fe1%2Fresources%2FMySqlWaitHandle?Timestamp=2013-12-26T01%3A17%3A47Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=e4538d20a9a9472490b8f0a8c09a1f06&SignatureVersion=2&Signature=9cq5QdllWONuSAsSO90T6h7chUKDLwna9GkZx5%2FW5uU%3D'
  exit 1
}
/opt/aws/bin/cfn-init -s mysql1 -r MySqlDatabaseServer  --region ap-southeast-1 || error_exit 'Failed to run cfn-init'
# Setup MySQL root password and create a user
mysqladmin -u root password 'passw0rd'
cat << EOF | mysql -u root --password='passw0rd'
CREATE DATABASE mydb;
GRANT ALL PRIVILEGES ON mydb.* TO "wp"@"%"
IDENTIFIED BY "wp";
FLUSH PRIVILEGES;
EXIT
EOF
# All is well so signal success
/opt/aws/bin/cfn-signal -e 0 -r "MySQL Database setup complete" 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fmysql1%2Fc4294177-3521-4c1c-b60d-223714104fe1%2Fresources%2FMySqlWaitHandle?Timestamp=2013-12-26T01%3A17%3A47Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=e4538d20a9a9472490b8f0a8c09a1f06&SignatureVersion=2&Signature=9cq5QdllWONuSAsSO90T6h7chUKDLwna9GkZx5%2FW5uU%3D'

--===============8755901773364978326==
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="loguserdata.py"

#!/usr/bin/env python

import errno
import datetime
import pkg_resources
import os
import subprocess
import sys

from distutils.version import LooseVersion

VAR_PATH = '/var/lib/heat-cfntools'

def chk_ci_version():
    v = LooseVersion(pkg_resources.get_distribution('cloud-init').version)
    return v >= LooseVersion('0.6.0')

def create_log(log_path):
    fd = os.open(log_path, os.O_WRONLY | os.O_CREAT, 0600)
    return os.fdopen(fd, 'w')

def call(args, logger):
    logger.write('%s\n' % ' '.join(args))
    logger.flush()
    try:
        p = subprocess.Popen(args, stdout=logger, stderr=logger)
        p.wait()
    except OSError as ex:
        if ex.errno == errno.ENOEXEC:
            logger.write('Userdata empty or not executable: %s\n' % str(ex))
            return os.EX_OK
        else:
            logger.write('OS error running userdata: %s\n' % str(ex))
            return os.EX_OSERR
    except Exception as ex:
        logger.write('Unknown error running userdata: %s\n' % str(ex))
        return os.EX_SOFTWARE
    return p.returncode

def main(logger):

    if not chk_ci_version():
        # pre 0.6.0 - user data executed via cloudinit, not this helper
        logger.write('Unable to log provisioning, need a newer version of'
                     ' cloud-init\n')
        return -1

    userdata_path = os.path.join(VAR_PATH, 'cfn-userdata')
    os.chmod(userdata_path, 0700)

    logger.write('Provision began: %s\n' % datetime.datetime.now())
    logger.flush()
    returncode = call([userdata_path], logger)
    logger.write('Provision done: %s\n' % datetime.datetime.now())
    if returncode:
        return returncode

if __name__ == '__main__':
    with create_log('/var/log/heat-provision.log') as log:
        code = main(log)
        if code:
            log.write('Provision failed')
            sys.exit(code)

    provision_log = os.path.join(VAR_PATH, 'provision-finished')
    with create_log(provision_log) as log:
        log.write('%s\n' % datetime.datetime.now())

--===============8755901773364978326==
Content-Type: text/x-cfninitdata; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cfn-init-data"

{"AWS::CloudFormation::Init": {"config": {"services": {"systemd": {"mysqld": {"ensureRunning": "true", "enabled": "true"}}}, "packages": {"yum": {"mysql-server": [], "mysql": []}}}}}
--===============8755901773364978326==
Content-Type: text/x-cfninitdata; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cfn-watch-server"

http://192.168.200.11:8003
--===============8755901773364978326==
Content-Type: text/x-cfninitdata; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cfn-metadata-server"

http://192.168.200.11:8000
--===============8755901773364978326==
Content-Type: text/x-cfninitdata; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cfn-boto-cfg"

[Boto]
debug = 0
is_secure = 0
https_validate_certificates = 1
cfn_region_name = heat
cfn_region_endpoint = 192.168.200.11
cloudwatch_region_name = heat
cloudwatch_region_endpoint = 192.168.200.11
--===============8755901773364978326==--

これまた長いです。。。。マルチパートで複数のファイルを送り込んでいるようです。Heatが自動で生成している部分と、先のテンプレートからやってきた部分を区別する必要がありそうです。なお、これらは、この中に含まれるpart-handler.pyによって、/var/log/heat-cfntools/以下にファイルとして保存されるようです。

# ls -l /var/lib/heat-cfntools/
合計 24
-rw-r--r--. 1 root root  196 12月 25 20:22 cfn-boto-cfg
-rw-r--r--. 1 root root  182 12月 25 20:22 cfn-init-data
-rw-r--r--. 1 root root   26 12月 25 20:22 cfn-metadata-server
-rwx------. 1 root root 1330 12月 25 20:22 cfn-userdata
-rw-r--r--. 1 root root   26 12月 25 20:22 cfn-watch-server
-rw-------. 1 root root   27 12月 25 20:36 provision-finished

まずは、メインのカスタマイズスクリプト(cfn-userdata)から見て行きましょう。

/var/lib/heat-cfntools/cfn-userdata

#!/bin/bash -v
# Helper function
function error_exit
{
  /opt/aws/bin/cfn-signal -e 1 -r "$1" 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fmysql1%2Fc4294177-3521-4c1c-b60d-223714104fe1%2Fresources%2FMySqlWaitHandle?Timestamp=2013-12-26T01%3A17%3A47Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=e4538d20a9a9472490b8f0a8c09a1f06&SignatureVersion=2&Signature=9cq5QdllWONuSAsSO90T6h7chUKDLwna9GkZx5%2FW5uU%3D'
  exit 1
}
/opt/aws/bin/cfn-init -s mysql1 -r MySqlDatabaseServer  --region ap-southeast-1 || error_exit 'Failed to run cfn-init'
# Setup MySQL root password and create a user
mysqladmin -u root password 'passw0rd'
cat << EOF | mysql -u root --password='passw0rd'
CREATE DATABASE mydb;
GRANT ALL PRIVILEGES ON mydb.* TO "wp"@"%"
IDENTIFIED BY "wp";
FLUSH PRIVILEGES;
EXIT
EOF
# All is well so signal success
/opt/aws/bin/cfn-signal -e 0 -r "MySQL Database setup complete" 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fmysql1%2Fc4294177-3521-4c1c-b60d-223714104fe1%2Fresources%2FMySqlWaitHandle?Timestamp=2013-12-26T01%3A17%3A47Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=e4538d20a9a9472490b8f0a8c09a1f06&SignatureVersion=2&Signature=9cq5QdllWONuSAsSO90T6h7chUKDLwna9GkZx5%2FW5uU%3D'

この部分は、基本的には、前述のテンプレートにベタ書きされていた内容と同じです。初めに、「cfn-init」を実行しています。

/opt/aws/bin/cfn-init -s mysql1 -r MySqlDatabaseServer  --region ap-southeast-1 || error_exit 'Failed to run cfn-init'

これは、CloudFormation謹製の自動セットアップツールで、先のテンプレートの下記の部分の指示にしたがって、パッケージの導入、サービスの起動といった典型処理を行います。

      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "config" : {
            "packages" : {
              "yum" : {
                "mysql"        : [],
                "mysql-server" : []
              }
            },
            "services" : {
              "systemd" : {
                "mysqld"   : { "enabled" : "true", "ensureRunning" : "true" }
              }
            }
          }
        }
      },

これに対応する情報は、先にcurlで引きぬいた情報に含まれており、cfn-initは、実際にはこのファイルを見て設定を行うのでしょう。

/var/lib/heat-cfntools/cfn-init-data

{"AWS::CloudFormation::Init": {"config": {"services": {"systemd": {"mysqld": {"ensureRunning": "true", "enabled": "true"}}}, "packages": {"yum": {"mysql-server": [], "mysql": []}}}}}

個人的には、最初の例のように、GitHub上のPuppetマニフェストを取ってきて、Puppetで構成した方がすっきりする気はします。(後でやってみます。)

その後は、MySQLの初期設定を行なって、最後に、次の「cfn-signal」コマンドを実行しています。

/opt/aws/bin/cfn-signal -e 0 -r "MySQL Database setup complete" 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fmysql1%2Fc4294177-3521-4c1c-b60d-223714104fe1%2Fresources%2FMySqlWaitHandle?Timestamp=2013-12-26T01%3A17%3A47Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=e4538d20a9a9472490b8f0a8c09a1f06&SignatureVersion=2&Signature=9cq5QdllWONuSAsSO90T6h7chUKDLwna9GkZx5%2FW5uU%3D'

これにより、Heat Engineにシグナルが飛んで、HeatはDBサーバーの構築完了を認識することになります。

この他には、ログイン用ユーザー「ec2-user」の作成などの処理が入っていますが、このあたりは、Heatがデフォルトで仕込むようです。Heat Engineの設定ファイルでカスタマイズ可能な予感がしています。

Content-Type: text/cloud-boothook; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="boothook.sh"

#!/bin/bash
setenforce 0
useradd -m ec2-user
echo -e 'ec2-user\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers

# Do not remove - the cloud boothook should always return success
exit 0

実験

先に触れたように、cfn-initを使わずにGitHub/Puppetと連携する形のテンプレートを作って見ました。

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

  "Description" : "A Database instance running a local PostgreSQL server",

  "Parameters" : {

    "GitRepo" : {
      "Description" : "Git Repository URL",
      "Type" : "String"
    },

    "GitTag" : {
      "Description" : "Git Tag",
      "Type" : "String"
    },

    "KeyName" : {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances",
      "Type" : "String"
    },

    "InstanceType" : {
      "Description" : "Database server EC2 instance type",
      "Type" : "String",
      "AllowedValues" : [ "m1.tiny", "m1.small", "m1.medium", "m1.large", "m1.xlarge" ],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    },

    "LinuxDistribution": {
      "Description" : "Distribution of choice",
      "Type": "String",
      "AllowedValues" : [ "F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3" ],
      "Default": "F17"
    }
  },

  "Mappings" : {
    "AWSInstanceType2Arch" : {
      "m1.tiny"    : { "Arch" : "32" },
      "m1.small"    : { "Arch" : "64" },
      "m1.medium"    : { "Arch" : "64" },
      "m1.large"   : { "Arch" : "64" },
      "m1.xlarge"   : { "Arch" : "64" }
    },
    "DistroArch2AMI": {
      "F18"      : { "32" : "F18-i386-cfntools", "64" : "F18-x86_64-cfntools" },
      "F17"      : { "32" : "F17-i386-cfntools", "64" : "F17-x86_64-cfntools" },
      "U10"      : { "32" : "U10-i386-cfntools", "64" : "U10-x86_64-cfntools" },
      "RHEL-6.1" : { "32" : "rhel61-i386-cfntools", "64" : "rhel61-x86_64-cfntools" },
      "RHEL-6.2" : { "32" : "rhel62-i386-cfntools", "64" : "rhel62-x86_64-cfntools" },
      "RHEL-6.3" : { "32" : "rhel63-i386-cfntools", "64" : "rhel63-x86_64-cfntools" }
    }
  },

  "Resources" : {
    "PgSQLDatabaseServer": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "ImageId" : { "Fn::FindInMap" : [ "DistroArch2AMI", { "Ref" : "LinuxDistribution" },
                          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData"       : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -x\n",
          "GitRepository=", { "Ref" : "GitRepo" }, "\n",
          "ConfigTag=", { "Ref" : "GitTag" }, "\n",
          "yum -y install puppet git\n",
          "RepoName=${GitRepository##*/}\n",
          "RepoName=${RepoName%.git}\n",
          "mkdir -p /tmp/gittmp\n",
          "cd /tmp/gittmp\n",
          "git clone $GitRepository\n",
          "cd $RepoName\n",
          "git checkout $ConfigTag\n",
          "export FACTER_manifest_dir=\"/tmp/gittmp/$RepoName\"\n",
          "puppet apply main.pp\n",
          "# All is well so signal success\n",
          "/opt/aws/bin/cfn-signal -e 0 -r \"PostgreSQL Database setup complete\" '",
          { "Ref" : "PgSQLWaitHandle" }, "'\n"
        ]]}}
      }
    },

    "PgSQLWaitHandle" : {
      "Type" : "AWS::CloudFormation::WaitConditionHandle"
    },

    "PgSQLWaitCondition" : {
      "Type" : "AWS::CloudFormation::WaitCondition",
      "DependsOn" : "PgSQLDatabaseServer",
      "Properties" : {
        "Handle" : {"Ref" : "PgSQLWaitHandle"},
        "Timeout" : "6000"
      }
    }

  },

  "Outputs" : {
    "PublicIp": {
      "Value": { "Fn::GetAtt" : [ "PgSQLDatabaseServer", "PublicIp" ] },
      "Description": "Database server IP"
    }
  }
}

次のコマンドでスタックを構築します。

# heat stack-create pgsql2 --template-file Pgsql_Instance.template --parameters="KeyName=userkey;LinuxDistribution=F17;InstanceType=m1.small;GitRepo=https://github.com/enakai00/pgsql_puppet.git;GitTag=f19"

まあ、結局は、カスタマイズスクリプトにゴリゴリ書いているだけなんですけどね。JSONのテンプレートファイルを見ていると、Puppet的な「宣言的」な定義ができる気になるのですが、実際には、冒頭の(1)〜(5)のような手続き型の流れを組み立てて、それをテンプレートに落とすような感覚です。

もうちょっと抽象化された宣言的な定義から、テンプレートをジェネレートするような仕組みを作ると幸せになれるのかも知れません。

ちなみに、カスタマイズスクリプトの実行ログは、ゲストOSの/var/log/heat-provision.logから確認できます。(実行時間がやたら長いのはNested KVMのテスト環境のためです。)

/var/log/heat-provision.log

Provision began: 2013-12-25 23:17:07.657192
/var/lib/heat-cfntools/cfn-userdata
+ GitRepository=https://github.com/enakai00/pgsql_puppet.git
+ ConfigTag=f19
+ yum -y install puppet git
Loaded plugins: fastestmirror, langpacks, presto, refresh-packagekit
Determining fastest mirrors
 * fedora: ftp.informatik.uni-frankfurt.de
 * updates: ftp.informatik.uni-frankfurt.de
Resolving Dependencies
--> Running transaction check
---> Package git.x86_64 0:1.7.11.7-3.fc17 will be installed
--> Processing Dependency: perl-Git = 1.7.11.7-3.fc17 for package: git-1.7.11.7-3.fc17.x86_64
--> Processing Dependency: perl(Git) for package: git-1.7.11.7-3.fc17.x86_64
--> Processing Dependency: perl(Error) for package: git-1.7.11.7-3.fc17.x86_64
---> Package puppet.noarch 0:2.7.21-2.fc17 will be installed
--> Processing Dependency: ruby(abi) = 1.9.1 for package: puppet-2.7.21-2.fc17.noarch
--> Processing Dependency: facter >= 1.5 for package: puppet-2.7.21-2.fc17.noarch
--> Processing Dependency: ruby(shadow) for package: puppet-2.7.21-2.fc17.noarch
--> Processing Dependency: ruby(selinux) for package: puppet-2.7.21-2.fc17.noarch
--> Processing Dependency: ruby(augeas) for package: puppet-2.7.21-2.fc17.noarch
--> Processing Dependency: /usr/bin/ruby for package: puppet-2.7.21-2.fc17.noarch
--> Running transaction check
---> Package facter.x86_64 0:1.6.18-3.fc17 will be installed
--> Processing Dependency: virt-what for package: facter-1.6.18-3.fc17.x86_64
--> Processing Dependency: dmidecode for package: facter-1.6.18-3.fc17.x86_64
---> Package libselinux-ruby.x86_64 0:2.1.10-3.fc17 will be installed
---> Package perl-Error.noarch 1:0.17016-7.fc17 will be installed
---> Package perl-Git.noarch 0:1.7.11.7-3.fc17 will be installed
---> Package ruby.x86_64 0:1.9.3.448-31.fc17 will be installed
--> Processing Dependency: rubygem(bigdecimal) >= 1.1.0 for package: ruby-1.9.3.448-31.fc17.x86_64
--> Processing Dependency: ruby(rubygems) >= 1.8.23 for package: ruby-1.9.3.448-31.fc17.x86_64
---> Package ruby-augeas.x86_64 0:0.4.1-3.fc17 will be installed
--> Processing Dependency: augeas-libs >= 0.8.0 for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0(AUGEAS_0.8.0)(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0(AUGEAS_0.12.0)(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0(AUGEAS_0.11.0)(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0(AUGEAS_0.10.0)(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0(AUGEAS_0.1.0)(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
--> Processing Dependency: libaugeas.so.0()(64bit) for package: ruby-augeas-0.4.1-3.fc17.x86_64
---> Package ruby-libs.x86_64 0:1.9.3.448-31.fc17 will be installed
---> Package ruby-shadow.x86_64 0:1.4.1-16.fc17 will be installed
--> Running transaction check
---> Package augeas-libs.x86_64 0:1.0.0-4.fc17 will be installed
---> Package dmidecode.x86_64 1:2.11-8.fc17 will be installed
---> Package rubygem-bigdecimal.x86_64 0:1.1.0-31.fc17 will be installed
---> Package rubygems.noarch 0:1.8.25-6.fc17 will be installed
--> Processing Dependency: rubygem(rdoc) >= 3.9.4 for package: rubygems-1.8.25-6.fc17.noarch
--> Processing Dependency: rubygem(io-console) >= 0.3 for package: rubygems-1.8.25-6.fc17.noarch
---> Package virt-what.x86_64 0:1.12-1.fc17 will be installed
--> Running transaction check
---> Package rubygem-io-console.x86_64 0:0.3-31.fc17 will be installed
---> Package rubygem-rdoc.noarch 0:3.12-5.fc17 will be installed
--> Processing Dependency: rubygem(json) < 2 for package: rubygem-rdoc-3.12-5.fc17.noarch
--> Processing Dependency: rubygem(json) >= 1.4 for package: rubygem-rdoc-3.12-5.fc17.noarch
--> Processing Dependency: ruby(irb) for package: rubygem-rdoc-3.12-5.fc17.noarch
--> Running transaction check
---> Package ruby-irb.noarch 0:1.9.3.448-31.fc17 will be installed
---> Package rubygem-json.x86_64 0:1.6.8-1.fc17 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package                 Arch        Version                 Repository    Size
================================================================================
Installing:
 git                     x86_64      1.7.11.7-3.fc17         updates      3.5 M
 puppet                  noarch      2.7.21-2.fc17           updates      1.0 M
Installing for dependencies:
 augeas-libs             x86_64      1.0.0-4.fc17            updates      297 k
 dmidecode               x86_64      1:2.11-8.fc17           fedora        73 k
 facter                  x86_64      1.6.18-3.fc17           updates       62 k
 libselinux-ruby         x86_64      2.1.10-3.fc17           fedora       111 k
 perl-Error              noarch      1:0.17016-7.fc17        fedora        30 k
 perl-Git                noarch      1.7.11.7-3.fc17         updates       45 k
 ruby                    x86_64      1.9.3.448-31.fc17       updates       61 k
 ruby-augeas             x86_64      0.4.1-3.fc17            fedora        21 k
 ruby-irb                noarch      1.9.3.448-31.fc17       updates       74 k
 ruby-libs               x86_64      1.9.3.448-31.fc17       updates      2.5 M
 ruby-shadow             x86_64      1.4.1-16.fc17           fedora        12 k
 rubygem-bigdecimal      x86_64      1.1.0-31.fc17           updates       70 k
 rubygem-io-console      x86_64      0.3-31.fc17             updates       44 k
 rubygem-json            x86_64      1.6.8-1.fc17            updates      195 k
 rubygem-rdoc            noarch      3.12-5.fc17             updates      218 k
 rubygems                noarch      1.8.25-6.fc17           updates      175 k
 virt-what               x86_64      1.12-1.fc17             fedora        24 k

Transaction Summary
================================================================================
Install  2 Packages (+17 Dependent packages)

Total download size: 8.5 M
Installed size: 32 M
Downloading Packages:
--------------------------------------------------------------------------------
Total                                           151 kB/s | 8.5 MB     00:58     
Running Transaction Check
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : ruby-libs-1.9.3.448-31.fc17.x86_64                          1/19 
  Installing : rubygem-json-1.6.8-1.fc17.x86_64                            2/19 
  Installing : rubygem-io-console-0.3-31.fc17.x86_64                       3/19 
  Installing : ruby-irb-1.9.3.448-31.fc17.noarch                           4/19 
  Installing : ruby-1.9.3.448-31.fc17.x86_64                               5/19 
  Installing : rubygem-bigdecimal-1.1.0-31.fc17.x86_64                     6/19 
  Installing : rubygem-rdoc-3.12-5.fc17.noarch                             7/19 
  Installing : rubygems-1.8.25-6.fc17.noarch                               8/19 
  Installing : 1:dmidecode-2.11-8.fc17.x86_64                              9/19 
  Installing : 1:perl-Error-0.17016-7.fc17.noarch                         10/19 
  Installing : perl-Git-1.7.11.7-3.fc17.noarch                            11/19 
  Installing : git-1.7.11.7-3.fc17.x86_64                                 12/19 
  Installing : virt-what-1.12-1.fc17.x86_64                               13/19 
  Installing : facter-1.6.18-3.fc17.x86_64                                14/19 
  Installing : ruby-shadow-1.4.1-16.fc17.x86_64                           15/19 
  Installing : libselinux-ruby-2.1.10-3.fc17.x86_64                       16/19 
  Installing : augeas-libs-1.0.0-4.fc17.x86_64                            17/19 
  Installing : ruby-augeas-0.4.1-3.fc17.x86_64                            18/19 
  Installing : puppet-2.7.21-2.fc17.noarch                                19/19 
  Verifying  : rubygems-1.8.25-6.fc17.noarch                               1/19 
  Verifying  : git-1.7.11.7-3.fc17.x86_64                                  2/19 
  Verifying  : facter-1.6.18-3.fc17.x86_64                                 3/19 
  Verifying  : rubygem-json-1.6.8-1.fc17.x86_64                            4/19 
  Verifying  : augeas-libs-1.0.0-4.fc17.x86_64                             5/19 
  Verifying  : ruby-libs-1.9.3.448-31.fc17.x86_64                          6/19 
  Verifying  : rubygem-io-console-0.3-31.fc17.x86_64                       7/19 
  Verifying  : ruby-irb-1.9.3.448-31.fc17.noarch                           8/19 
  Verifying  : 1:perl-Error-0.17016-7.fc17.noarch                          9/19 
  Verifying  : perl-Git-1.7.11.7-3.fc17.noarch                            10/19 
  Verifying  : ruby-augeas-0.4.1-3.fc17.x86_64                            11/19 
  Verifying  : libselinux-ruby-2.1.10-3.fc17.x86_64                       12/19 
  Verifying  : puppet-2.7.21-2.fc17.noarch                                13/19 
  Verifying  : ruby-1.9.3.448-31.fc17.x86_64                              14/19 
  Verifying  : ruby-shadow-1.4.1-16.fc17.x86_64                           15/19 
  Verifying  : virt-what-1.12-1.fc17.x86_64                               16/19 
  Verifying  : rubygem-bigdecimal-1.1.0-31.fc17.x86_64                    17/19 
  Verifying  : rubygem-rdoc-3.12-5.fc17.noarch                            18/19 
  Verifying  : 1:dmidecode-2.11-8.fc17.x86_64                             19/19 

Installed:
  git.x86_64 0:1.7.11.7-3.fc17           puppet.noarch 0:2.7.21-2.fc17          

Dependency Installed:
  augeas-libs.x86_64 0:1.0.0-4.fc17                                             
  dmidecode.x86_64 1:2.11-8.fc17                                                
  facter.x86_64 0:1.6.18-3.fc17                                                 
  libselinux-ruby.x86_64 0:2.1.10-3.fc17                                        
  perl-Error.noarch 1:0.17016-7.fc17                                            
  perl-Git.noarch 0:1.7.11.7-3.fc17                                             
  ruby.x86_64 0:1.9.3.448-31.fc17                                               
  ruby-augeas.x86_64 0:0.4.1-3.fc17                                             
  ruby-irb.noarch 0:1.9.3.448-31.fc17                                           
  ruby-libs.x86_64 0:1.9.3.448-31.fc17                                          
  ruby-shadow.x86_64 0:1.4.1-16.fc17                                            
  rubygem-bigdecimal.x86_64 0:1.1.0-31.fc17                                     
  rubygem-io-console.x86_64 0:0.3-31.fc17                                       
  rubygem-json.x86_64 0:1.6.8-1.fc17                                            
  rubygem-rdoc.noarch 0:3.12-5.fc17                                             
  rubygems.noarch 0:1.8.25-6.fc17                                               
  virt-what.x86_64 0:1.12-1.fc17                                                

Complete!
+ RepoName=pgsql_puppet.git
+ RepoName=pgsql_puppet
+ mkdir -p /tmp/gittmp
+ cd /tmp/gittmp
+ git clone https://github.com/enakai00/pgsql_puppet.git
Cloning into 'pgsql_puppet'...
+ cd pgsql_puppet
+ git checkout f19
Note: checking out 'f19'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 9710345... Add empty variables.pp
+ export FACTER_manifest_dir=/tmp/gittmp/pgsql_puppet
+ FACTER_manifest_dir=/tmp/gittmp/pgsql_puppet
+ puppet apply main.pp
notice: /Stage[main]/Pgsql_install/Package[postgresql-server]/ensure: created
notice: /Stage[main]/Pgsql_init/Exec[initdb]/returns: Initializing database ... OK
notice: /Stage[main]/Pgsql_init/Exec[initdb]/returns: executed successfully
notice: /Stage[main]/Pgsql_init/Exec[init_pw]/returns: Redirecting to /bin/systemctl start  postgresql.service
notice: /Stage[main]/Pgsql_init/Exec[init_pw]/returns: ALTER ROLE
notice: /Stage[main]/Pgsql_init/Exec[init_pw]/returns: Redirecting to /bin/systemctl stop  postgresql.service
notice: /Stage[main]/Pgsql_init/Exec[init_pw]: Triggered 'refresh' from 1 events
notice: /File[/var/lib/pgsql/data/pg_hba.conf]/content: content changed '{md5}825a0fdcb66009d65b7ed6eb4941a935' to '{md5}3d7342d0bc0c5e1700ccec5f2c5571fb'
notice: /Stage[main]/Pgsql_service/Service[postgresql]/ensure: ensure changed 'stopped' to 'running'
notice: /Stage[main]/Pgsql_service/Service[postgresql]: Triggered 'refresh' from 1 events
notice: Finished catalog run in 477.82 seconds
+ /opt/aws/bin/cfn-signal -e 0 -r 'PostgreSQL Database setup complete' 'http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fpgsql3%2Fdfa6b4db-25ac-409f-b666-60b24f488d58%2Fresources%2FPgSQLWaitHandle?Timestamp=2013-12-26T04%3A10%3A08Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=6b0d8d1521714066915c13c9a5202a20&SignatureVersion=2&Signature=j0rSIP9SK%2BcxfD%2BDOisnCKL%2FMJKLwvpL7Sr3k6hXpAY%3D'
DEBUG [2013-12-25 23:34:39,016] cfn-signal called Namespace(data='Application has completed configuration.', exit_code='0', reason='PostgreSQL Database setup complete', success='true', unique_id='00000', url='http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fpgsql3%2Fdfa6b4db-25ac-409f-b666-60b24f488d58%2Fresources%2FPgSQLWaitHandle?Timestamp=2013-12-26T04%3A10%3A08Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=6b0d8d1521714066915c13c9a5202a20&SignatureVersion=2&Signature=j0rSIP9SK%2BcxfD%2BDOisnCKL%2FMJKLwvpL7Sr3k6hXpAY%3D') 
DEBUG [2013-12-25 23:34:39,061] Running command: curl -X PUT -H 'Content-Type:' --data-binary '{"Status": "SUCCESS", "Reason": "PostgreSQL Database setup complete", "Data": "Application has completed configuration.", "UniqueId": "00000"}' "http://192.168.200.11:8000/v1/waitcondition/arn%3Aopenstack%3Aheat%3A%3A0693aaa4c0184441b6e790f23a0242b7%3Astacks%2Fpgsql3%2Fdfa6b4db-25ac-409f-b666-60b24f488d58%2Fresources%2FPgSQLWaitHandle?Timestamp=2013-12-26T04%3A10%3A08Z&SignatureMethod=HmacSHA256&AWSAccessKeyId=6b0d8d1521714066915c13c9a5202a20&SignatureVersion=2&Signature=j0rSIP9SK%2BcxfD%2BDOisnCKL%2FMJKLwvpL7Sr3k6hXpAY%3D"
Provision done: 2013-12-25 23:34:45.511971