2016年12月29日木曜日

LibreOffice(calc) マクロとTeraTermマクロを使って簡易リリースツールを作ってみる


WindowsでLibreOffice(calc)BasicマクロとTeraTermマクロを使用した、リリースツールを作ってみます。

リリース先は CentOS6.7です。
Windows側で今回使用するソフトウェアのバージョンは以下のとおり。
Windows10
TeraTerm4.93
LibeOffice5.1.5.2

リリースするファイルと配布先のIPアドレスなどの情報は、CSVファイルに記載することにします。
このCSVファイルを、LibreOfficeで作成し、TeraTermマクロで入力して、ファイル送信します。

TeraTermマクロの仕様は以下のとおり。
TeraTermマクロでファイルを送信するには、サーバ側に openssh-clients パッケージがインストールされている必要があります。
また、文字コード変換には nkf を使用します。
  • CSVファイルを入力する
  • 指示されたサーバにログインする
  • ファイル種別により以下の処理を行う
    • "txt" の場合
      ファイルを送信する
      オーナ、パーミションを設定する
      文字コードをUTF-8、改行コードをLFに変換する
    • "bin" の場合
      ファイルを送信する
      オーナ、パーミションを設定する
    • "cmd" の場合
      指示されたコマンドを実行する
  • デスクトップにTeraTermログファイルを出力する
LibreOffice(calc)Basicマクロの仕様は以下のとおり。
  • CSVファイルを作成する
  • IPアドレス(A列)と送信元ファイル名(B列)でソートする
  • 上記TeraTermマクロを実行する
CSVファイルの項目は以下のとおり
  • IPアドレス
  • 送信元ファイルPATH (txt|bin) または コマンド実行順序(cmd)
  • 送信先ファイルPATH (txt|bin) または コマンド(cmd)
  • オーナ
  • パーミション
  • 種別
    • txt : テキストファイル
    • bin : バイナリファイル
    • cmd : コマンド
  • 備考

1.TeraTermマクロ


マクロは下記PATHに保存します。
C:\workspace\LibreOfficeマクロ\rel.ttl

CSVファイルは下記PATHに格納されるものとします。
C:\workspace\LibreOfficeマクロ\sample.csv

配布先のサーバには、"root" でログインします
マクロのソースコードは以下のとおり。

;
; リリース
;
; [注意]
; サーバ側に下記パッケージがインストール済みであること
;  - openssh-clients
;  - nkf

; SSH接続アカウント情報
remote_prompt = '#'
remote_user = 'root'
remote_pass = 'p@ssw0rd'

; リリースするファイルのリスト
csvfile = 'C:\workspace\LibreOfficeマクロ\sample.csv'
separator = ','
 
; 読み取り専用でCSVファイルを開く
fileopen fh csvfile 0 0

; 1行読み飛ばし
filereadln fh buf

old_ip = '0'
while 1
    ; 1 行入力
    filereadln fh buf
    if result then
        break
    endif

    ; フィールド入力
    strsplit buf separator
    ip = groupmatchstr1
    src = groupmatchstr2
    dst = groupmatchstr3
    owner = groupmatchstr4
    mode = groupmatchstr5
    type = groupmatchstr6

    ; ログイン
    strcompare old_ip ip
    if result != 0 then

        ; IPが変わったら接続中のTerminalを ログアウト
        strcompare old_ip '0'
        if result != 0 then
            sendln 'exit'
            unlink
        endif

        ; ログイン
        sprintf2 conbuf '%s /ssh /auth=password /user=%s /passwd=%s' ip remote_user remote_pass
        connect conbuf
        if result != 2 then
            messagebox 'teraterm macro could not connect to this Server.' 'Error'
            end 
        endif

        ; ログイン待ち
        timeout = 3
        wait remote_prompt
        if result == 0 then 
            disconnect 0 
            end 
        endif
        old_ip = ip

        ; ログファイル
        getspecialfolder log_path 'Desktop'
        getdate log_name '\&h_%Y%m%d_%H%M%S.log'
        strconcat log_path log_name
        logopen log_path 0 1

        ; 接続先情報
        sendln 'date ; uname -n ; whoami'
        wait remote_prompt

    endif

    strcompare type 'cmd'
    if result == 0 then

        sendln dst
        wait remote_prompt

    else

        ; ローカルからリモートへファイル送信
        scpsend src dst

        ; 送信完了まで待機. 徐々に pause 時間を増やす
        n = 1
        while 1
            msec = 800 * n
            mpause msec
            sprintf2 cmdbuf 'ps -ef |grep -v grep |egrep -c "scp .* %s"' dst
            flushrecv
            sendln cmdbuf
            waitln '0' '1'
            if result == 1 then
                break
            endif
            n = n + 1
        endwhile

        ; オーナ設定
        sprintf2 cmdbuf 'chown %s %s' owner dst
        sendln cmdbuf
        wait remote_prompt

        ; パーミション設定
        sprintf2 cmdbuf 'chmod %s %s' mode dst
        sendln cmdbuf
        wait remote_prompt

        ; UTF8 & LF 変換
        strcompare type 'txt'
        if result == 0 then
            sprintf2 cmdbuf 'nkf --overwrite -w -Lu %s' dst
            sendln cmdbuf
            wait remote_prompt
        endif

        ; ファイル情報
        sprintf2 cmdbuf 'ls -l %s ; file %s' dst dst
        sendln cmdbuf
        wait remote_prompt

    endif

endwhile

; ログアウト
strcompare old_ip '0'
if result != 0 then
    sendln 'exit'
    unlink
endif

; CSVファイルクローズ
fileclose fh

end

2.LibreOffice(calc)Basicマクロ


