Vagrant + Chef Soloで環境構築してみる


## はじめに
今更感もありますが、標題の件をいじってみたのでメモしておきます。         
[こちら](
http://www.atmarkit.co.jp/ait/articles/1305/24/news003_2.html
)の內容をVagrantを使った方法に置き換えて、VirtualBoxとAWSの両方に適用してみることにしました。

使用したホストOS(ローカルPC)の環境は以下のとおりです。
* OS : MacOS X 10.9.5
* Vagrant : 1.6.5
* VirtualBox : 4.3.14


## VirtualBoxで実行してみる
まずは、ローカルPCのVirtualBoxで仮想マシン起動・プロビジョニングをしてみます。
### Vagrantfile作る
適当なディレクトリ作って、Vagrant設定を作成します。
boxは以前addしたものを使用しました。
確かbentoからaddしたboxなので、boxにはChef一式が入ってました。

   `vagrant init centos64-x64`

### ホストOS(ローカルPC)にChefインストール
knifeでレシピディレクトリ作りたいので、ホストOS(ローカルPC)にChef一式を入れます。

   `% curl -L https://www.opscode.com/chef/install.sh | sudo bash`

### レシピディレクトリ作成
   `% knife cookbook create apache2-take-simple -o .`
  recipes/defalut.rbとtemplates/default/index.html.erbを作成します。

```ruby:default.rb
package 'apache2' do
  case node[ :platform ]
  when 'redhat', 'centos'
    package_name 'httpd'
  when 'debian', 'ubuntu'
    package_name 'apache2'
  end
end
#
# set content
#
template 'index.html' do
  case node[ :platform ]
  when 'redhat', 'centos'
    path '/var/www/html/index.html'
  when 'debian', 'ubuntu'
    path '/var/www/index.html'
  end
  source 'index.html.erb'
  mode 0644
  variables( { :fqdn => node[ :fqdn] } )
end
#
# set auto start and restart apache2
#
service 'apache2' do
  case node[ :platform ]
  when  'redhat', 'centos'
    service_name 'httpd'
  when 'debian', 'ubuntu'
    service_name 'apache2'
  end
  action [ :enable, :start ]
end
```

```index.html.erb
Welcome to <%= @fqdn %>
```

### provision設定追加
以下のように、Vagrantfileへprovision設定を追加します。

```ruby:Vagrantfile
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "centos64-x64"
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "."
    chef.run_list = "recipe[apache2-take-simple]"
  end
end
```

### vagrant up

`vagrant up`で、仮想マシンが立ち上がります。
`vagrant ssh`で仮想マシンに入って、設定ができている事を確認します。

* Apacheがサービス登録されていることを確認。  
      `chkconfig --list httpd`
* Apacheが起動していることを確認。  
      `ps -aef | grep httpd`
* htmlが正しく置換・配置されたことを確認。  
      `curl 127.0.0.1`

問題なく起動・設定できました。

## AWS（EC2）で実行してみる

次はAWSへ適用してみます。
### vagrant-awsプラグインを入れる

`vagrant plugin install vagrant-aws`
で、インストール開始。
完了後に追加されている事を確認してみます。

```
% vagrant plugin list
vagrant-aws (0.5.0)
vagrant-login (1.0.1, system)
vagrant-share (1.1.2, system)
```
### ダミーのboxファイル追加
AMIを使うことになるのでboxはいらないけれど、何も指定しないとエラーになってしまうらしいので、ダミーのボックスを追加して使用します。

`vagrant box add dummy https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box`

### Vagrantfile作る
通常の手順と一緒です。
適当なディレクトリを掘って、そこに移動。
で、vagrant initすればOK。

```
% mkdir to_aws_sample
[naokienokido@Naoki-Enokido-no-MacBook-Pro] ~/Documents/vagrantDir
% cd to_aws_sample
[naokienokido@Naoki-Enokido-no-MacBook-Pro] ~/Documents/vagrantDir/to_aws_sample
% vagrant init
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.
```

