2016年7月31日日曜日

[AWS] AMIへのリリースを Packer で自動化する


AMIに埋め込んだ、HTMLページを更新したいと思います。

直接AMIの更新はできないので、以下のような手順で新しいAMIを作り直すことになります。
  1. AMIからEC2インスタンスを起動する
  2. EC2インスタンスにHTMLページを配置する
  3. EC2インスタンスを停止する
  4. 新しいAMIを作成する
この手順を Packer で自動化してみます。

テンプレートは以下のとおり。
このテンプレートでは、環境変数でパラメータを設定し、パブリックIPでSSH(SCP)接続します。
ec2-user には、/var/ww/html 以下を触れる権限がないので、一旦、/tmp にアップロードした後にコピーします。

{
 "variables": {
    "server_name": "{{env `BUILD_SERVER_NAME`}}",
    "ami": "{{env `BUILD_AMI`}}",
    "volume_size": "{{env `BUILD_EBS_SIZE`}}",
    "src_path": "{{env `BUILD_SPATH`}}",
    "dst_path": "{{env `BUILD_DPATH`}}"
  },
  "builders" : [{
    "type" : "amazon-ebs",
    "access_key": "AKIAIC2J7TWPXY2L2HGQ",
    "secret_key": "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",
    "region" : "us-east-1",
    "availability_zone" : "us-east-1b",
    "instance_type" : "t2.nano",
    "source_ami" : "{{user `ami`}}",
    "launch_block_device_mappings": [
      {
        "device_name": "/dev/xvda",
        "volume_size": "{{user `volume_size`}}",
        "volume_type": "gp2",
        "delete_on_termination": "true"
      }
    ],
    "subnet_id" : "subnet-f528a2ad",
    "associate_public_ip_address" : "true",
    "security_group_id": "sg-bbf176df",
    "ssh_keypair_name" : "virginia_key",
    "ssh_username" : "ec2-user",
    "ssh_private_key_file" : "/root/workspace/packer/virginia_key.pem",
    "ami_name" : "TEST-AMI-{{user `server_name`}}",
    "ami_description" : "{{user `server_name`}} created by packer",
    "tags" : {
      "Name" : "{{user `server_name`}}",
      "Environment" : "develop"
    }
  }],
  "provisioners" : [
    {
     "type" : "file",
     "source" : "{{user `src_path`}}",
     "destination" : "{{user `dst_path`}}"
    },
    {
     "type" : "shell",
     "inline" : [ "sudo /bin/cp /tmp/index.html /var/www/html/index.html", "/bin/rm -f /tmp/index.html"  ]
    }
   ]
}

実行は以下のとおり。

[root@localhost packer]# env BUILD_SERVER_NAME=web01-002 BUILD_AMI=ami-5d901b4a BUILD_EBS_SIZE=8 BUILD_SPATH=/root/workspace/packer/index.html BUILD_DPATH=/tmp/index.html packer build packer.json
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-75cb94e5
==> amazon-ebs: Waiting for instance (i-75cb94e5) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Uploading /root/workspace/packer/index.html => /tmp/index.html
==> amazon-ebs: Provisioning with shell script: /tmp/packer-shell671690326
==> amazon-ebs: Stopping the source instance...
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: TEST-AMI-web01-002
    amazon-ebs: AMI: ami-c177e1d6
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Modifying attributes on AMI (ami-c177e1d6)...
    amazon-ebs: Modifying: description
==> amazon-ebs: Adding tags to AMI (ami-c177e1d6)...
    amazon-ebs: Adding tag: "Name": "web01-002"
    amazon-ebs: Adding tag: "Environment": "develop"
==> amazon-ebs: Tagging snapshot: snap-661c2288
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

us-east-1: ami-c177e1d6




2016年7月27日水曜日

[AWS] aws-cli でエラーになったらリトライする


AWSは、ネットワーク障害で aws-cli がエラーになることがある。

そこで、終了ステータスが 0 以外ならリトライするようにしてみます。

#!/bin/bash

RETRY_MAX=5
RETRY_INTERVAL=2

function retry ()
{
    cmd="$1"
    i=0
    while [ 1 ]
    do
        eval "${cmd}"
        if [ $? -ne 0 ]; then
            i=$((i+1))
            if [ $i -eq $((RETRY_MAX+1)) ]; then
                break
            fi
            echo "retry $i times waiting ..."
            sleep ${RETRY_INTERVAL}
            continue
        fi
        return 0
    done
    return 1
}

retry 'aws ec2 describe-instances --filters "Name=tag:Name,Values=ga*"'
echo "status=$?"

exit 0






2016年7月26日火曜日

[AWS] aws-cli のJSON形式のスケルトン


aws-cli では --cli-input-json '<Value>' 引数を使用すると JSON形式のパラメータを渡せます。

以下のように --generate-cli-skeleton を指定して実行すると、JSON形式の「ひな形」を生成してくれます。
これに値を設定して、--cli-input-json で指定します。