マクロのソースコードは以下のとおり
LibreOffice(calc) > [ツール] > [マクロ] > [マクロの管理] > [LibreOffice Basic] を選択して保存します。
Sub ReleaseStart

    ' 最終行
    Dim oSheet as object
    Dim oCursor as Object
    Dim oRange As Object
    Dim oEndRow as Long
    oSheet = ThisComponent.getSheets().getByIndex(0)
    ThisComponent.CurrentController.setActiveSheet (oSheet)
    oRange = oSheet.getCellRangeByName("A2")
    oCursor = oSheet.createCursorByRange(oRange)
    oCursor.gotoEndOfUsedArea(True)
    oEndRow  =  oCursor.Rows.Count + 1
    
    ' ソート範囲
    Dim sort_info(1) As New com.sun.star.beans.PropertyValue
    Dim sort_fields(1) As New com.sun.star.util.SortField
    oRange = oSheet.getCellRangeByName("A1:G" & oEndRow )
    
    ' ソートキーの列を指定
    sort_fields(0).Field = 0            ' 列番号,0始まり
    sort_fields(0).SortAscending = True ' ASCかどうか
    sort_fields(1).Field = 1            ' 列番号,0始まり
    sort_fields(1).SortAscending = True ' ASCかどうか
    sort_info(0).Name = "SortFields"
    sort_info(0).Value = sort_fields()

    ' ソート範囲に列のヘッダを含むかどうか
    sort_info(1).Name = "ContainsHeader"
    sort_info(1).Value = True

    ' ソート範囲を選択してソート実行
    ThisComponent.getCurrentController().select( oRange )
    oRange.sort( sort_info() )

    ' CSVファイルに保存
    Dim sFilename as String
    Dim args(1) As New com.sun.star.beans.PropertyValue
    sFilename = "C:\workspace\LibreOfficeマクロ\sample.csv"
    args(0).Name = "FilterName"
    args(0).Value = "Text - txt - csv (StarCalc)"
    args(1).Name = "FilterOptions"
    args(1).Value = "44,34,60,1,,0,false,true,true,false"
    ThisComponent.storeToURL(ConvertToUrl(sFilename), args())

    ' ttマクロを実行
    Dim cmd as String    
    cmd = "c:\workspace\teraterm-4.93\ttpmacro.exe /v c:\workspace\LibreOfficeマクロ\rel.ttl"
    shell(cmd,0)

End Sub

3.LibreOffice(calc)にリリース情報登録


下図のように、LibreOffice(calc)の一番左側のシートに、リリース情報を登録します。



4.実行


LibreOffice(calc)の [ツール] > [マクロ] > [マクロを実行] を選択して、上記のマクロを実行します。
実行中は、下図のように、TeraTermのウインドウが表示されます。
また、ファイル転送のたびに、scp のダイアログが表示されます。



TeraTermのログファイルが、IPアドレス単位にデスクトップに出力されます。








2016年12月26日月曜日

LibreOffice(calc) のマクロでAMI一覧を作る


下記の記事で、aws-cli で取得したJSONデータを、LibreOffice(calc)に読み込むことができました。

ここでは、Basicマクロでヘッダと罫線を作成してAMI一覧を完成させます。

LibreOffice(calc)のBasicマクロからPythonマクロを実行する」で作成したマクロを修正して、以下のようにします。

REM  *****  BASIC  *****

Sub Main

    ' JSONデータ入力. Pythonマクロ(TestScript.py の amiList)を実行
    Dim a(0),b(0),c(0) As Variant
    scpr = ThisComponent.getScriptProvider
    scmod = scpr.getScript("vnd.sun.star.script:TestScript.py$amiList?language=Python&location=user")
    ret = scmod.invoke(a,b,c)
    
    'ヘッダ
    Dim oDoc as Object, oSheet as Object
    oDoc = ThisComponent
    oSheet = oDoc.CurrentController.ActiveSheet
    oSheet.getCellRangeByName("A1").String = "入力位置"
    oSheet.getCellRangeByName("B1").String = "VirtualizationType"
    oSheet.getCellRangeByName("C1").String = "Name"
    oSheet.getCellRangeByName("D1").String = "Hypervisor"
    oSheet.getCellRangeByName("E1").String = "ImageId"
    oSheet.getCellRangeByName("F1").String = "RootDeviceType"
    oSheet.getCellRangeByName("G1").String = "Architecture"
    oSheet.getCellRangeByName("H1").String = "[RootDevice]" & Chr$(10) & "Name"
    oSheet.getCellRangeByName("I1").String = "[RootDevice]" & Chr$(10) & "DeleteOnTermination"
    oSheet.getCellRangeByName("J1").String = "[RootDevice]" & Chr$(10) & "VolumeSize"
    oSheet.getCellRangeByName("K1").String = "[RootDevice]" & Chr$(10) & "VolumeType"
    oSheet.getCellRangeByName("L1").String = "[RootDevice]" & Chr$(10) & "Encrypted"

    ' ヘッダ属性
    oSheet.getCellRangeByName("A1:L1").CellBackColor = RGB(0, 0, 100)
    oSheet.getCellRangeByName("A1:L1").CharColor = RGB(255, 255, 255)
    oSheet.getCellRangeByName("A1:L1").VertJustify = com.sun.star.table.CellVertJustify.TOP

    ' セル高さ&幅の最適化
    oSheet.getRows.OptimalHeight = True
    oSheet.getColumns.OptimalWidth = True

    '最終行
    Dim oCursor as Object
    Dim oRange As Object
    Dim oEndRow as Long
    oRange = oSheet.getCellRangeByName("A2")
    oCursor = oSheet.createCursorByRange(oRange)
    oCursor.gotoEndOfUsedArea(True)
    oEndRow  =  oCursor.Rows.Count + 1
    'msgbox(oEndRow,0,"最終行取得")
   
    '罫線
    Dim oCtrl as Object
    Dim oSelRange as Object, oCellRange as Object
    Dim oBorder1 as Object, oBorder2 as Object, oBorder3 as Object, oBorder4 as Object
    oCtrl = oDoc.getCurrentController()
    oSelRange = oCtrl.getActiveSheet().getCellRangeByName("A1:L" & oEndRow) 
    oCtrl.select( oSelRange )
    '
    oCellRange = oDoc.CurrentSelection(0)
    ' Border1 Property
    oBorder1 = CreateUnoStruct("com.sun.star.table.BorderLine2")
    oBorder1.Color = RGB(0, 0, 0)
    oBorder1.LineWidth = 1
    oBorder1.LineStyle = 2  
    ' Border2 Property
    oBorder2 = CreateUnoStruct("com.sun.star.table.BorderLine2")
    oBorder2.Color = RGB(0, 0, 0)
    oBorder2.LineWidth = 1
    oBorder2.LineStyle = 2
    ' Border3 Property
    oBorder3 = CreateUnoStruct("com.sun.star.table.BorderLine2")
    oBorder3.Color = RGB(0, 0, 0)
    oBorder3.LineWidth = 1
    oBorder3.LineStyle = 2
    ' Border4 Property
    oBorder4 = CreateUnoStruct("com.sun.star.table.BorderLine2")
    oBorder4.Color = RGB(0, 0, 0)
    oBorder4.LineWidth = 1
    oBorder4.LineStyle = 2
    ' Set Border
    oCellRange.BottomBorder = oBorder1
    oCellRange.TopBorder = oBorder2
    oCellRange.LeftBorder = oBorder3
    oCellRange.RightBorder = oBorder4
    '
    oSelRange = oCtrl.getActiveSheet().getCellRangeByName( "A1" ) 
    oCtrl.select( oSelRange )