Vagrantfileを以下のように編集しました。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "dummy"
  config.vm.provider :aws do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID'] # AWSのアクセスキーID指定 同名で環境変数に設定されている前提
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] # AWSのシークレットアクセスキー指定 同名で環境変数に設定されている前提
    aws.keypair_name = "vagrant-ec2" # 使用するKeyPairの名前を指定
    override.ssh.username = "ec2-user" # sshするユーザ名を指定 ec2-user固定
    override.ssh.private_key_path = "~/.ssh/vagrant-ec2.pem" # KeyPairからDLした鍵ファイルを指定
    aws.instance_type = "t2.micro" # 使用するインスタンスタイプを指定
    aws.region = "ap-northeast-1" # リージョン指定
    aws.ami = "ami-35072834" # 使用するAMIのIDを指定
    aws.subnet_id = "subnet-8ed6d9c8" # 使用するサブネットIDを指定
    aws.private_ip_address = "10.0.1.11" # VPC内のローカルIPを指定
    aws.security_groups = [ 'WEB-SG' ] # 適用するセキュリティグループを指定
  end
end
```

アクセスキーIDとシークレットアクセスキーは、.zshrcに同名で環境変数として追加してあります。

### vagrant up
上記Vagrantfileを使用して

`vagrant up --provider=aws`

でインスタンスを立ち上げてみます。  
すると、こんなエラーが出ました。  

`The parameter as cannot be used with the parameter subnet`

セキュリティグループをIDではなくて名前で指定してしまっていたのが原因だったので、修正します。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "dummy"
  config.vm.provider :aws do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID'] # AWSのアクセスキーID指定 同名で環境変数に設定されている前提
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] # AWSのシークレットアクセスキー指定 同名で環境変数に設定されている前提
    aws.keypair_name = "vagrant-ec2" # 使用するKeyPairの名前を指定
    override.ssh.username = "ec2-user" # sshするユーザ名を指定 ec2-user固定
    override.ssh.private_key_path = "~/.ssh/vagrant-ec2.pem" # KeyPairからDLした鍵ファイルを指定
    aws.instance_type = "t2.micro" # 使用するインスタンスタイプを指定
    aws.region = "ap-northeast-1" # リージョン指定
    aws.ami = "ami-35072834" # 使用するAMIのIDを指定
    aws.subnet_id = "subnet-8ed6d9c8" # 使用するサブネットIDを指定
    aws.private_ip_address = "10.0.1.11" # VPC内のローカルIPを指定
    aws.security_groups = [ 'sg-fcaa6a99' ] # 適用するセキュリティグループをIDで指定
  end
end
```

再度`vagrant up --provider=aws`を実行し、AWSコンソールからインスタンスが立ち上がることを確認しました。
が、`vagrant up`の実行が終わりません。

```
==> default: Waiting for SSH to become available...
```
というところで止まっているように見えます。

詳細を見るために、DEBUG指定でvagrant upしてみました。

`VAGRANT_LOG=DEBUG vagrant up --provider=aws`

色々ログが出ますが、以下のSSH接続のところでループしているのが分かりました。

```
DEBUG ssh: == Net-SSH connection debug-level log END ==
 INFO retryable: Retryable exception raised: #<Errno::ECONNREFUSED: Connection refused - connect(2)>
 INFO ssh: Attempting to connect to SSH...
 INFO ssh:   - Host: 10.0.1.11
 INFO ssh:   - Port: 22
 INFO ssh:   - Username: ec2-user
 INFO ssh:   - Password? false
 INFO ssh:   - Key Path: ["/Users/naokienokido/.ssh/vagrant-ec2.pem"]
DEBUG ssh: == Net-SSH connection debug-level log START ==
DEBUG ssh: D, [2014-10-15T10:32:56.841175 #10222] DEBUG -- net.ssh.transport.session[80762d68]: establishing connection to 10.0.1.11:22
```

Public DNSが設定できてない事が原因のようです。
そこで、以下の設定をVagrantfileに追加して、EIPが振られるようにします。

`aws.elastic_ip = true`

