Chef & Test Kitchen+Serverspec & Docker & PackerによるInfrastructure as Code事始め

[Chef](https://www.chef.io/chef/)、[Test Kitchen](http://kitchen.ci)＋[Serverspec](http://serverspec.org)、[Docker](https://www.docker.com)、及びHashiCorp [Packer](https://www.packer.io)を活用して、Infrastructure as Codeを体験してみる。

1. Test Ketchen + Serverspecを用い、ローカルのDockerコンテナ上でcookbookのテスト実施
2. Packerを用いてcookbookからDockerイメージを生成し、cookbook適用済のDockerコンテナをローカルで起動して動作確認
3. Knife zeroを用いて、テスト済みのcookbookを他のサーバに適用

cookbookを適用する対象のOSは、Ubuntu14.04、CentOS6、CentOS7の３つ。

# 検証環境
今回の検証はAmazon Web Services Tokyo Regionで実施したが、他のクラウドやベアメタルでも同様の手順で再現できるハズ。たぶん。

* chef zero（knife zero）を動作させるWorkstationのスペック

|ITEM|SPEC|
|:--|:--|
|AMI|ami-89634988|
|TYPE|t2.small (1 vCPU, 2GiB RAM)|
|DISK|10 GiB (SSD)|

|ITEM|VERSION|
|:--|:--|
|OS|CentOS Linux release 7.1.1503 (Core) |
|Kernel|3.10.0-229.11.1.el7.x86_64|
|ChefDK|0.7.0|
|Docker|1.8.1, build d12ea79|
|Packer|0.8.6|

# 環境構築
WorkstationのOS環境の設定やミドルウェアのインストールを行う。

## tty無しのsudoを許可
Test KitchenでDockerを利用する際に必要になるため、tty無しでsudoできるようにsudoersの設定を変更する。

```text:requiretty無し
[centos@ip-10-0-0-11 ~]$ sudo sed -i -e 's/^Defaults    requiretty/Defaults   !requiretty/g' /etc/sudoers
[centos@ip-10-0-0-11 ~]$ sudo grep "requiretty" /etc/sudoers
Defaults   !requiretty
```

## パッケージインストール
各種パッケージをインストールする。

```text:パッケージインストール
[centos@ip-10-0-0-11 ~]$ sudo yum update -y
[centos@ip-10-0-0-11 ~]$ sudo yum install git wget unzip -y
```

## Dockerインストール
Dockerをインストールして起動し、Dockerサービスを自動起動するように設定する。

```text:dockerインストール
[centos@ip-10-0-0-11 ~]$ curl -sSL https://get.docker.com/ | sh
[centos@ip-10-0-0-11 ~]$ sudo usermod -aG docker centos
```

```text:Dockerサービス起動
[centos@ip-10-0-0-11 ~]$ sudo systemctl enable docker.service
[centos@ip-10-0-0-11 ~]$ sudo systemctl start docker.service
```

一度ログアウトしてcentosでログインし直すと、centosでdockerが利用できるようになる。

```text:docker確認
[centos@ip-10-0-0-11 ~]$ docker run hello-world
```

## Packerインストール
Packerをインストールし、PATHを通す。
（CentOS7には `/usr/sbin/packer` という別のpacker実行ファイルがインストールされている。誤ってこちらのpackerを利用しないように、PATHの設定に注意すること）

```text:Packerインストール
[centos@ip-10-0-0-11 ~]$ wget -O /tmp/packer.zip https://dl.bintray.com/mitchellh/packer/packer_0.8.6_linux_amd64.zip
[centos@ip-10-0-0-11 ~]$ mkdir packer
[centos@ip-10-0-0-11 ~]$ unzip /tmp/packer.zip -d ./packer
```

```text:PATH設定
[centos@ip-10-0-0-11 ~]$ echo 'export PATH=$HOME/packer:$PATH' >> ~/.bash_profile
[centos@ip-10-0-0-11 ~]$ source .bash_profile
[centos@ip-10-0-0-11 ~]$ which packer
~/packer/packer
```

## Chef Development Kitインストール
Chefを中心としたエコシステムを構成する各種ツールはRubyで書かれており、rubygemsからインストールできる。しかしRubyやそれらのgemをインストールするのはネイティブコンパイルなども絡んで結構たいへんなので、今回はChefの開発元が提供する「Chef開発ツール一式」である [Chef Development Kit](https://downloads.chef.io/chef-dk/)を利用する。

```text:ChefDKインストール
[centos@ip-10-0-0-11 ~]$ sudo yum install https://opscode-omnibus-packages.s3.amazonaws.com/el/7/x86_64/chefdk-0.7.0-1.el7.x86_64.rpm -y
```

## 作業用リポジトリの準備
Infrastructure as Codeの舞台となるChefリポジトリを作成する。

```text:chefリポジトリ作成
[centos@ip-10-0-0-11 ~]$ chef generate repo chef-repo
[centos@ip-10-0-0-11 ~]$ cd chef-repo/
```

また今回KnifeやTest Kitchenではchef zeroを用いるので、ローカルモードを用いるようにchefの設定を行う。

```text:chefローカルモードの設定
[centos@ip-10-0-0-11 chef-repo]$ mkdir .chef
[centos@ip-10-0-0-11 chef-repo]$ echo 'local_mode true' > .chef/knife.rb
```

## 足りないgemのインストール
Gemfileを作成し、Chef Development Kitには同梱されないgemをインストールする。

```text:Gemfile作成
[centos@ip-10-0-0-11 chef-repo]$ vi Gemfile
[centos@ip-10-0-0-11 chef-repo]$ chef exec bundle install
```

```ruby:Gemfile
source "https://rubygems.org"

gem "knife-zero"
gem "test-kitchen"
gem "kitchen-docker"
gem "serverspec"
```

test-kitchenとserverspecはChef Development Kitに入ってるかも。

# Cookbookの準備
今回はシンプルに、apache2とphpをパッケージからインストールしてテスト画面を表示させるrecipeを作る（chef supermarketで公開されているサードパーティのcookbookは利用しない）。

なおUbuntu14.04とCentOS6、CentOS7で動作するように、パッケージ名等の環境依存部分はattributeに追い出すことにする。

## cookbookの雛形を生成
ChefDKのchefコマンドを用いてcookbookの雛形を作成する。

```text:cookbookの雛形生成
[centos@ip-10-0-0-11 chef-repo]$ mkdir site-cookbooks
[centos@ip-10-0-0-11 chef-repo]$ chef generate cookbook site-cookbooks/testweb
[centos@ip-10-0-0-11 chef-repo]$ chef generate attribute site-cookbooks/testweb default
[centos@ip-10-0-0-11 chef-repo]$ chef generate template site-cookbooks/testweb index.php.erb
[centos@ip-10-0-0-11 chef-repo]$ touch Berksfile
```

## recipeの修正
以下の作業を行うrecipeを作成する。

1. packageのアップデート（yum update -yかapt-get update）
2. apache2とphpのインストール
3. apache2の自動起動設定と再起動
4. templateからPHPファイルを生成して配置

```ruby:site-cookbooks/testweb/recipes/default.rb
#
# Cookbook Name:: testweb
# Recipe:: default
#
# Copyright (c) 2015 The Authors, All Rights Reserved.

execute "package update" do
  user    "root"
  command node["pkg"]["update"]
  action  :run
end

[node["pkg"]["httpd"], node["pkg"]["php"]].each do |pkg|
  package pkg do
    action :install
  end
end

service node["pkg"]["httpd"] do
  action [:enable, :restart]
end

template "#{node["web"]["docroot"]}/index.php" do
  source "index.php.erb"
  owner  "root"
  group  "root"
  mode   0644
  variables(
    :platform => node["platform"],
    :version  => node["platform_version"]
  )
end
```

## attributeの修正
recipeが利用する変数を定義する。OSごとに値の変わるパッケージ名は、Chefが収集したOS情報を参照して適切な値を設定する。

```ruby:site-cookbooks/testweb/attributes/default.rb
default["web"]["docroot"] = "/var/www/html"

case node["platform_family"]
when "rhel", "fedora"
  default["pkg"]["update"] = "yum update -y"
  default["pkg"]["httpd"]  = "httpd"
  default["pkg"]["php"]    = "php"
when "debian"
  default["pkg"]["update"] = "apt-get update -y"
  default["pkg"]["httpd"]  = "apache2"
  default["pkg"]["php"]    = "php5"
else
  raise
end
```

## templateの修正
Apache2から呼び出されるPHPファイルの元となるtemplateを作成する。**<%= ... %>** の部分は、recipeから与えられた変数で置き換えられる。

```html+erb:site-cookbooks/testweb/templates/default/index.php.erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : <%= @platform %> <%= @version %></p>
  <p>このサーバのIPアドレス : <?php echo $_SERVER['SERVER_ADDR']; ?></p>
</body>
</html>
```

## cookbookの依存関係の解決
今回はサードパーティのcookbookを利用していないが、自作したcookbookの "testweb" も含めBerkshelfを用いてcookbookの依存関係を解決する。

```text:cookbookの依存関係の解決
[centos@ip-10-0-0-11 chef-repo]$ vi Berksfile
[centos@ip-10-0-0-11 chef-repo]$ berks vendor cookbooks
```

```ruby:Berksfile
source "https://supermarket.chef.io"

cookbook "testweb", path: "./site-cookbooks/testweb"
```

# Test Kitchen + Serverspecの準備
上記で作成したcookbook "testweb" をDockerを用いてテストするためのTest Kitchen + Serverspec環境を準備する

## Test Kitchenの雛形作成
まずTest Kitchenの雛形を作成する。chefignoreがconflictした、と言ってくるが、さくっと無視して構わない。

```text:TestKitchen雛形作成
[centos@ip-10-0-0-11 chef-repo]$ kitchen init --driver=kitchen-docker
      create  .kitchen.yml
    conflict  chefignore
Overwrite /home/centos/chef-repo/chefignore? (enter "h" for help) [Ynaqdh] n
        skip  chefignore
      create  test/integration/default
      append  .gitignore
      append  .gitignore
```

## Test Kitchen用のCentOS7系Dockerイメージ生成
CentOS7ではinitdがsystemdに変わり、かつDockerHubにあるCentOS7イメージには `/sbin/service` が含まれていない。そのためCentOS7は、Ubuntu14.04やCentOS6のように簡単にTest Kitchenから利用することができない。

そこで[2k0ri](http://qiita.com/2k0ri)さんの[kitchen-dockerで動くCentOS7 Dockerコンテナ](http://qiita.com/2k0ri/items/78efaca90ee0f9c0b286)を参考に、`initscripts` をインストール済みのCentOS7イメージを作る。

```text:Dockerイメージ作成
[centos@ip-10-0-0-11 chef-repo]$ mkdir docker
[centos@ip-10-0-0-11 chef-repo]$ cd docker
[centos@ip-10-0-0-11 docker]$ vi Dockerfile
[centos@ip-10-0-0-11 docker]$ docker build -t testweb/centos7 .
[centos@ip-10-0-0-11 docker]$ cd ..
```

```text:docker/Dockerfile
FROM centos:centos7

RUN yum install -y initscripts
```

## Test Kitchenの設定ファイル作成
Test Kitchenの設定ファイル `.kitchen.yml` を修正する。修正ポイントは以下の３つ。

* chef soloではなくchef zeroを使うようにprovisionerを変更
* Ubuntu、CentOS6、CentOS7でテストするようにplatformsを変更
    * CentOS7では、先ほど作成した `initscripts` インストール済みのイメージを、特権モードかつ起動コマンドを `/sbin/init` にして使用する
* 先ほど作ったcookbokを利用するようにrun_listを変更

```yaml:.kitchen.yml
---
driver:
  name: docker

provisioner:
  name: chef_zero

platforms:
  - name: ubuntu-14.04
  - name: centos-7.1
    driver_config:
      image: testweb/centos7
      privileged: true
      run_command: /sbin/init; sleep 3
  - name: centos-6.6

suites:
  - name: default
    run_list:
      - recipe[testweb]
    attributes:
```

## Serverspecでテストスクリプト作成
作成したcookbook "testweb" をテストするServerspecスクリプトを作成する。Test Kitchenのルール上、`serverspec`ディレクトリ下に `*_spec.rb` という名前で作らなければならない。

```text:テストスクリプト作成
[centos@ip-10-0-0-11 chef-repo]$ mkdir -p test/integration/default/serverspec
[centos@ip-10-0-0-11 chef-repo]$ mkdir -p test/integration/helpers/serverspec
[centos@ip-10-0-0-11 chef-repo]$ vi test/integration/helpers/serverspec/spec_helper.rb
[centos@ip-10-0-0-11 chef-repo]$ vi test/integration/default/serverspec/testweb_spec.rb
```

```ruby:test/integration/helpers/serverspec/spec_helper.rb
require 'serverspec'

set :backend, :exec
```

```ruby:test/integration/default/serverspec/testweb_spec.rb
require 'spec_helper'

pkg = /redhat/i =~ os[:family] ? "httpd" : "apache2"

describe package(pkg) do
  it { should be_installed }
end

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

describe process(pkg) do
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

describe command("curl -LI http://localhost/index.php") do
  its(:stdout) { should contain("HTTP/1.1 200 OK") }
end

describe command("curl http://localhost/index.php") do
  its(:stdout) { should contain("<h1>Web Test</h1>") }
end
```

実行するテストは、以下の7つ。

* apache2のパッケージがインストールされているか
* apache2のサービスが自動起動する設定になっており、かつ起動中か
* apache2のプロセスが立ち上がっているか
* ポート80が待ち受けているか
* localhost/index.php にアクセスすると、ステータスコードが200でレスポンスが得られるか
* localhost/index.php から得られたHTMLに `<h1>Web Test</h1>` という文字列が含まれているか

# Test Kitchenの実行
`kitchen test` を実行すると、以下の1〜5のステップが自動実行される。

||Command|Description|
|:--|:--|:--|
|1|kitchen create|.kitchen.ymlで設定した、Ubuntu14.04 CentOS7 CentOS6のDockerコンテナ立ち上げ|
|2|kitchen setup|chef clientのセットアップ等、各コンテナの環境を整備|
|3|kitchen converge|各コンテナにcookbook "testweb" を適用|
|4|kitchen verify|Serverspecのテストスイートを実行|
|5|kitchen destroy|テスト済みのコンテナを破棄|

テストが成功すると、**7 examples, 0 failures**と表示されるはず。

```text:テスト実行
[centos@ip-10-0-0-11 chef-repo]$ kitchen test
...
       Package "apache2"
         should be installed
       
       Service "apache2"
         should be enabled
         should be running
       
       Process "apache2"
         should be running
       
       Port "80"
         should be listening
       
       Command "curl -LI http://localhost/index.php"
         stdout
           should contain "HTTP/1.1 200 OK"
       
       Command "curl http://localhost/index.php"
         stdout
           should contain "<h1>Web Test</h1>"
       
       Finished in 0.11714 seconds (files took 0.61704 seconds to load)
       7 examples, 0 failures
...
```

ここまでで、作成したcookbook "testweb" がUbuntu14.04、CentOS6、CentOS7で問題なく動作することが確認できた。

# Packerを用いたDockerイメージの生成
次にPackerを用いて、テスト済みのcookbookからDockerイメージを作成する。

## 設定ファイルの作成
Dockerイメージを生成するための設定ファイルを作成する。以下3つのフェーズに分けて設定を記述する。

* **builder** : 指定したDockerイメージをPULLする
* **provisioner** : PULLしたDockerイメージを元にプロビジョニングを行って、Dockerイメージを生成する
* **post-processor** : 生成したDockerイメージをローカルリポジ
トリに登録する
    * 今回は実行していないが、自動的にDockerHubにPUSHすることもできる

```text:packerの設定ファイル作成
[centos@ip-10-0-0-11 chef-repo]$ mkdir packer
[centos@ip-10-0-0-11 chef-repo]$ vi packer/packer_docker_ubuntu14.04.json
[centos@ip-10-0-0-11 chef-repo]$ vi packer/packer_docker_centos6.json
[centos@ip-10-0-0-11 chef-repo]$ vi packer/packer_docker_centos7.json
```

### Ubuntu14.04用設定ファイル
Packerは内部でcurlを用いてChef Clientをインストールするが、DockerHubからPULLしたUbuntu14.04イメージにはcurlがインストールされていない。

そこでcookbook "testweb" を適用するchef-solo provisionerを実行するより前に、shell provisionerでcurlをインストールしておく。

```json:packer/packer_docker_ubuntu14.04.json
{
  "builders": [
    {
      "type": "docker",
      "image": "ubuntu:14.04",
      "export_path": "image.tar"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "apt-get update -y",
        "apt-get install -y curl"
      ]
    },
    {
      "type": "chef-solo",
      "cookbook_paths": ["cookbooks"],
      "run_list": ["testweb::default"]
    }
  ],
  "post-processors": [
    {
      "type": "docker-import",
      "repository": "testweb/packer-docker-ubuntu14.04",
      "tag": "0.1"
    }
  ]
}
```

### CentOS6用設定ファイル
今回のcookbook "testweb" は、サービスをインストールするためsudoが必要だが、DockerHubからPULLしたCentOS6イメージにはsudoがインストールされていない。

そこでcookbook "testweb" を適用するchef-solo provisionerを実行するより前に、shell provisionerでsudoをインストールし、tty無しでもsudoできるように設定する。

```json:packer/packer_docker_centos6.json
{
  "builders": [
    {
      "type": "docker",
      "image": "centos:6",
      "export_path": "image.tar"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "yum -y update",
        "yum install -y sudo",
        "sed -i -e 's/^Defaults    requiretty/Defaults   !requiretty/g' /etc/sudoers"
      ]
    },
    {
      "type": "chef-solo",
      "cookbook_paths": ["cookbooks"],
      "run_list": ["testweb::default"]
    }
  ],
  "post-processors": [
    {
      "type": "docker-import",
      "repository": "testweb/packer-docker-centos6",
      "tag": "0.1"
    }
  ]
}
```

### CentOS7用設定ファイル
CentOS7はinitdがsystemdに変わり、加えてDockerHubにあるCentOS7イメージには `/sbin/service` が含まれていない。またコンテナ内でサービスを立ち上げるためには、privileged特権付きかつ起動コマンドを `/sbin/init` にしなければならない。

そのためshell provisionerで必要なパッケージをインストールするだけでなく、Packerがdocker runする際のコマンドオプションをbuilderで明示的に指定する必要がある。

```json:packer/packer_docker_centos7.json
{
  "builders": [
    {
      "type": "docker",
      "image": "centos:7",
      "export_path": "image.tar",
      "run_command": [
        "--privileged",
        "-d",
        "{{.Image}}",
        "/sbin/init"
      ]
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "yum -y update",
        "yum install -y sudo initscripts",
        "sed -i -e 's/^Defaults    requiretty/Defaults   !requiretty/g' /etc/sudoers"
      ]
    },
    {
      "type": "chef-solo",
      "cookbook_paths": ["cookbooks"],
      "run_list": ["testweb::default"]
    }
  ],
  "post-processors": [
    {
      "type": "docker-import",
      "repository": "testweb/packer-docker-centos7",
      "tag": "0.1"
    }
  ]
}
```

# Packerで生成したDockerイメージの起動
Packerを用いてUbuntu14.04、CentOS6、CentOS7のDockerイメージを作成して起動し、cookbook "testweb" どおりにコンテナが構成されていることを確認する。

## Dockerイメージの生成
次のコマンドで各Dockerイメージを生成する。

```text:Dockerイメージの生成
[centos@ip-10-0-0-11 chef-repo]$ packer build packer/packer_docker_ubuntu14.04.json
[centos@ip-10-0-0-11 chef-repo]$ packer build packer/packer_docker_centos6.json
[centos@ip-10-0-0-11 chef-repo]$ packer build packer/packer_docker_centos7.json
```

## Dockerコンテナの起動と確認
生成したイメージからDockerコンテナを起動する。CentOS7だけ通常とは異なる手順を踏んでいることに注意。

### Ubuntu14.04

```text:Ubuntu14.04コンテナ起動と確認
[centos@ip-10-0-0-11 chef-repo]$ docker run -it testweb/packer-docker-ubuntu14.04:0.1 /bin/bash
root@ad4c41852056:/# service apache2 start
 * Starting web server apache2                                                  AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.25. Set the 'ServerName' directive globally to suppress this message
 * 
root@ad4c41852056:/# curl http://localhost/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : ubuntu 14.04</p>
  <p>このサーバのIPアドレス : ::1</p>
</body>
</html>
```

### CentOS6

```text:CentOS6コンテナの起動と確認
[centos@ip-10-0-0-11 chef-repo]$ docker run -it testweb/packer-docker-centos6:0.1 /bin/bash
[root@2449245b8eaf /]# service httpd start
Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.26 for ServerName
                                                           [  OK  ]
[root@2449245b8eaf /]# curl http://localhost/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : centos 6.7</p>
  <p>このサーバのIPアドレス : ::1</p>
</body>
</html>
```

### CentOS7

```text:CentOS7コンテナの起動と確認
[centos@ip-10-0-0-11 chef-repo]$ CID=$(docker run --privileged -d testweb/packer-docker-centos7:0.1 /sbin/init)
[centos@ip-10-0-0-11 chef-repo]$ docker exec -it $CID /bin/bash
[root@21b348656b3f /]# service httpd start
Redirecting to /bin/systemctl start  httpd.service
[root@21b348656b3f /]# curl http://localhost/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : centos 7.1.1503</p>
  <p>このサーバのIPアドレス : ::1</p>
</body>
</html>
```

# 他サーバのプロビジョニング
最後に、cookbook "testweb" を他のサーバに適用する。

## SSH鍵の転送
Knifeで鍵を用いてSSHできるように、AWSに登録している鍵のpemファイルをChef zero Workstationにscpする。

```text:SSH鍵の転送
nmatsui@develop:~$ scp -i .ssh/aws_tokyo.pem /home/nmatsui/.ssh/aws_tokyo.pem centos@XXX.XXX.XXX.XXX:~/.ssh
```

## EC2インスタンスの起動
転送した鍵ファイルを指定して、Ubuntu14.04（10.0.0.41）、CentOS6（10.0.0.42）、CentOS7（10.0.0.43）の3つのEC2インスタンスを起動する。

## bootstrapの実行
knife zero bootstrapを実行して、プロビジョニング対象のnodeを管理下におく。

```text:bootstrap実行
[centos@ip-10-0-0-11 chef-repo]$ knife zero bootstrap 10.0.0.41 -x ubuntu -i ~/.ssh/aws_tokyo.pem --sudo
[centos@ip-10-0-0-11 chef-repo]$ knife zero bootstrap 10.0.0.42 -x root -i ~/.ssh/aws_tokyo.pem --sudo
[centos@ip-10-0-0-11 chef-repo]$ knife zero bootstrap 10.0.0.43 -x centos -i ~/.ssh/aws_tokyo.pem --sudo
[centos@ip-10-0-0-11 chef-repo]$ knife node list
ip-10-0-0-41.ap-northeast-1.compute.internal
ip-10-0-0-42.ap-northeast-1.compute.internal
ip-10-0-0-43.ap-northeast-1.compute.internal
```

## run listの登録
管理下におかれたnodeのrun_listに、cookbook "testweb" のレシピを登録する。

```text:run_list登録
[centos@ip-10-0-0-11 chef-repo]$ knife node run_list add ip-10-0-0-41.ap-northeast-1.compute.internal 'recipe[testweb::default]'
ip-10-0-0-41.ap-northeast-1.compute.internal:
  run_list: recipe[testweb::default]
[centos@ip-10-0-0-11 chef-repo]$ knife node run_list add ip-10-0-0-42.ap-northeast-1.compute.internal 'recipe[testweb::default]'
ip-10-0-0-42.ap-northeast-1.compute.internal:
  run_list: recipe[testweb::default]
[centos@ip-10-0-0-11 chef-repo]$ knife node run_list add ip-10-0-0-43.ap-northeast-1.compute.internal 'recipe[testweb::default]'
ip-10-0-0-43.ap-northeast-1.compute.internal:
  run_list: recipe[testweb::default]
```

## run_list実行
管理下の各nodeでrun_listを実行する。

```text:run_list実行
[centos@ip-10-0-0-11 chef-repo]$ knife zero chef_client 'name:ip-10-0-0-41.ap-northeast-1.compute.internal' -x ubuntu -i ~/.ssh/aws_tokyo.pem --sudo
[centos@ip-10-0-0-11 chef-repo]$ knife zero chef_client 'name:ip-10-0-0-42.ap-northeast-1.compute.internal' -x root -i ~/.ssh/aws_tokyo.pem --sudo
[centos@ip-10-0-0-11 chef-repo]$ knife zero chef_client 'name:ip-10-0-0-43.ap-northeast-1.compute.internal' -x centos -i ~/.ssh/aws_tokyo.pem --sudo
```

## 動作確認
各インスタンスにapach2とPHPがプロビジョニングされていることが確認できる。

```text:Ubuntu14.04（10.0.0.41）の動作確認
[centos@ip-10-0-0-11 chef-repo]$ curl http://10.0.0.41/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : ubuntu 14.04</p>
  <p>このサーバのIPアドレス : 10.0.0.41</p>
</body>
</html>
```
```text:CentOS6（10.0.0.42）の動作確認
[centos@ip-10-0-0-11 chef-repo]$ curl http://10.0.0.42/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : centos 6.5</p>
  <p>このサーバのIPアドレス : 10.0.0.42</p>
</body>
</html>
```
```text:CentOS7（10.0.0.43）の動作確認
[centos@ip-10-0-0-11 chef-repo]$ curl http://10.0.0.43/index.php
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8"/>
  <title>Web Test</title>
</head>
<body>
  <h1>Web Test</h1>
  <p>このサーバのプラットフォーム : centos 7.0.1406</p>
  <p>このサーバのIPアドレス : 10.0.0.43</p>
</body>
</html>
```

# まとめ
ChefとServerspecを中心に、開発したcookbookをローカルのDockerでテストした後に他のサーバに適応するという一連の流れを追ってみた。
CentOS7への対応が思っていた以上に面倒だったため、その面倒な部分をツール側が隠蔽してくれるような改善が望まれる。

なお今回作成したcookbookやPacker設定ファイル等はGithubで公開しているので、興味があれば確認して欲しい。

https://github.com/nmatsui/chef-testkitchen-docker-packer