End Sub


このマクロを実行すると、JSONデータを入力して、以下のようなAMI一覧が作成されます。






2016年12月25日日曜日

LibreOffice(calc)のBasicマクロからPythonマクロを実行する


下記URLによると、LibreOffice のBasicマクロから、Python マクロを実行できるようです

そこで、「awscliのdescribeで取得したjsonデータをLibreOffice(calc)に読み込むPythonマクロ」で作成したPythonマクロをBasicマクロから実行してみました。


1.Python マクロの修正


awscliのdescribeで取得したjsonデータをLibreOffice(calc)に読み込むPythonマクロ」で作成したPythonマクロを以下のように修正します。(赤字部分)

#coding:utf-8
import json

def amiList(*args):


2.Basicマクロの作成


BASICマクロを作成します。
Python マクロを実行するソースコードは以下のとおり。
赤字部分が、スクリプトファイル名です。
緑色部分が、関数名です。

Sub Main

    ' JSONデータ入力. Pythonマクロ(TestScript.py の amiList)を実行
    Dim a(0),b(0),c(0) As Variant
    scpr = ThisComponent.getScriptProvider
    scmod = scpr.getScript("vnd.sun.star.script:TestScript.py$amiList?language=Python&location=user")
    ret = scmod.invoke(a,b,c)

End Sub

このソースを LibreOffice に登録します。
LibreOffice(calc) を起動して、[ツール] > [マクロ] > [マクロの管理] > [LibreOffice Basic] を選択すると下図の画面が表示されます。
下図のように赤枠を選択して、登録先を選択し、[編集]ボタンをクリックします。



下図の画面が表示されたら、ソースコードを記載して保存します。




3.Basicマクロの実行


Basic マクロの実行には、JRE が必要です。
[ツール] > [オプション] を選択すると、下図の画面が表示されます。
[詳細] をクリックして、JRE を指定します。



Basicマクロを実行します。
[ツール] > [マクロ] > [マクロの実行] を選択すると下図の画面が表示されます。
下図の赤枠のように選択して [実行] ボタンをクリックします。



BasicマクロからPythonマクロが実行され、以下のように Jsonデータを読み込めました




Basicマクロに比べて、Pythonマクロは、情報が少なくて、セルの操作などえ使用法がわからないことが多かったのですが、
これで、Jsonデータの入力だけ、Pythonマクロで行い、セルの操作などは、Basicマクロで実施できるようになりました。



2016年12月23日金曜日

awscliのdescribeで取得したjsonデータをLibreOffice(calc)に読み込むPythonマクロ


下記URLで LibreOffice(calc) で Python マクロが使えるのを知りました。

そこで、
awscli の describe した json データを LibreOffice に読み込んでみることにしました。
LibreOffice は Windows10 にインストールしたものを使用します。
LibreOffice のバージョンは 5.1.5 です。
Python は LibreOffice同梱のもを使用します。

1.JSONデータの準備


まず、試験用にAMIの一覧を describe して、json データを取得します。
全部だと多いので、 AMI名に "centos6" を含むAMIだけに絞り込みます。

$ aws ec2 describe-images --filters 'Names=name,Values=centos6' --output json > describe-images.json

この json ファイルを、LibreOfficeがインストールされた Windowsマシン上に保管します。

2. Python マクロの準備


上記のJSONデータを入力して LibreOffice(calc)で一覧を作成するマクロを用意します。
JSONデータは、"C:\data\describe^images.json" に格納されていることとします。
一覧を作成するシートの名称は、"Sheet1" とします。
ソースコードは以下のとおり。

#coding:utf-8
import json