で、再度vagrant upすると、

```
 INFO interface: Machine: error-exit ["Vagrant::Errors::VagrantError", "The following SSH command responded with a non-zero exit status.\nVagrant assumes that this means the command failed!\n\nmkdir -p '/vagrant'\n\nStdout from the command:\n\n\n\nStderr from the command:\n\nsudo: sorry, you must have a tty to run sudo\n"]
```

みたいなエラーが出るが、インスタンスの起動自体は成功しました。
ただ、/vagrantディレクトリの同期は失敗した状態です。
(前述のエラー通り、sudo指定での/vagrantディレクトリの作成に失敗している。)


### sudoエラーの解消
前述のエラーの內容は、「tty経由ではないsudoは実行できない」という、よく見かけるやつです。
Amazon Linuxでは、requirettyはデフォルトで有効化されているようです。
（以前はそうではなかった？というのを何かで見たような気がします。）

解決策をググると、
「ゲストOSの/etc/sudoersを直接書き換えてrequirettyを無効化する」
という方法が結構引っかかってきますが、requirettyのそもそもの目的を考えると微妙な気がしました。

他の方法を探したところ、Vagrant1.4以降では以下のオプションを指定すれば解決しそうだったので、これをVagrantfileに追加します。

`config.ssh.pty = true`

これで再度vagrant upして、エラーが解消され、起動したインスタンス側に/vagrantディレクトリが作成され、Rsync Synced Folderとして機能していることが確認できました。

この時点で、Vagrantfileは以下のようになりました。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  # config.vm.boot_timeout = 120
  config.vm.box = "dummy" # dummyボックスを使用 実際はAMIを使用して仮想マシンが作成される
  config.ssh.pty = true # Amazon Linuxでは/etc/sudoersでrequirettyが有効になっているためsudoエラーにならないように指定
  config.vm.provider :aws do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID'] # AWSのアクセスキーID指定 同名で環境変数に設定されている前提
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] # AWSのシークレットアクセスキー指定 同名で環境変数に設定されている前提
    aws.keypair_name = "vagrant-ec2" # 使用するKeyPairの名前を指定
    override.ssh.username = "ec2-user" # sshするユーザ名を指定 ec2-user固定
    override.ssh.private_key_path = "~/.ssh/vagrant-ec2.pem" # KeyPairからDLした鍵ファイルを指定
    aws.instance_type = "t2.micro" # 使用するインスタンスタイプを指定
    aws.region = "ap-northeast-1" # リージョン指定
    aws.ami = "ami-35072834" # 使用するAMIのIDを指定
    aws.subnet_id = "subnet-8ed6d9c8" # 使用するサブネットIDを指定
    aws.private_ip_address = "10.0.1.11" # VPC内のローカルIPを指定
    aws.elastic_ip = true # EIPを自動で割り当てる　public subnet使用時はtrueにしておく必要がある
    aws.security_groups = [ 'sg-fcaa6a99' ] # 適用するセキュリティグループをIDで指定
  end
end