[root@localhost ~]# aws ec2 run-instances --generate-cli-skeleton
{
    "DryRun": true,
    "ImageId": "",
    "MinCount": 0,
    "MaxCount": 0,
    "KeyName": "",
    "SecurityGroups": [
        ""
    ],
    "SecurityGroupIds": [
        ""
    ],
    "UserData": "",
    "InstanceType": "",
    "Placement": {
        "AvailabilityZone": "",
        "GroupName": "",
        "Tenancy": "",
        "HostId": "",
        "Affinity": ""
    },
    "KernelId": "",
    "RamdiskId": "",
    "BlockDeviceMappings": [
        {
            "VirtualName": "",
            "DeviceName": "",
            "Ebs": {
                "SnapshotId": "",
                "VolumeSize": 0,
                "DeleteOnTermination": true,
                "VolumeType": "",
                "Iops": 0,
                "Encrypted": true
            },
            "NoDevice": ""
        }
    ],
    "Monitoring": {
        "Enabled": true
    },
    "SubnetId": "",
    "DisableApiTermination": true,
    "InstanceInitiatedShutdownBehavior": "",
    "PrivateIpAddress": "",
    "ClientToken": "",
    "AdditionalInfo": "",
    "NetworkInterfaces": [
        {
            "NetworkInterfaceId": "",
            "DeviceIndex": 0,
            "SubnetId": "",
            "Description": "",
            "PrivateIpAddress": "",
            "Groups": [
                ""
            ],
            "DeleteOnTermination": true,
            "PrivateIpAddresses": [
                {
                    "PrivateIpAddress": "",
                    "Primary": true
                }
            ],
            "SecondaryPrivateIpAddressCount": 0,
            "AssociatePublicIpAddress": true
        }
    ],
    "IamInstanceProfile": {
        "Arn": "",
        "Name": ""
    },
    "EbsOptimized": true
}




2016年7月25日月曜日

[AWS] aws-cli で 特定のワードを含むAMIを探す


例えば、AMI名が "web01" で始まる AMI を探したい場合、以下のようにします。

[root@localhost ~]# aws ec2 describe-images --filters "Name=name,Values=web01*" --query "Images[].[CreationDate,Name,ImageId]" --output text
2016-07-23T23:48:40.000Z        web01   ami-5d901b4a

この例では、

--filters で 検索条件を設定しています。

また

--query で 検索結果に表示する項目を 「作成日」、「AMI名」、「AMI-ID」だけに設定しています。






2016年7月24日日曜日

[AWS] aws-cli で AutoScaling の AMI を差し替える


aws-cli を使用して、AutoScaling のAMIを差し替えてみます。

AutoScaling の AMI を差し替えるには、新しい起動設定を作成して、AutoScalingGroup の起動設定を変更します。

既存の起動設定をコピーして「起動設定の名称」と「AMI-ID」だけを変更したかったので、以下のようなシェルをつくりました。
[root@localhost packer]# cat as_config_copy.sh
#!/bin/bash

CRT_CONFIG_NAME="web01-001"
NEW_CONFIG_NAME="web01-002"
NEW_AMI_ID="ami-6869aa05"

# 現在の起動設定をJSON形式で取得
aws autoscaling describe-launch-configurations \
 --launch-configuration-names ${CRT_CONFIG_NAME} \
 --output json \
 | jq .LaunchConfigurations[0] \
 > /tmp/xxx.tmp1

# 不要な情報を削除
cat /tmp/xxx.tmp1 \
 | egrep -v '^ *"RamdiskId"|^ *"KernelId"|^ *"LaunchConfigurationARN"|^ *"CreatedTime"|^ *"ClassicLinkVPCSecurityGroups"' \
 > /tmp/xxx.tmp2

# 起動設定の名称とAMI-IDを変更
cat /tmp/xxx.tmp2 \
 | sed -e "s/^\( *\"LaunchConfigurationName\": \)[^ ]*$/\1\"${NEW_CONFIG_NAME}\",/" \
 | sed -e "s/^\( *\"ImageId\": \)[^ ]*$/\1\"${NEW_AMI_ID}\",/" \
 > /tmp/xxx.tmp3

# 新しい起動設定を作成
new_config=`cat /tmp/xxx.tmp3`
eval "aws autoscaling create-launch-configuration --cli-input-json '${new_config}'"

rm /tmp/xxx.tmp1
rm /tmp/xxx.tmp2
rm /tmp/xxx.tmp3

exit 0

このシェルを使用して、AutoScaling のAMIを差し替えてみます。

まず、現在の起動設定と、AutoScalingGroupを確認します。
[root@localhost ~]# aws autoscaling describe-launch-configurations --query "LaunchConfigurations[].[LaunchConfigurationName,ImageId]"
-------------------------------
|DescribeLaunchConfigurations |
+------------+----------------+
|  web01-001 |  ami-5d901b4a  |
+------------+----------------+
[root@localhost ~]# aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[].[AutoScalingGroupName,LaunchConfigurationName]"
---------------------------
|DescribeAutoScalingGroups|
+-----------+-------------+
|  WebGroup |  web01-001  |
+-----------+-------------+

上記のシェルで web01-001 をコピーして web01-002 という名称に変更し、AMI-ID も変更した起動設定を作成します。
[root@localhost ~]# bash ./packer/as_config_copy.sh
[root@localhost ~]# aws autoscaling describe-launch-configurations --query "LaunchConfigurations[].[LaunchConfigurationName,ImageId]"
-------------------------------
|DescribeLaunchConfigurations |
+------------+----------------+
|  web01-001 |  ami-5d901b4a  |
|  web01-002 |  ami-6869aa05  |
+------------+----------------+

AutoScalingGroupの "WebGroup" の起動設定を "web01-002" に変更します。
[root@localhost ~]# aws autoscaling update-auto-scaling-group --auto-scaling-group-name "WebGroup" --launch-configuration-name "web01-002"
[root@localhost ~]# aws autoscaling describe-auto-scaling-groups --query "AutoScalingGroups[].[AutoScalingGroupName,LaunchConfigurationName]"
---------------------------
|DescribeAutoScalingGroups|
+-----------+-------------+
|  WebGroup |  web01-002  |
+-----------+-------------+

最後に、不要になった起動設定の "web01-001" を削除します。
root@localhost ~]# aws autoscaling delete-launch-configuration --launch-configuration-name "web01-001"
[root@localhost ~]# aws autoscaling describe-launch-configurations --query "LaunchConfigurations[].[LaunchConfigurationName,ImageId]"
-------------------------------
|DescribeLaunchConfigurations |
+------------+----------------+
|  web01-002 |  ami-6869aa05  |
+------------+----------------+