def amiList():

 f = open(r"C:\data\describe-images.json","r")
 data = json.load(f)
 f.close()

 doc = XSCRIPTCONTEXT.getDocument()
 sheet = doc.getSheets().getByName('Sheet1')

 for i,e in enumerate(data['Images']):
  p = i + 2
  sheet.getCellRangeByName('A'+str(p)).Value = i + 1
  sheet.getCellRangeByName('B'+str(p)).String = str(e['VirtualizationType'])
  sheet.getCellRangeByName('C'+str(p)).String = str(e['Name'])
  sheet.getCellRangeByName('D'+str(p)).String = str(e['Hypervisor'])
  sheet.getCellRangeByName('E'+str(p)).String = str(e['ImageId'])
  sheet.getCellRangeByName('F'+str(p)).String = str(e['RootDeviceType'])
  sheet.getCellRangeByName('G'+str(p)).String = str(e['Architecture'])
  sheet.getCellRangeByName('H'+str(p)).String = str(e['BlockDeviceMappings'][0]['DeviceName'])
  sheet.getCellRangeByName('I'+str(p)).String = str(e['BlockDeviceMappings'][0]['Ebs']['DeleteOnTermination'])
  sheet.getCellRangeByName('J'+str(p)).String = str(e['BlockDeviceMappings'][0]['Ebs']['VolumeSize'])
  sheet.getCellRangeByName('K'+str(p)).String = str(e['BlockDeviceMappings'][0]['Ebs']['VolumeType'])
  sheet.getCellRangeByName('L'+str(p)).String = str(e['BlockDeviceMappings'][0]['Ebs']['Encrypted'])
このソースをLibreOfficeから実行するには、ファイルに保存してPythonマクロ用のフォルダに格納します。
C:\Users\<ユーザ名>\AppData\Roaming\LibreOffice\4\user\Scripts\python
以下のファイル名で保存します。
C:\Users\<ユーザ名>\AppData\Roaming\LibreOffice\4\user\Scripts\python\TestScript.py

3. Python マクロの実行


LibreOffice(calc) を起動します。
[ツール] > [マクロ] > [マクロの実行] を選択します。



下図の[マクロセレクター]が表示されます。
[マクロ] をクリックして、展開します。



ライブラリ欄の[TestScript]を選択すると、マクロ名欄に [amiList]が表示されるので選択して、[実行]ボタンをクリックします。



下図のように JSONデータが一覧表示されます。




エクセルのマクロで Jsonデータを入力したい場合は、下記URLが参考になります。






2016年11月19日土曜日

aws-cli で「削除保護」を解除してEC2インスタンスを削除する


削除保護されたインスタンスを削除しようとすると、以下のようにエラーになります。

[root@centos6 ~]# aws ec2 terminate-instances --instance-ids i-bca3812f

An error occurred (OperationNotPermitted) when calling the TerminateInstances operation: The instance 'i-bca3812f' may not be terminated. Modify its 'disableApiTermination' instance attribute and try again.

そこで、以下のようにして、「削除保護」を無効にします。

[root@centos6 ~]# aws ec2 modify-instance-attribute --instance-id i-bca3812f --no-disable-api-termination

これで、以下のようにインスタンスが削除できます。

[root@centos6 ~]# aws ec2 terminate-instances --instance-ids i-bca3812f
-------------------------------
|     TerminateInstances      |
+-----------------------------+
||   TerminatingInstances    ||
|+---------------------------+|
||        InstanceId         ||
|+---------------------------+|
||  i-bca3812f               ||
|+---------------------------+|
|||      CurrentState       |||
||+-------+-----------------+||
||| Code  |      Name       |||
||+-------+-----------------+||
|||  32   |  shutting-down  |||
||+-------+-----------------+||
|||      PreviousState      |||
||+---------+---------------+||
|||  Code   |     Name      |||
||+---------+---------------+||
|||  16     |  running      |||
||+---------+---------------+||


aws-cli を使ってインスタンスIDからボリュームを探す


あるインスタンスIDで使用しているボリュームを aws-cli で探します。

[root@centos6 ~]# aws ec2 describe-volumes --filters "Name=attachment.instance-id,Values=i-bca3812f" --output table
---------------------------------------------------------
|                    DescribeVolumes                    |
+-------------------------------------------------------+
||                       Volumes                       ||
|+---------------------+-------------------------------+|
||  AvailabilityZone   |  us-east-1b                   ||
||  CreateTime         |  2016-11-19T06:41:58.265Z     ||
||  Encrypted          |  False                        ||
||  Iops               |  100                          ||
||  Size               |  8                            ||
||  SnapshotId         |  snap-9dc0730b                ||
||  State              |  in-use                       ||
||  VolumeId           |  vol-c962b558                 ||
||  VolumeType         |  gp2                          ||
|+---------------------+-------------------------------+|
|||                    Attachments                    |||
||+----------------------+----------------------------+||
|||  AttachTime          |  2016-11-19T06:41:58.000Z  |||
|||  DeleteOnTermination |  True                      |||
|||  Device              |  /dev/sda1                 |||
|||  InstanceId          |  i-bca3812f                |||
|||  State               |  attached                  |||
|||  VolumeId            |  vol-c962b558              |||
||+----------------------+----------------------------+||


aws-cli で EC2 インスタンスを作成する


aws-cli でEC2インスタンスを作ります。

まず、--dry-run を指定して、引数などに間違いがないかチェックしてみます。

[root@centos6 ~]# aws ec2 run-instances --image-id ami-1c221e76 --key-name virginia_key --security-group-ids sg-bbf176df --instance-type t2.micro --placement "AvailabilityZone=us-east-1b,GroupName=,Tenancy=default" --block-device-mappings "DeviceName=/dev/sda1,Ebs={DeleteOnTermination=true,VolumeType=gp2}" --monitoring "Enabled=false" --disable-api-termination --private-ip-address 10.0.11.10 --associate-public-ip-address --subnet-id subnet-f528a2ad --count 1 --output table --dry-run

An error occurred (DryRunOperation) when calling the RunInstances operation: Request would have succeeded, but DryRun flag is set.