```

### vagrant-omnibusプラグインを入れる
AMIにはChefは入っていないので、vagrant-omnibusプラグインを使ってインスタンス起動時にインストールするようにします。

`vagrant plugin install vagrant-omnibus`  
で、インストール実行。
完了後に追加されている事を確認します。

```
% vagrant plugin list
vagrant-aws (0.5.0)
vagrant-login (1.0.1, system)
vagrant-omnibus (1.4.1)
vagrant-share (1.1.2, system)
```

Vagrantfileに以下を追加します。

`config.omnibus.chef_version = :latest`

`vagrant provision`の都度で、最新のChefをインストールしてくれるようです。

### レシピディレクトリ作成
ホストOSのVagrantfileが置いてあるディレクトリで、以下を実行。

`% knife cookbook create apache2-take-simple -o .`

recipes/defalut.rbとtemplates/default/index.html.erbは、VirtualBoxで実行したものをコピーしました。

### Vagrantfileにprovisioning指定追加
Vagrantfileに、Virtualboxで実行した時と同じ設定を追加します。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  # config.vm.boot_timeout = 120
  config.vm.box = "dummy" # dummyボックスを使用 実際はAMIを使用して仮想マシンが作成される
  config.ssh.pty = true # Amazon Linuxでは/etc/sudoersでrequirettyが有効になっているためsudoエラーにならないように指定
  config.omnibus.chef_version = :latest # Chefをインストールする
  config.vm.provider :aws do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID'] # AWSのアクセスキーID指定 同名で環境変数に設定されている前提
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] # AWSのシークレットアクセスキー指定 同名で環境変数に設定されている前提
    aws.keypair_name = "vagrant-ec2" # 使用するKeyPairの名前を指定
    override.ssh.username = "ec2-user" # sshするユーザ名を指定 ec2-user固定
    override.ssh.private_key_path = "~/.ssh/vagrant-ec2.pem" # KeyPairからDLした鍵ファイルを指定
    aws.instance_type = "t2.micro" # 使用するインスタンスタイプを指定
    aws.region = "ap-northeast-1" # リージョン指定
    aws.ami = "ami-35072834" # 使用するAMIのIDを指定
    aws.subnet_id = "subnet-8ed6d9c8" # 使用するサブネットIDを指定
    aws.private_ip_address = "10.0.1.11" # VPC内のローカルIPを指定
    aws.elastic_ip = true # EIPを自動で割り当てる　public subnet使用時はtrueにしておく必要がある
    aws.security_groups = [ 'sg-fcaa6a99' ] # 適用するセキュリティグループをIDで指定
  end
  # chef-soloによるプロビジョニングの設定
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "."
    chef.run_list = "recipe[apache2-take-simple]"
  end
end
```

### 再度 vagrant up
`vagrant up --provider=aws`を実行すると、エラーが出ました。

```
==> default:     Chef::Exceptions::Package
==> default:     -------------------------
==> default:     No version specified, and no candidate version available for apache2
==> default:
==> default:     Resource Declaration:
==> default:     ---------------------
==> default:     # In /tmp/vagrant-chef-3/chef-solo-1/cookbooks/apache2-take-simple/recipes/default.rb
==> default:
==> default:      12: package 'apache2' do
==> default:      13:   case node[ :platform ]
==> default:      14:   when 'redhat', 'centos'
==> default:      15:     package_name 'httpd'
==> default:      16:   when 'debian', 'ubuntu'
==> default:      17:     package_name 'apache2'
==> default:      18:   end
==> default:      19: end
==> default:
==> default:     Compiled Resource:
==> default:     ------------------
==> default:     # Declared in /tmp/vagrant-chef-3/chef-solo-1/cookbooks/apache2-take-simple/recipes/default.rb:12:in `from_file'
==> default:
==> default:     package("apache2") do
==> default:       action :install
==> default:       retries 0
==> default:       retry_delay 2
==> default:       guard_interpreter :default
==> default:       package_name "apache2"
==> default:       timeout 900
==> default:       cookbook_name :"apache2-take-simple"
==> default:       recipe_name "default"
==> default:     end
```

パッケージ名「apache2」でインストールが実行された事が原因です。
recipes/default.rbでplatform判定をしていますが、Amazon Linuxに該当する条件がないため、  
`package 'apache2'`  
で指定した名前がそのままパッケージ名として使用されています。

```ruby:default.rb
package 'apache2' do
  case node[ :platform ]
  when 'redhat', 'centos'
    package_name 'httpd'
  when 'debian', 'ubuntu'
    package_name 'apache2'
  end
end
```

Amazon Linuxを判定するためには「amazon」というplatform名が使えるらしいので、以下のようにコード修正しました。

```ruby:default.rb
package 'apache2' do
  case node[ :platform ]
  when 'redhat', 'centos', 'amazon'
    package_name 'httpd'
  when 'debian', 'ubuntu'
    package_name 'apache2'
  end