以上でAutoScalingのAMIを差し替えられました。

起動中のEC2インスタンスは、古いままなので、
一度、EC2インスタンスをSTOPすれば、新しいAMIでEC2インスタンスが起動します。




2016年7月23日土曜日

[AWS] ELB+EC2(Apache/Tomcat) を Gatling で負荷テスト


ELB+EC2(1台)の構成で、Gatling で負荷テストしてみます。


1.WEBサーバの準備


テスト対象のWEBサーバを用意します。
今回は、AmazonLinux のAMIでEC2を作成し、インスタンスタイプは、t2.nano にしました。

1.1.パッケージのインストール


このEC2インスタンスに Apache、Tomcat をインストールします。また、Tomcatのサンプルアプリケーションもインストールします。
[ec2-user@ip-10-0-11-160 ~]$ sudo yum install httpd tomcat8 tomcat8-webapps

1.2.Apacheの設定


Apache を worker で動かします。
/etc/sysconfig/httpd を修正します。
下記がコメントアウトされているので1桁目の "#" を削除して、有効にします。
HTTPD=/usr/sbin/httpd.worker
/etc/httpd/conf/httpd.conf を修正します。
ELB向けに Timeout と Keepalive の設定を変更します。
KeepAliveTimeout は、ELBのアイドルタイムアウトより長くします。
KeepAlive On
MaxKeepAliveRequests 50
KeepAliveTimeout 120
MPMは 以下のようにしました。 MaxClient をデフォルトより増やしています。
<IfModule worker.c>
StartServers         4
MaxClients         512
MinSpareThreads     75
MaxSpareThreads    250
ThreadsPerChild     64
MaxRequestsPerChild  0
</IfModule>

1.3.ApacheとTomcat連携の設定


Apache へのリクエストを Tomcat に連携します。
/etc/httpd/conf.d/proxy_ajp.conf を作成して、内容を以下のようにします。
Tomcat のサンプルアプリけしょんが動くようにします。
<Location /sample/>
    ProxyPass ajp://localhost:8009/sample/ keepalive=on
    ProxyPassReverse ajp://localhost:8009/sample/
</Location>

1.4.Tomcat の設定


Tomcat はデフォルト設定で使用します。

1.5.ヘルスチェックページの用意


ELBが使用するヘルスチェック用のページを用意します。
[ec2-user@ip-10-0-11-160 html]$ cat /var/www/html/alive
alive

1.6.ApacheとTomcat の起動


サービスを起動します
[ec2-user@ip-10-0-11-160 ~]$ service httpd start
[ec2-user@ip-10-0-11-160 ~]$ service tomcat start

1.7.動作確認


curl を使用して、Apache経由でTomcat のサンプルアプリケーションにアクセスします
[ec2-user@ip-10-0-11-160 ~]$ curl http://localhost/sample/hello
<html>
<head>
<title>Sample Application Servlet Page</title>
</head>
<body bgcolor=white>
<table border="0">
<tr>
<td>
<img src="images/tomcat.gif">
</td>
<td>
<h1>Sample Application Servlet</h1>
This is the output of a servlet that is part of
the Hello, World application.
</td>
</tr>
</table>
</body>
</html>

バージョンは以下のとおり
[ec2-user@ip-10-0-11-160 ~]$ apachectl -V
Server version: Apache/2.2.31 (Unix)
Server built:   Jul 19 2016 00:11:53
Server's Module Magic Number: 20051115:40
Server loaded:  APR 1.5.1, APR-Util 1.4.1
Compiled using: APR 1.5.1, APR-Util 1.4.1
Architecture:   64-bit
Server MPM:     Worker
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/worker"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"
[ec2-user@ip-10-0-11-160 ~]$ rpm -aq | grep tomcat8
tomcat8-servlet-3.1-api-8.0.35-1.61.amzn1.noarch
tomcat8-lib-8.0.35-1.61.amzn1.noarch
tomcat8-el-3.0-api-8.0.35-1.61.amzn1.noarch
tomcat8-jsp-2.3-api-8.0.35-1.61.amzn1.noarch
tomcat8-8.0.35-1.61.amzn1.noarch
tomcat8-webapps-8.0.35-1.61.amzn1.noarch
ApacheとTomcatの制限値はデフォルトです。
[ec2-user@ip-10-0-11-160 ~]$ cat /proc/`pgrep java|head -1`/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             1898                 1898                 processes
Max open files            4096                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       1898                 1898                 signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us
[ec2-user@ip-10-0-11-160 ~]$ cat /proc/`pgrep httpd|head -1`/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             1898                 1898                 processes
Max open files            1024                 4096                 files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       1898                 1898                 signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us


2.ELBを作成


内部用ELBを作成して、上記で作成してWEBサーバを登録します。
各種パラメータはデフォルト値にします。
ヘルスチェック用のURLは "/alive" にします。


3.Gatling用サーバの準備


Gatlingを動かすサーバを用意します。
今回は、AmazonLinux のAMIでEC2を作成し、インスタンスタイプは、t2.nano にしました。

3.1.Javaのインストール


OpenJDKを使います
[ec2-user@ip-10-0-11-193 ~]$ sudo yum install java-1.8.0-openjdk
バージョンを確認します。
[ec2-user@ip-10-0-11-193 ~]$ java -version
openjdk version "1.8.0_101"
OpenJDK Runtime Environment (build 1.8.0_101-b13)
OpenJDK 64-Bit Server VM (build 25.101-b13, mixed mode)
このとき、別のバージョンが表示されるようであれば、以下のようにして切り替えます。
[ec2-user@ip-10-0-11-193 ~]$ sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
* +1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2

3.2.Gatling のインストール