次に --dry-run を外して、実行します。
EC2インスタンスの作成に成功すると、以下のように表示されます。

[root@centos6 ~]# aws ec2 run-instances --image-id ami-1c221e76 --key-name virginia_key --security-group-ids sg-bbf176df --instance-type t2.micro --placement "AvailabilityZone=us-east-1b,GroupName=,Tenancy=default" --block-device-mappings "DeviceName=/dev/sda1,Ebs={DeleteOnTermination=true,VolumeType=gp2}" --monitoring "Enabled=false" --disable-api-termination --private-ip-address 10.0.11.10 --associate-public-ip-address --subnet-id subnet-f528a2ad --count 1 --output table
-----------------------------------------------------------
|                      RunInstances                       |
+----------------------------+----------------------------+
|  OwnerId                   |  544509011205              |
|  ReservationId             |  r-2497ec97                |
+----------------------------+----------------------------+
||                       Instances                       ||
|+------------------------+------------------------------+|
||  AmiLaunchIndex        |  0                           ||
||  Architecture          |  x86_64                      ||
||  ClientToken           |                              ||
||  EbsOptimized          |  False                       ||
||  Hypervisor            |  xen                         ||
||  ImageId               |  ami-1c221e76                ||
||  InstanceId            |  i-bca3812f                  ||
||  InstanceType          |  t2.micro                    ||
||  KeyName               |  virginia_key                ||
||  LaunchTime            |  2016-11-19T06:41:57.000Z    ||
||  PrivateDnsName        |  ip-10-0-11-10.ec2.internal  ||
||  PrivateIpAddress      |  10.0.11.10                  ||
||  PublicDnsName         |                              ||
||  RootDeviceName        |  /dev/sda1                   ||
||  RootDeviceType        |  ebs                         ||
||  SourceDestCheck       |  True                        ||
||  StateTransitionReason |                              ||
||  SubnetId              |  subnet-f528a2ad             ||
||  VirtualizationType    |  hvm                         ||
||  VpcId                 |  vpc-d480c3b1                ||
|+------------------------+------------------------------+|
|||                     Monitoring                      |||
||+----------------------+------------------------------+||
|||  State               |  disabled                    |||
||+----------------------+------------------------------+||
|||                  NetworkInterfaces                  |||
||+---------------------+-------------------------------+||
|||  Description        |                               |||
|||  MacAddress         |  0e:99:1e:0e:11:6a            |||
|||  NetworkInterfaceId |  eni-f27d1233                 |||
|||  OwnerId            |  544509011205                 |||
|||  PrivateDnsName     |  ip-10-0-11-10.ec2.internal   |||
|||  PrivateIpAddress   |  10.0.11.10                   |||
|||  SourceDestCheck    |  True                         |||
|||  Status             |  in-use                       |||
|||  SubnetId           |  subnet-f528a2ad              |||
|||  VpcId              |  vpc-d480c3b1                 |||
||+---------------------+-------------------------------+||
||||                    Attachment                     ||||
|||+----------------------+----------------------------+|||
||||  AttachTime          |  2016-11-19T06:41:57.000Z  ||||
||||  AttachmentId        |  eni-attach-ab648822       ||||
||||  DeleteOnTermination |  True                      ||||
||||  DeviceIndex         |  0                         ||||
||||  Status              |  attaching                 ||||
|||+----------------------+----------------------------+|||
||||                      Groups                       ||||
|||+-----------------------+---------------------------+|||
||||  GroupId              |  sg-bbf176df              ||||
||||  GroupName            |  default                  ||||
|||+-----------------------+---------------------------+|||
||||                PrivateIpAddresses                 ||||
|||+-------------------+-------------------------------+|||
||||  Primary          |  True                         ||||
||||  PrivateDnsName   |  ip-10-0-11-10.ec2.internal   ||||
||||  PrivateIpAddress |  10.0.11.10                   ||||
|||+-------------------+-------------------------------+|||
|||                      Placement                      |||
||+------------------------------+----------------------+||
|||  AvailabilityZone            |  us-east-1b          |||
|||  GroupName                   |                      |||
|||  Tenancy                     |  default             |||
||+------------------------------+----------------------+||
|||                   SecurityGroups                    |||
||+------------------------+----------------------------+||
|||  GroupId               |  sg-bbf176df               |||
|||  GroupName             |  default                   |||
||+------------------------+----------------------------+||
|||                        State                        |||
||+---------------------+-------------------------------+||
|||  Code               |  0                            |||
|||  Name               |  pending                      |||
||+---------------------+-------------------------------+||
|||                     StateReason                     |||
||+-------------------------+---------------------------+||
|||  Code                   |  pending                  |||
|||  Message                |  pending                  |||
||+-------------------------+---------------------------+||


2016年11月18日金曜日

aws-cli で Volume の Delete On Termination を操作する


EC2インスタンスを作成するときに、Delete On Termination を有効したい場合は、以下のように実行する。
aws ec2 run-instances --image-id ami-12345678 --count 1 --instance-type t2.micro --key-name default --security-groups test_group --block-device-mappings "[{\"DeviceName\": \"/dev/sda1\",\"Ebs\":{\"DeleteOnTermination\": \"true\"}}]"


EC2インスタンスを作成した後で有効したい場合は、以下のように実行する。
aws ec2 modify-instance-attribute --instance-id i-12345678 --block-device-mappings "[{\"DeviceName\": \"/dev/sda1\",\"Ebs\":{\"DeleteOnTermination\": true}}]"


2016年11月14日月曜日

root が使用できないので ssh と sudo でファイル転送したい


root が使用できない環境で、root がオーナのファイルを転送したい場合、よくやるのが以下の手順です。

  1. 一時的に /tmp などにファイルを scp する
  2. ssh で sudo cp する
  3. 上記1で/tmp に置いたファイルを削除する

