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