end
#
# set content
#
template 'index.html' do
  case node[ :platform ]
  when 'redhat', 'centos', 'amazon'
    path '/var/www/html/index.html'
  when 'debian', 'ubuntu'
    path '/var/www/index.html'
  end
  source 'index.html.erb'
  mode 0644
  variables( { :fqdn => node[ :fqdn] } )
end
#
# set auto start and restart apache2
#
service 'apache2' do
  case node[ :platform ]
  when  'redhat', 'centos', 'amazon'
    service_name 'httpd'
  when 'debian', 'ubuntu'
    service_name 'apache2'
  end
  action [ :enable, :start ]
end
```

これで`vagrant up --provier=aws`を実行してもエラーが出なくなりました。  
インスタンスが起動したら`vagrant ssh`でインスタンスに入って、設定ができている事を確認してみます。

* Apacheがサービス登録されていることを確認。  
`chkconfig --list httpd`
* Apacheが起動していることを確認。  
`ps -aef | grep httpd`
* htmlが正しく置換・配置されたことを確認。  
`curl 127.0.0.1`
* プラウザからPublic DNSを指定して、上記と同じ画面が開くことを確認。

全て問題なく設定されていました。

最終的にVagrantfileは以下のようになりました。

```ruby:Vagrantfile
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  # config.vm.boot_timeout = 120
  config.vm.box = "dummy" # dummyボックスを使用 実際はAMIを使用して仮想マシンが作成される
  config.ssh.pty = true # Amazon Linuxでは/etc/sudoersでrequirettyが有効になっているためsudoエラーにならないように指定
  config.omnibus.chef_version = :latest # Chefをインストールする
  config.vm.provider :aws do |aws, override|
    aws.access_key_id = ENV['AWS_ACCESS_KEY_ID'] # AWSのアクセスキーID指定 同名で環境変数に設定されている前提
    aws.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY'] # AWSのシークレットアクセスキー指定 同名で環境変数に設定されている前提
    aws.keypair_name = "vagrant-ec2" # 使用するKeyPairの名前を指定
    override.ssh.username = "ec2-user" # sshするユーザ名を指定 ec2-user固定
    override.ssh.private_key_path = "~/.ssh/vagrant-ec2.pem" # KeyPairからDLした鍵ファイルを指定
    aws.instance_type = "t2.micro" # 使用するインスタンスタイプを指定
    aws.region = "ap-northeast-1" # リージョン指定
    aws.ami = "ami-35072834" # 使用するAMIのIDを指定
    aws.subnet_id = "subnet-8ed6d9c8" # 使用するサブネットIDを指定
    aws.private_ip_address = "10.0.1.11" # VPC内のローカルIPを指定
    aws.elastic_ip = true # EIPを自動で割り当てる　public subnet使用時はtrueにしておく必要がある
    aws.security_groups = [ 'sg-fcaa6a99' ] # 適用するセキュリティグループをIDで指定
  end
  # chef-soloによるプロビジョニングの設定
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = "."
    chef.run_list = "recipe[apache2-take-simple]"
  end
end
```
もっといい記述方法もありそうなので、追々改善していきたいと思います。

## 参考
以下を参考にさせていただきました。
ありがとうございました。

### 実施した內容の元
http://www.atmarkit.co.jp/ait/articles/1305/24/news003_2.html
### Vagrantの設定
http://dev.classmethod.jp/tool/vagrant/
### AWS（EC2）での実行
vagrant-awsプラグインの公式ドキュメント：https://github.com/mitchellh/vagrant-aws
http://d.hatena.ne.jp/naoya/20130315/1363340698
http://www.ryuzee.com/contents/blog/6807
http://dqn.sakusakutto.jp/2013/10/the_following_settings_dont_exist_ssh_private_key_path_ssh_username.html
### sudoエラー(tty指定）の辺り
http://j-caw.co.jp/blog/?p=1327
https://github.com/mitchellh/vagrant/issues/1482
http://www.1x1.jp/blog/2014/03/vagrant-rsync-synced-folder.html
### platform判定の辺り
http://ao-log.hatenablog.com/entry/2013/11/27/220136