これは、面倒です。。

以下のように ssh に標準入力経由でローカルファイルのデータを渡し、
sudo で root 権限を使用して dd コマンドで処理すると、
ローカルのファイルを1回でリモートサーバにファイル転送できます。

ssh user@server "sudo dd of=/etc/hosts" < my_hosts

リモートサーバのファイルをローカルに転送したい場合は、以下のとおり。

ssh user@server "sudo dd if=/etc/hosts" > hosts.backup

dd のかわりに、tar コマンドを使用して、リモートサーバのファイルを、直接ローカルにアーカイブすることもできます。

ssh user@server "sudo tar cvfz - /etc" > etc.tgz




2016年11月11日金曜日

ssh で sudo したいが、sudoers は変更できない場合は


"-t" を複数つけて強制的に仮想端末を割り当ててssh を実行します

ssh -t -t user@server "sudo cat /etc/hosts"

2016年11月10日木曜日

while ループの Here Document で ssh を使うと1回しかループしない


以下のようにして、複数のサーバにコマンドを実行しようとすると1回しかループしない。

while read ip
do
    ssh user@${ip} 'cat /etc/hosts'
done<<END
10.1.0.11
10.1.0.12
10.1.0.13
END

これは、ssh を実行することで 標準入力が切り替わることが影響しているらしい。

以下のように -n をつけると、3回ループして、期待どおりに動いた。

while read ip
do
    ssh -n user@${ip} 'cat /etc/hosts'
done<<END
10.1.0.11
10.1.0.12
10.1.0.13
END

2016年9月14日水曜日

[AWS] IAM Role で aws-cli を実行する場合、プロキシ経由は注意が必要


IAM Role で aws-cli を実行する場合、プロキシを利用するときには、以下のプロキシ除外設定が必要。

IAM Roleを利用する場合、aws-cliはインスタンスメタデータを利用するらしいので、プロキシ経由で169.254.169.254 にアクセスすると失敗するらしい。 


□ マニュアルからの抜粋

Using a proxy on EC2 Instances

If you configure a proxy on an ec2 instance launched with an IAM role, you should also set the NO_PROXY environment variable with the IP address 169.254.169.254, so that the AWS CLI can access the Instance Meta Data Service (IMDS).
Linux, OS X, or Unix
$ export NO_PROXY=169.254.169.254



2016年8月20日土曜日

[AWS] aws-cli は時刻がずれていると動かない


aws-cli は、現在時刻とずれてると、エラーになります。

試しに、ntpd を停止して、時刻を過去に設定して、aws コマンドを実行すると、以下のようなエラーになります。

[root@localhost ~]# service ntpd stop
ntpd を停止中:                                             [  OK  ]
[root@localhost ~]# date
2016年  8月 20日 土曜日 11:00:51 JST
[root@localhost ~]# aws ec2 describe-instances
-------------------
|DescribeInstances|
+-----------------+
[root@localhost ~]# date -s "08/20 10:00 2016"
2016年  8月 20日 土曜日 10:00:00 JST
[root@localhost ~]# date
2016年  8月 20日 土曜日 10:00:06 JST
[root@localhost ~]# aws ec2 describe-instances

An error occurred (AuthFailure) when calling the DescribeInstances operation: AWS was not able to validate the provided access credentials
[root@localhost ~]#

時間が、ずれていると認証に失敗するようです。



2016年8月11日木曜日

[CentOS6][Ansible] include_vars で外部ファイルに定義した変数を読み込む


Ansible のプレイブックで使用する変数を、個別のファイルに定義して、必要に応じて include_vars で読み込むことができます。

例えば、以下の2種類の変数ファイルを用意してパッケージをインストールしてみます。
demo1.yml は 「demo1サーバ用」、demo2.yml は「demo2サーバ用」とします。

[root@localhost workspace]# cat ./demo1.yml
---
pkg_names:
  - telnet
  - httpd
[root@localhost workspace]# cat ./demo2.yml
---
pkg_names:
  - mysql
  - tcpdump
[root@localhost workspace]#

プレイブックは以下のとおり。
「include_vars で変数ファイルを読み込むタスク」と、「yum でパッケージをインストールするタスク」があります。
なお、変数の TYPE の値は ansible を実行するときに引数で渡すことにします。

---
- hosts: all
  remote_user: root
  become: false
  vars:
  tasks:

    - name: 変数の入力
      include_vars: '{{TYPE}}.yml'

    - name: パッケージのインストール
      yum: name={{item}} state=latest
      with_items: '{{pkg_names}}'

demo1.yml を使用してプレイブックを実行すると以下のとおり。
--check をつけて自ホストでシミュレーションしてます。

[root@localhost workspace]# ansible-playbook -i ,127.0.0.1 ./test_playbook.yml --check --ask-pass -e "TYPE=demo1"
SSH password:

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

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

TASK [変数の入力] *******************************************************************
ok: [127.0.0.1]

TASK [パッケージのインストール] ************************************************************
changed: [127.0.0.1] => (item=[u'telnet', u'httpd'])

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

[root@localhost workspace]#

こんどは demo2.yml を使用するようにプレイブックを実行してみます。

[root@localhost workspace]# ansible-playbook -i ,127.0.0.1 ./test_playbook.yml --check --ask-pass -e "TYPE=demo2"
SSH password:

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

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

TASK [変数の入力] *******************************************************************
ok: [127.0.0.1]

TASK [パッケージのインストール] ************************************************************
changed: [127.0.0.1] => (item=[u'mysql', u'tcpdump'])

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

[root@localhost workspace]#



2016年8月6日土曜日

[AWS] SNS(SQS) を使用せずに AutoScaling のライフサイクルフックを使う


AutoScaling でスケールインするとき、ライフサイクルフックを試してターミネートを制御してみます。