Gatling のサイトから zip ファイルをダウンロードして、workspace ディレクトリで解凍します。
解凍後のディレクトリ名が長すぎるので、短い名称に変更します。
[ec2-user@ip-10-0-11-193 ~]$ mkdir ~/workspace
[ec2-user@ip-10-0-11-193 ~]$ cd ~/workspace
[ec2-user@ip-10-0-11-193 workspace]$ curl -OL https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/2.2.2/gatling-charts-highcharts-bundle-2.2.2-bundle.zip
[ec2-user@ip-10-0-11-193 workspace]$ unzip ./gatling-charts-highcharts-bundle-2.2.2-bundle.zip
[ec2-user@ip-10-0-11-193 workspace]$ mv ./gatling-charts-highcharts-bundle-2.2.2-bundle gatling

3.3.Gatlingのテストシナリオ作成


テストシナリオは、以下のようにしたいと思います。
 ・1ユーザ1アクセスにする
 ・/sample/hello にアクセスする
 ・レスポンスのステータスが 200 であることをチェックする
 ・レスポンスに "Sample Application Servler" が含まれることをチェックする
 ・2分間の合計で10000ユーザになるまで段階的にユーザを増減させる。

Gatoling のテストケースは、以下のとおり。
後述しますが、Gatling 実行時に メニューの class 名が表示されます。
[ec2-user@ip-10-0-11-193 workspace]$ cat ./gatling/user-files/simulations/test.scala
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class TestMySimulation extends Simulation {

  val httpConf = http
      .baseURL("http://internal-in-elb-1279773330.us-east-1.elb.amazonaws.com")
      .acceptCharsetHeader("ISO-8859-1,utf-8;q=0.7,*;q=0.7")
      .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
      .acceptEncodingHeader("gzip, deflate")
      .acceptLanguageHeader("fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3")
      .disableFollowRedirect

  val scn = scenario("Test Scenario")
      .exec(http("request")
          .get("/sample/hello")
          .check(status.is(200))
          .check(regex( """Sample Application Servlet"""))
      )

  setUp(scn.inject(
     //atOnceUsers(1)
     //rampUsersPerSec(1).to(60).during(30),
     //constantUsersPerSec(60) during(300),
     //rampUsersPerSec(60).to(1).during(30)
     heavisideUsers(10000) over(120 seconds)
  ).protocols(httpConf))

}

3.4.Gatling実行時にDNSキャッシュを無効にする


JavaはデフォルトでDNSキャッシュが有効になっています。
ELBはDNSラウンドロビンを使用しているので、アクセスに偏りでできないように、JavaのDNSキャッシュを無効にします。
下記の赤字部分を追加します。
[ec2-user@ip-10-0-11-193 gatling]$ cat ./bin/gatling.sh
~省略~
JAVA_OPTS="${JAVA_OPTS} -XX:+HeapDumpOnOutOfMemoryError"
JAVA_OPTS="${JAVA_OPTS} -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false"
JAVA_OPTS="${JAVA_OPTS} -Dsun.net.inetaddr.ttl=0 -Dsun.net.inetaddr.negative.ttl=0"
COMPILER_OPTS="$JAVA_OPTS -Xss100M"

# Setup classpaths
~省略~

3.5.Gatlingのレポートをブラウザでみれるようにする


Gatlingを実行するとHTML形式のレポートを作成してくれます。
このレポートをブラウザで見るために Apache をインストールして、レポートが格納されたディレクトリにリンクを作成します。
apache ユーザで参照できるように、/home/ec2-user のパーミションを変更しています。
[ec2-user@ip-10-0-11-193 ~]$ sudo yum install httpd
[ec2-user@ip-10-0-11-193 ~]$ chmod 755 /home/ec2-user
[ec2-user@ip-10-0-11-193 workspace]$ ln -s /home/ec2-user/workspace/gatling/results /var/www/html/gatling
[ec2-user@ip-10-0-11-193 ~]$ service httpd start


4.Gating の実行


準備ができたので、Gatling を実行します。
以下のようにして、gatling.sh を実行すると、上記で作成したテストシナリオが一覧に表示されるので、番号で選択してシナリオを実行します。
緑字部分で ENTER します。
[ec2-user@ip-10-0-11-193 workspace]$ ./gatling/bin/gatling.sh
GATLING_HOME is set to /home/ec2-user/workspace/gatling
Choose a simulation number:
     [0] TestMySimulation
     [1] computerdatabase.BasicSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
0 <ENTER>
Select simulation id (default is 'testmysimulation'). Accepted characters are a-z, A-Z, 0-9, - and _
<ENTER>
Select run description (optional)
<ENTER>
Simulation TestMySimulation started...

~省略~

================================================================================
2016-07-23 05:32:27                                         117s elapsed
---- Test Scenario -------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done:10000
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=10000  KO=0     )
> request                                                  (OK=10000  KO=0     )
================================================================================

