今回は、以下の手順で、作業することにします。
- AmazonLinux のAMIからEC2インスタンスを起動
- EC2インスタンスにSSHログイン
- ApacheHTTPをインストール
- httpサービスの起動確認
- EC2インスタンスを停止
- AMIを作成
- EC2インスタンスをターミネート
手作業だと、AWSコンソールや Teraterm を使うことになりますが、
これを、Packer+Ansible+Serverspec で自動化し、Packerのコマンド1回で済むようにしてみます。
これを、Packer+Ansible+Serverspec で自動化し、Packerのコマンド1回で済むようにしてみます。
Packerで1~7までの作業を実施しますが、
3では、Ansible のプレイブックを実行し、4では、Serverspec を実行します。
1.作業用のサーバを準備する
Packer+Ansible+Serverspecを実行するサーバを用意します。
今回は、AmazonLinuxを使用します。
なお、全ての作業は、ec2-user ユーザで行い、~/workspace ディレクトリを作って、そこに、Packer、Ansible、Serverspec のファイルを置くことにします。
なお、全ての作業は、ec2-user ユーザで行い、~/workspace ディレクトリを作って、そこに、Packer、Ansible、Serverspec のファイルを置くことにします。
1.1.作業用のEC2インスタンスを作成
今回、作業用に使用するEC2インスタンスには、管理者権限を付与したIAMロールを割り当てます。
これにより、Packer でAMIを作成するときに、アクセスキーの設定が不要になります。
これにより、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-10be3307Packer実行中は、下図のように "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 $retaws-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
なお、Packerは、Ansible や Serverspec でエラーになると、AMIは作成せずに、EC2インスタンスをターミネートして終了します。