面倒なので、今回は、SNSやSQSを使用しません。


1.AutoScalingグループを用意する


AWSコンソールで作成し、用意したAutoScalingグループは以下のとおり。
この時点では、EC2インスタンスは 0 にしています。あとは、ほとんどデフォルト設定です。
[root@localhost ~]# aws autoscaling describe-auto-scaling-groups  --auto-scaling-group-name WebGroup
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                        DescribeAutoScalingGroups                                                                       |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
||                                                                           AutoScalingGroups                                                                          ||
|+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+|
||  AutoScalingGroupARN             |  arn:aws:autoscaling:us-east-1:xxx:autoScalingGroupName/WebGroup                                                                  ||
||  AutoScalingGroupName            |  WebGroup                                                                                                                         ||
||  CreatedTime                     |  2016-07-23T23:55:45.798Z                                                                                                         ||
||  DefaultCooldown                 |  300                                                                                                                              ||
||  DesiredCapacity                 |  0                                                                                                                                ||
||  HealthCheckGracePeriod          |  300                                                                                                                              ||
||  HealthCheckType                 |  EC2                                                                                                                              ||
||  LaunchConfigurationName         |  web01-002                                                                                                                        ||
||  MaxSize                         |  1                                                                                                                                ||
||  MinSize                         |  0                                                                                                                                ||
||  NewInstancesProtectedFromScaleIn|  False                                                                                                                            ||
||  VPCZoneIdentifier               |  subnet-f528a2ad                                                                                                                  ||
|+----------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+|
|||                                                                          AvailabilityZones                                                                         |||
||+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+||
|||  us-east-1b                                                                                                                                                        |||
||+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+||
|||                                                                                Tags                                                                                |||
||+-------------------------------------------------------------------------------+------------------------------------------------------------------------------------+||
|||  Key                                                                          |  Name                                                                              |||
|||  PropagateAtLaunch                                                            |  True                                                                              |||
|||  ResourceId                                                                   |  WebGroup                                                                          |||
|||  ResourceType                                                                 |  auto-scaling-group                                                                |||
|||  Value                                                                        |  WebGroup                                                                          |||
||+-------------------------------------------------------------------------------+------------------------------------------------------------------------------------+||
|||                                                                         TerminationPolicies                                                                        |||
||+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+||
|||  Default                                                                                                                                                           |||
||+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+||


2.ライフサイクルフックを作成する


スケールイン用のライフサイクルフックを作成して、パラメータを確認します。
[root@localhost ~]# aws autoscaling put-lifecycle-hook --lifecycle-hook-name terminate-hook --auto-scaling-group-name WebGroup --lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING
[root@localhost ~]# aws autoscaling describe-lifecycle-hooks --auto-scaling-group-name WebGroup
-----------------------------------------------------------------------------------------------------------------------------------------------
|                                                           DescribeLifecycleHooks                                                            |
+---------------------------------------------------------------------------------------------------------------------------------------------+
||                                                              LifecycleHooks                                                               ||
|+----------------------+----------------+----------------+-------------------+--------------------+-----------------------------------------+|
|| AutoScalingGroupName | DefaultResult  | GlobalTimeout  | HeartbeatTimeout  | LifecycleHookName  |           LifecycleTransition           ||
|+----------------------+----------------+----------------+-------------------+--------------------+-----------------------------------------+|
||  WebGroup            |  ABANDON       |  172800        |  3600             |  terminate-hook    |  autoscaling:EC2_INSTANCE_TERMINATING   ||
|+----------------------+----------------+----------------+-------------------+--------------------+-----------------------------------------+|

ちなみに、ライフサイクルフックを削除したい場合は、以下のコマンドです。
aws autoscaling delete-lifecycle-hook --lifecycle-hook-name terminate-hook --auto-scaling-group-name WebGroup


3.ライフサイクルフックの動作確認


これで、ライフサイクルフックが使えます。
スケールインしたときに、ターミネートが止まるか確認してみます。

まず、スケールアウトしてEC2インスタンスを1台起動します。
[root@localhost ~]# aws autoscaling update-auto-scaling-group --auto-scaling-group-name WebGroup --desired-capacity 1 --min-size 1

アクティビティ履歴で、起動完了を確認します。 "i-15c9ff85" が起動されたEC2インスタンスです。
[root@localhost ~]# aws autoscaling describe-scaling-activities --auto-scaling-group-name WebGroup --query "Activities[].[StartTime,Description,StatusCode]"
----------------------------------------------------------------------------------------
|                               DescribeScalingActivities                              |
+---------------------------+--------------------------------------------+-------------+
|  2016-08-06T03:28:36.720Z |  Launching a new EC2 instance: i-15c9ff85  |  Successful |
|  2016-08-06T02:56:34.931Z |  Terminating EC2 instance: i-96dbed06      |  Successful |
|  2016-08-06T02:55:07.365Z |  Launching a new EC2 instance: i-96dbed06  |  Successful |
|  2016-08-06T02:38:21.331Z |  Terminating EC2 instance: i-e7d4e277      |  Successful |
|  2016-08-06T02:33:56.376Z |  Launching a new EC2 instance: i-e7d4e277  |  Successful |
|  2016-07-24T00:08:09.846Z |  Terminating EC2 instance: i-f80d4868      |  Successful |
|  2016-07-24T00:04:38.938Z |  Launching a new EC2 instance: i-f80d4868  |  Successful |
|  2016-07-24T00:04:06.533Z |  Terminating EC2 instance: i-40c5bfd0      |  Successful |
|  2016-07-23T23:56:02.592Z |  Launching a new EC2 instance: i-40c5bfd0  |  Successful |
+---------------------------+--------------------------------------------+-------------+