Simulation TestMySimulation completed in 117 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                      10000 (OK=10000  KO=0     )
> min response time                                      2 (OK=2      KO=-     )
> max response time                                    176 (OK=176    KO=-     )
> mean response time                                     4 (OK=4      KO=-     )
> std deviation                                          7 (OK=7      KO=-     )
> response time 50th percentile                          3 (OK=3      KO=-     )
> response time 75th percentile                          4 (OK=4      KO=-     )
> response time 95th percentile                          7 (OK=7      KO=-     )
> response time 99th percentile                         12 (OK=12     KO=-     )
> mean requests/sec                                 84.746 (OK=84.746 KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                         10000 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 1s.
Please open the following file: /home/ec2-user/workspace/gatling/results/testmysimulation-1469251829209/index.html
テストが終わると、最後にサマリーが表示されます。
サマリーの一番下が、レポートの格納場所です。
ブラウザで http://<IPアドレス>/gatling/ にアクセスすると、レポートの一覧が表示されるので見たいレポートをクリックします。



上記実行結果のレポートは下図のとおり。


アクセス状況をグラフで見ると、徐々にアクセスが増えて、徐々にアクセスが減る山の形になっているのがわかります。



ちなみに、
テストシナリオ実行中のWEBサーバのアクセスログを見ると、以下のように表示されます。
ELBのIPアドレスが2つあるのがわかります。
[ec2-user@ip-10-0-11-160 ~]$ sudo tail /var/log/httpd/access_log
10.0.11.119 - - [23/Jul/2016:05:32:21 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:22 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:23 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.119 - - [23/Jul/2016:05:32:23 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:24 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.119 - - [23/Jul/2016:05:32:25 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.8 - - [23/Jul/2016:05:32:27 +0000] "GET /sample/hello HTTP/1.1" 200 343 "-" "-"
10.0.11.119 - - [23/Jul/2016:05:32:27 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.8 - - [23/Jul/2016:05:32:53 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"
10.0.11.119 - - [23/Jul/2016:05:32:57 +0000] "GET /alive HTTP/1.1" 200 6 "-" "ELB-HealthChecker/1.0"










2016年7月16日土曜日

[AWS] Packer + Ansible + Serverspec でAMI作成を自動化する


AmazonLinux にApacheHTTPをインストールしてAMIを作成したいと思います。
今回は、以下の手順で、作業することにします。
  1. AmazonLinux のAMIからEC2インスタンスを起動
  2. EC2インスタンスにSSHログイン
  3. ApacheHTTPをインストール
  4. httpサービスの起動確認
  5. EC2インスタンスを停止
  6. AMIを作成
  7. EC2インスタンスをターミネート
手作業だと、AWSコンソールや Teraterm を使うことになりますが、
これを、Packer+Ansible+Serverspec で自動化し、Packerのコマンド1回で済むようにしてみます。

Packerで1~7までの作業を実施しますが、
3では、Ansible のプレイブックを実行し、4では、Serverspec を実行します。


1.作業用のサーバを準備する


Packer+Ansible+Serverspecを実行するサーバを用意します。
今回は、AmazonLinuxを使用します。
なお、全ての作業は、ec2-user ユーザで行い、~/workspace ディレクトリを作って、そこに、Packer、Ansible、Serverspec のファイルを置くことにします。

1.1.作業用のEC2インスタンスを作成


今回、作業用に使用するEC2インスタンスには、管理者権限を付与したIAMロールを割り当てます。
これにより、Packer でAMIを作成するときに、アクセスキーの設定が不要になります。

1.2.Packer のインストール


Packer のダウンロードサイトからバイナリのコマンドをダウンロードして解凍し、/usr/local/bin に置きます。
[ec2-user@ip-10-0-11-158 ~]$ curl -OL https://releases.hashicorp.com/packer/0.10.1/packer_0.10.1_linux_amd64.zip
[ec2-user@ip-10-0-11-158 ~]$ unzip ./packer_0.10.1_linux_amd64.zip
[ec2-user@ip-10-0-11-158 ~]$ ls
packer  packer_0.10.1_linux_amd64.zip
[ec2-user@ip-10-0-11-158 ~]$ sudo mv ./packer /usr/local/bin/.
バージョンは以下のとおり。
[ec2-user@ip-10-0-11-158 ~]$ packer --version
0.10.1

1.3.Ansible のインストール


pipコマンドで Ansible をインストールします。
[ec2-user@ip-10-0-11-158 ~]$ sudo pip install ansible
バージョンは以下のとおり。
[ec2-user@ip-10-0-11-158 ~]$ ansible --version
ansible 2.1.0.0
  config file =
  configured module search path = Default w/o overrides
設定は以下のとおり。
[ec2-user@ip-10-0-11-158 ~]$ cat ~/.ansible.cfg
[defaults]
retry_files_enabled = False
host_key_checking = False
log_path=~/workspace/logs/ansible.log
[ssh_connection]
scp_if_ssh = True

1.4.Serverspec のインストール


gem コマンドで Serverspec をインストールします。
[ec2-user@ip-10-0-11-158 ~]$ sudo gem install serverspec
[ec2-user@ip-10-0-11-158 ~]$ sudo gem install rake
バージョンは以下のとおり。
[ec2-user@ip-10-0-11-158 ~]$ gem list --local | grep serverspec
serverspec (2.36.0)


2.Ansibleのプレイブックを作成する


Ansible のプレイブックを作成します。
yum で httpd をインストールし、自動起動をONにしてサービスを開始します。なお、httpがインストールされるサーバのIPアドレスを確認するために、ipコマンドで取得した eth0 のIPアドレスを表示します。
[ec2-user@ip-10-0-11-158 workspace]$ cat playbook_dev001.yml
---
- hosts: all
  become: yes
  tasks:
    - name: IPアドレス
      shell: ip a | awk '($0~/inet .* eth0/){split($2,i,"/");print i[1]}'
      register: ip_ret
    - debug: var=ip_ret.stdout

    - name: httpd インストール
      yum: name=httpd state=present
    - name: httpd の自動起動ON
      service: name=httpd enabled=yes state=started
自分自身を対象にして、プレイブックをテストします。
[ec2-user@ip-10-0-11-158 workspace]$  ansible-playbook -i ,127.0.0.1 playbook_dev001.yml -u ec2-user --private-key=./virginia_key.pem

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [127.0.0.1]

TASK [IPアドレス] ******************************************************************
changed: [127.0.0.1]

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "ip_ret.stdout": "10.0.11.158"
}

TASK [httpd インストール] ************************************************************
changed: [127.0.0.1]

TASK [httpd の自動起動ON] ***********************************************************
changed: [127.0.0.1]

PLAY RECAP *********************************************************************
127.0.0.1                  : ok=5    changed=3    unreachable=0    failed=0

3.Packer+AnsibleでAMIを作成する


Ansibleのプレイブックができたので、Packer から起動したEC2インスタンスに Ansible のプレイブックを適用してAMIを作成してみます。
Packer のテンプレートは以下のようにました。
環境変数で、サーバ名、AMI-ID、EBSボリュームのサイズ(GB)、プレイブックを渡します。
[ec2-user@ip-10-0-11-158 workspace]$ cat packer.json
{
 "variables": {
    "server_name": "{{env `BUILD_SERVER_NAME`}}",
    "ami": "{{env `BUILD_AMI`}}",
    "volume_size": "{{env `BUILD_EBS_SIZE`}}",
    "ansible_playbook": "{{env `BUILD_PLAYBOOK`}}"
  },
  "builders" : [{
    "type" : "amazon-ebs",
    "region" : "us-east-1",
    "availability_zone" : "us-east-1b",
    "instance_type" : "t2.nano",
    "source_ami" : "{{user `ami`}}",
    "launch_block_device_mappings": [
      {
        "device_name": "/dev/xvda",
        "volume_size": "{{user `volume_size`}}",
        "volume_type": "gp2",
        "delete_on_termination": "true"
      }
    ],
    "subnet_id" : "subnet-f528a2ad",
    "associate_public_ip_address" : "true",
    "security_group_id": "sg-bbf176df",
    "ssh_keypair_name" : "virginia_key",
    "ssh_username" : "ec2-user",
    "ssh_private_key_file" : "/home/ec2-user/workspace/virginia_key.pem",
    "ssh_private_ip" : "true",
    "ami_name" : "TEST-AMI-{{user `server_name`}}",
    "ami_description" : "{{user `server_name`}} created by packer",
    "tags" : {
      "Name" : "{{user `server_name`}}",
      "Environment" : "develop"
    }
  }],
  "provisioners" : [
    {
     "type" : "ansible",
     "playbook_file" : "{{user `ansible_playbook`}}"
    }
   ]
}
キーペアのvirginia_keyはAWSコンソールで事前に作成しておきます。
サブネットやセキュリティグループは、作業用サーバと同じにして、プライベートIPアドレスで接続可能な環境を前提にしています。
また、yum を使用するので、Packer が一時的に起動するEC2インスタンスには パブリックIPアドレスを自動割り当てします。

続いて、Packe 実行用のシェルを作成します。
[ec2-user@ip-10-0-11-158 workspace]$ cat packer.sh
#!/bin/bash

server_name="dev001"
base_dir="/home/ec2-user/workspace"
packer_json="${base_dir}/packer.json"
packer_log="${base_dir}/logs/packer-${server_name}.log"

# Packer
export BUILD_PLAYBOOK="${base_dir}/playbook_${server_name}.yml"
export BUILD_SERVER_NAME="${server_name}"
export BUILD_AMI="ami-6869aa05"
export BUILD_EBS_SIZE="10"

env PACKER_LOG=1 PACKER_LOG_PATH=${packer_log} packer build ${packer_json}

exit 0
環境変数にAmazonLinux のAMI-IDなどを設定して、Packerを実行するようにし、詳細なログファイルを出力するようにしています。

これを実行すると以下のようになります。
[ec2-user@ip-10-0-11-158 workspace]$ ./packer.sh
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-1fc8af8f
==> amazon-ebs: Waiting for instance (i-1fc8af8f) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with Ansible...
==> amazon-ebs: Executing Ansible: ansible-playbook /home/ec2-user/workspace/playbook_dev001.yml -i /tmp/packer-provisioner-ansible182536530 --private-key /tmp/ansible-key107272703
==> amazon-ebs: SSH proxy: serving on 127.0.0.1:45242
    amazon-ebs:
    amazon-ebs: PLAY [all] *********************************************************************
    amazon-ebs:
    amazon-ebs: TASK [setup] *******************************************************************
    amazon-ebs: SSH proxy: accepted connection
==> amazon-ebs: authentication attempt from 127.0.0.1:45682 to 127.0.0.1:45242 as ec2-user using none
==> amazon-ebs: authentication attempt from 127.0.0.1:45682 to 127.0.0.1:45242 as ec2-user using publickey
    amazon-ebs: ok: [default]
    amazon-ebs:
    amazon-ebs: TASK [IPアドレス] ******************************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: TASK [debug] *******************************************************************
    amazon-ebs: ok: [default] => {
    amazon-ebs:     "ip_ret.stdout": "10.0.11.39"
    amazon-ebs: }
    amazon-ebs:
    amazon-ebs: TASK [httpd インストール] ************************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: TASK [httpd の自動起動ON] ***********************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: PLAY RECAP *********************************************************************
    amazon-ebs: default                    : ok=5    changed=3    unreachable=0    failed=0
    amazon-ebs:
==> amazon-ebs: shutting down the SSH proxy
==> amazon-ebs: Stopping the source instance...
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: TEST-AMI-dev001
    amazon-ebs: AMI: ami-10be3307
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Modifying attributes on AMI (ami-10be3307)...
    amazon-ebs: Modifying: description
==> amazon-ebs: Adding tags to AMI (ami-10be3307)...
    amazon-ebs: Adding tag: "Name": "dev001"
    amazon-ebs: Adding tag: "Environment": "develop"
==> amazon-ebs: Tagging snapshot: snap-4179b4a5
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

us-east-1: ami-10be3307
Packer実行中は、下図のように "Packer Builder" という名称のEC2インスタンスが起動します。
この "Packer Builder" に対して、プレイブックが適用されます。
なお、AMI作成後は、ターミネートされます。



AMIは下図のように作成されています。



4.Serverspecを作成する


Serverspecで http のテストケースを作成します。
まず、以下のようにして serverspec の雛形を作成します。
[ec2-user@ip-10-0-11-158 workspace]$ mkdir serverspec
[ec2-user@ip-10-0-11-158 workspace]$ cd serverspec
[ec2-user@ip-10-0-11-158 serverspec]$ serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 1

Vagrant instance y/n: n
Input target host name: packer-builder
 + spec/
 + spec/packer-builder/
 + spec/packer-builder/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec
次に環境変数でSSH秘密鍵を指定できるように ~/workspace/serverspec/spec/spec_helper.rb を修正します。
以下の赤字部分を spec_helper.rb に追加します。
options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin
options[:keys] = ENV['TARGET_SSH_KEY']

set :host,        options[:host_name] || host
set :ssh_options, options
テストケースは以下のとおりです。
httpdパッケージのインストール、自動起動ON、サービス起動中かをチェックします。
[ec2-user@ip-10-0-11-158 workspace]$ cat serverspec/spec/packer-builder/http_spec.rb
require 'spec_helper'

describe package('httpd') do
  it { should be_installed }
end

describe service('httpd') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end
あとは、Packer が起動したEC2インスタンスに対して、Serverspec を実行するようにします。
いろいろ、調べたのですが、簡単な方法が見つかりませんでした。

しかたないので、Packer が起動したEC2インスタンスのIPアドレスをaws-cliで取得して、Serverspecを実行することにしました。
以下のような、Packer から Serverspec を実行するためのシェルを作成しました
[ec2-user@ip-10-0-11-158 workspace]$ cat serverspec.sh
#!/bin/bash

BASE_DIR=/home/ec2-user/workspace
SERVERSPEC_DIR=${BASE_DIR}/serverspec

ip=` \
aws ec2 describe-instances \
 --region us-east-1 \
 --filters "Name=tag-key,Values=Name" \
  "Name=tag-value,Values=Packer Builder" \
  "Name=instance-state-name,Values=running" \
 --output text \
 --query "Reservations[].Instances[].[LaunchTime,PrivateIpAddress]" \
 | sort | tail -1 | cut -f2 \
`
ln -s ./packer-builder ${SERVERSPEC_DIR}/spec/${ip}

(
  cd ${SERVERSPEC_DIR}
  env TARGET_SSH_KEY=${BASE_DIR}/virginia_key.pem rake spec:${ip} > /tmp/xxx
)

rm -f ${SERVERSPEC_DIR}/spec/${ip} > /dev/null 2>&1

n=`cat /tmp/xxx | tail -2 | grep -c '0 failures'`
cat /tmp/xxx
rm -f /tmp/xxx

ret=0
if [ $n -eq 0 ]; then
  ret=1
fi

exit $ret

aws-cli で "Nameタグ"が"Packer Builder"のインスタンスのなかから、起動時間が最新のものを選択し、プライベートIPアドレスを取得します。

取得したIPアドレスで Serverspec の packer-build にシンボリックリンクを作成して、Serverspec を実行します。

テスト結果が全部OKであれば終了ステータスで 0 を返します。

このシェルをテストしてみます。
作業用のサーバの "Nameタグ" を "Packer Builder" に変更して、シェルを実行すると以下のようになります。
httpd をインストールしてないので、エラーになりますが、ちゃんと動いているようです。
[ec2-user@ip-10-0-11-158 workspace]$ ./serverspec.sh
/usr/bin/ruby2.0 -I/usr/local/share/ruby/gems/2.0/gems/rspec-support-3.5.0/lib:/usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/lib /usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/exe/rspec --pattern spec/10.0.11.158/\*_spec.rb failed
/usr/bin/ruby2.0 -I/usr/local/share/ruby/gems/2.0/gems/rspec-support-3.5.0/lib:/usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/lib /usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/exe/rspec --pattern spec/10.0.11.158/\*_spec.rb

Package "httpd"
  should be installed (FAILED - 1)

Service "httpd"
  should be enabled (FAILED - 2)
  should be running (FAILED - 3)

Port "80"
  should be listening (FAILED - 4)

Failures:

  1) Package "httpd" should be installed
     On host `10.0.11.158'
     Failure/Error: it { should be_installed }
       expected Package "httpd" to be installed
       sudo -p 'Password: ' /bin/sh -c rpm\ -q\ httpd
       package httpd is not installed

     # ./spec/10.0.11.158/http_spec.rb:4:in `block (2 levels) in <top (required)>'

  2) Service "httpd" should be enabled
     On host `10.0.11.158'
     Failure/Error: it { should be_enabled }
       expected Service "httpd" to be enabled
       sudo -p 'Password: ' /bin/sh -c chkconfig\ --list\ httpd\ \|\ grep\ 3:on

     # ./spec/10.0.11.158/http_spec.rb:8:in `block (2 levels) in <top (required)>'

  3) Service "httpd" should be running
     On host `10.0.11.158'
     Failure/Error: it { should be_running }
       expected Service "httpd" to be running
       sudo -p 'Password: ' /bin/sh -c ps\ aux\ \|\ grep\ -w\ --\ httpd\ \|\ grep\ -qv\ grep

     # ./spec/10.0.11.158/http_spec.rb:9:in `block (2 levels) in <top (required)>'

  4) Port "80" should be listening
     On host `10.0.11.158'
     Failure/Error: it { should be_listening }
       expected Port "80" to be listening
       sudo -p 'Password: ' /bin/sh -c netstat\ -tunl\ \|\ grep\ --\ :80\\\

     # ./spec/10.0.11.158/http_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.31946 seconds (files took 0.28991 seconds to load)
4 examples, 4 failures

Failed examples:

rspec ./spec/10.0.11.158/http_spec.rb:4 # Package "httpd" should be installed
rspec ./spec/10.0.11.158/http_spec.rb:8 # Service "httpd" should be enabled
rspec ./spec/10.0.11.158/http_spec.rb:9 # Service "httpd" should be running
rspec ./spec/10.0.11.158/http_spec.rb:13 # Port "80" should be listening

[ec2-user@ip-10-0-11-158 workspace]$ echo $?
1

5.Packer+Ansibe+ServerspecでAMIを作成する


前述のPacker のテンプレートを修正して、Ansibleの後に Serverspec を実行するようにします。
テンプレートは以下のとおり。
赤字部分を追加しました。
[ec2-user@ip-10-0-11-158 workspace]$ cat packer.json
{
 "variables": {
    "server_name": "{{env `BUILD_SERVER_NAME`}}",
    "ami": "{{env `BUILD_AMI`}}",
    "volume_size": "{{env `BUILD_EBS_SIZE`}}",
    "ansible_playbook": "{{env `BUILD_PLAYBOOK`}}"
  },
  "builders" : [{
    "type" : "amazon-ebs",
    "region" : "us-east-1",
    "availability_zone" : "us-east-1b",
    "instance_type" : "t2.nano",
    "source_ami" : "{{user `ami`}}",
    "launch_block_device_mappings": [
      {
        "device_name": "/dev/xvda",
        "volume_size": "{{user `volume_size`}}",
        "volume_type": "gp2",
        "delete_on_termination": "true"
      }
    ],
    "subnet_id" : "subnet-f528a2ad",
    "associate_public_ip_address" : "true",
    "security_group_id": "sg-bbf176df",
    "ssh_keypair_name" : "virginia_key",
    "ssh_username" : "ec2-user",
    "ssh_private_key_file" : "/home/ec2-user/workspace/virginia_key.pem",
    "ssh_private_ip" : "true",
    "ami_name" : "TEST-AMI-{{user `server_name`}}",
    "ami_description" : "{{user `server_name`}} created by packer",
    "tags" : {
      "Name" : "{{user `server_name`}}",
      "Environment" : "develop"
    }
  }],
  "provisioners" : [
    {
     "type" : "ansible",
     "playbook_file" : "{{user `ansible_playbook`}}"
    },
    {
     "type" : "shell-local",
     "command" : "/home/ec2-user/workspace/serverspec.sh"
    }
   ]
}
これを、前述のシェルで実行すると、以下のとおり。
[ec2-user@ip-10-0-11-158 workspace]$ ./packer.sh
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name...
==> amazon-ebs: Inspecting the source AMI...
==> amazon-ebs: Launching a source AWS instance...
    amazon-ebs: Instance ID: i-b12e4821
==> amazon-ebs: Waiting for instance (i-b12e4821) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with Ansible...
==> amazon-ebs: Executing Ansible: ansible-playbook /home/ec2-user/workspace/playbook_dev001.yml -i /tmp/packer-provisioner-ansible519331836 --private-key /tmp/ansible-key013979345
==> amazon-ebs: SSH proxy: serving on 127.0.0.1:43408
    amazon-ebs:
    amazon-ebs: PLAY [all] *********************************************************************
    amazon-ebs:
    amazon-ebs: TASK [setup] *******************************************************************
    amazon-ebs: SSH proxy: accepted connection
==> amazon-ebs: authentication attempt from 127.0.0.1:55066 to 127.0.0.1:43408 as ec2-user using none
==> amazon-ebs: authentication attempt from 127.0.0.1:55066 to 127.0.0.1:43408 as ec2-user using publickey
    amazon-ebs: ok: [default]
    amazon-ebs:
    amazon-ebs: TASK [IPアドレス] ******************************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: TASK [debug] *******************************************************************
    amazon-ebs: ok: [default] => {
    amazon-ebs:     "ip_ret.stdout": "10.0.11.66"
    amazon-ebs: }
    amazon-ebs:
    amazon-ebs: TASK [httpd インストール] ************************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: TASK [httpd の自動起動ON] ***********************************************************
    amazon-ebs: changed: [default]
    amazon-ebs:
    amazon-ebs: PLAY RECAP *********************************************************************
    amazon-ebs: default                    : ok=5    changed=3    unreachable=0    failed=0
    amazon-ebs:
==> amazon-ebs: shutting down the SSH proxy
==> amazon-ebs: Executing local command: /home/ec2-user/workspace/serverspec.sh
    amazon-ebs: /usr/bin/ruby2.0 -I/usr/local/share/ruby/gems/2.0/gems/rspec-support-3.5.0/lib:/usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/lib /usr/local/share/ruby/gems/2.0/gems/rspec-core-3.5.1/exe/rspec --pattern spec/10.0.11.66/\*_spec.rb
    amazon-ebs:
    amazon-ebs: Package "httpd"
    amazon-ebs: should be installed
    amazon-ebs:
    amazon-ebs: Service "httpd"
    amazon-ebs: should be enabled
    amazon-ebs: should be running
    amazon-ebs:
    amazon-ebs: Port "80"
    amazon-ebs: should be listening
    amazon-ebs:
    amazon-ebs: Finished in 0.31962 seconds (files took 0.29783 seconds to load)
    amazon-ebs: 4 examples, 0 failures
    amazon-ebs:
==> amazon-ebs: Stopping the source instance...
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: TEST-AMI-dev001
    amazon-ebs: AMI: ami-274ec330
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Modifying attributes on AMI (ami-274ec330)...
    amazon-ebs: Modifying: description
==> amazon-ebs: Adding tags to AMI (ami-274ec330)...
    amazon-ebs: Adding tag: "Environment": "develop"
    amazon-ebs: Adding tag: "Name": "dev001"
==> amazon-ebs: Tagging snapshot: snap-fe5ac41c
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:

us-east-1: ami-274ec330

これで、Ansible で作ったEC2インスタンスを Serverspec でチェックし、OKならAMIを作成するようになりました。

なお、Packerは、Ansible や Serverspec でエラーになると、AMIは作成せずに、EC2インスタンスをターミネートして終了します。