こんどは、スケールインしてみます。
[root@localhost ~]# aws autoscaling update-auto-scaling-group --auto-scaling-group-name WebGroup --desired-capacity 0 --min-size 0

しばらくして、アクティビティ履歴を確認すると、スケールインしたEC2インスタンスが、"MidTerminatingLifecycleAction" と表示されています。
この表示になったときには、ライフサイクルフックでターミネートが一時停止しています。
[root@localhost ~]# aws autoscaling describe-scaling-activities --auto-scaling-group-name WebGroup --query "Activities[].[StartTime,Description,StatusCode]"
-----------------------------------------------------------------------------------------------------------
|                                        DescribeScalingActivities                                        |
+--------------------------+--------------------------------------------+---------------------------------+
|  2016-08-06T03:36:57.926Z|  Terminating EC2 instance: i-15c9ff85      |  MidTerminatingLifecycleAction  |
|  2016-08-06T03:28:36.720Z|  Launching a new EC2 instance: i-15c9ff85  |  Successful                     |
|  2016-08-06T02:56:34.931Z|  Terminating EC2 instance: i-96dbed06      |  Successful                     |
|  2016-08-06T02:55:07.365Z|  Launching a new EC2 instance: i-96dbed06  |  Successful                     |
|  2016-08-06T02:38:21.331Z|  Terminating EC2 instance: i-e7d4e277      |  Successful                     |
|  2016-08-06T02:33:56.376Z|  Launching a new EC2 instance: i-e7d4e277  |  Successful                     |
|  2016-07-24T00:08:09.846Z|  Terminating EC2 instance: i-f80d4868      |  Successful                     |
|  2016-07-24T00:04:38.938Z|  Launching a new EC2 instance: i-f80d4868  |  Successful                     |
|  2016-07-24T00:04:06.533Z|  Terminating EC2 instance: i-40c5bfd0      |  Successful                     |
|  2016-07-23T23:56:02.592Z|  Launching a new EC2 instance: i-40c5bfd0  |  Successful                     |
+--------------------------+--------------------------------------------+---------------------------------+

このまま放置すると、デフォルトでは60分でタイムアウトして、ターミネートの一時停止が解除されます。

タイムアウトを待たずに、解除したい場合は、インスタンスIDを指定して、以下のコマンドを実行します。
[root@localhost ~]# aws autoscaling complete-lifecycle-action --lifecycle-hook-name terminate-hook --auto-scaling-group-name WebGroup --lifecycle-action-result CONTINUE --instance-id i-15c9ff85

ターミネートの一時停止を解除してから、しばらくして、アクティビティ履歴を確認すると、ターミネートが完了しているのがわかります。
[root@localhost ~]# aws autoscaling describe-scaling-activities --auto-scaling-group-name WebGroup --query "Activities[].[StartTime,Description,StatusCode]"
----------------------------------------------------------------------------------------
|                               DescribeScalingActivities                              |
+---------------------------+--------------------------------------------+-------------+
|  2016-08-06T03:36:57.926Z |  Terminating EC2 instance: i-15c9ff85      |  Successful |
|  2016-08-06T03:28:36.720Z |  Launching a new EC2 instance: i-15c9ff85  |  Successful |
|  2016-08-06T02:56:34.931Z |  Terminating EC2 instance: i-96dbed06      |  Successful |
|  2016-08-06T02:55:07.365Z |  Launching a new EC2 instance: i-96dbed06  |  Successful |
|  2016-08-06T02:38:21.331Z |  Terminating EC2 instance: i-e7d4e277      |  Successful |
|  2016-08-06T02:33:56.376Z |  Launching a new EC2 instance: i-e7d4e277  |  Successful |
|  2016-07-24T00:08:09.846Z |  Terminating EC2 instance: i-f80d4868      |  Successful |
|  2016-07-24T00:04:38.938Z |  Launching a new EC2 instance: i-f80d4868  |  Successful |
|  2016-07-24T00:04:06.533Z |  Terminating EC2 instance: i-40c5bfd0      |  Successful |
|  2016-07-23T23:56:02.592Z |  Launching a new EC2 instance: i-40c5bfd0  |  Successful |
+---------------------------+--------------------------------------------+-------------+

ライフサイクルフックで、ターミネートが一時停止するのは、OSがシャットダウンする前です。
なので、SSHでログインできるし、crontab も動いてます。

小規模環境で、SNS(SQS)をつかわず、手軽にライフサイクルフックを使用したい場合もあります。

crontab で定期的に「アクティビティ履歴」をチェックするスクリプトを、AutoScalingで使用するAMIに仕込んでおけば、スケールイン時にライフサイクルフックの発動を検知して「終了処理」を実行し、「終了処理」が完了してから、ターミネートを続行させることができます。








2016年8月2日火曜日

[AWS] aws-cli の実行結果を query 引数でソートする


aws cli のquery は、実行結果をソートできるらしい。

[root@localhost ~]# aws ec2 describe-instances --query 'sort_by(Instances[].[LaunchTime,InstanceId])'


2016年8月1日月曜日

[AWS] aws-cli で AMI と 紐付くスナップショットを削除する


AMIのタグ(Name)で、削除したいAMIを探し、AMI-ID と SNAPSHOT-ID を表示します。

[root@localhost workspace]# aws ec2 describe-images --filter "Name=tag:Name,Values=web01-002" --query="Images[].[ImageId,BlockDeviceMappings[].Ebs[].[SnapshotId]]" --output=text
ami-c177e1d6
snap-661c2288

AMI を削除します。

[root@localhost workspace]# aws ec2 deregister-image --image-id ami-c177e1d6

SNAPSHOT を削除します。

[root@localhost workspace]# aws ec2 delete-snapshot --snapshot-id snap-661c2288





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インスタンスをターミネートして終了します。