Chefハンズオン#1 Chefの環境構築とサンプルレシピの作成・実行


## Windowsのターミナルエミュレータ(蛇足)

Chefではコマンドを利用する場合や、SSH接続の簡略化のため `~/.ssh/` などのパスを利用する場合があります。
Windowsローカルの環境で開発を行う場合は、[ターミナルエミュレータminttyのインストール](http://dogmap.jp/2011/11/15/mintty/)などをオススメします。

ミドルウェアのインストール
--------------------------

VirtualBoxとVagrantはインストーラでインストールします。

- [Downloads - Oracle VM VirtualBox](https://www.virtualbox.org/wiki/Downloads)
- [Downloads - Vagrant](https://www.vagrantup.com/downloads.html)


### Rubyのインストール

[Rubyのインストール](https://www.ruby-lang.org/ja/installation/)から適切な方法を選択します。
バージョンは現在の最新版である2.1.2を前提として進めます。

- OS X : `brew install rbenv ruby-build` でrbenvをインストールする方法をオススメします。
- Linux : $HOME以下にrbenvでインストールする方法をオススメします。
- Windows : インストーラでインストールします。

Rubyインストール後はBundlerは必ず入れておきましょう。
(`rbenv rehash`はrbenvでRubyをインストールした場合に実行します。)

```
$ gem install bundler
$ rbenv rehash
```



作業環境のセットアップ
----------------------

作業ディレクトリを作り、Chefの実行やテストに必要なgemをインストールします。

```
$ mkdir sample
$ cd sample
$ bundle init
$ vim Gemfile
```

`Gemfile`には以下を記述します。(バージョンは随時確認し、最新版を利用してください)

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

gem 'knife-solo',          '~> 0.4.2'
gem 'knife-solo_data_bag', '~> 1.1.0'
gem 'berkshelf',           '~> 3.1.5'

group :test do
  gem 'foodcritic',   '~> 4.0.0'
  gem 'test-kitchen', '~> 1.2.1'
  gem 'serverspec',   '~> 2.1.0'
  gem 'chefspec',     '~> 4.0.2'
end
```

記述したgemをインストールします。

```
$ bundle install
$ rbenv rehash
```

Chef-Repo(knife-solo版)セットアップ
-----------------------------------

作業ディレクトリ以下にChefのファイル構成（通称Chef-Repo）を作成します。今回はknife-solo版です。
また、ついでに一つクックブックを作っておきます。

```
$ knife solo init .
$ knife cookbook create sample -o site-cookbooks
```

作成直後はこのようなディレクトリ構成になります。

```
sample/
├── Berksfile
├── cookbooks/
├── data_bags/
├── environments/
├── nodes/
├── roles/
└── site-cookbooks/
    └── sample/
        ├── CHANGELOG.md
        ├── README.md
        ├── attributes/
        ├── definitions/
        ├── files/
        │   └── default/
        ├── libraries/
        ├── metadata.rb
        ├── providers/
        ├── recipes/
        │   └── default.rb
        ├── resources/
        ├── spec/
        │   ├── recipes/
        │   │   └── default_spec.rb
        │   └── spec_helper.rb
        └── templates/
            └── default/
```


Vagrant セットアップ
--------------------

Vagrantを使ってテスト用の仮想OSを管理します。
ゼロインストール状態からの再テストなどを行う時に便利なsaharaプラグインをインストールしておきます。

```
$ vagrant plugin install sahara
```

次にOSイメージの選択とイメージの取得、VMの起動を行います。
(`vagrant up` は初回はイメージのダウンロードに時間がかかります)

```
$ vagrant init chef/debian-7.4
$ vagrant up 
```

VMへのSSHログインを試してみます。ログインできたらCtrl+Dで終了しましょう。

```
$ vagrant ssh
```

`ssh`コマンドでログインできるように、
`vagrant ssh-config`コマンドが自動作成したssh設定を保存しておきます。

```
$ vagrant ssh-config --host vagrant
$ vagrant ssh-config --host vagrant >> ~/.ssh/config
```

これにより、次のようにsshコマンドでログインできるはずです。

```
$ ssh vagrant
```

Chefの実行
----------

Vagrantで建てたサーバにクックブックを適用してみましょう。
まずはじめに、chefの実行環境をサーバにセットアップします。以下のコマンドを実行してください。

```
$ knife solo prepare vagrant
```

`Bootstrapping Chef...`などの出力があり、chef-clientのセットアップが自動で進行します。
今回は仮想OSに Debian 7.4 を選択しましたが、OSのバージョンにあわせて自動でパッケージが選択されます。

chef-clientのインストールが完了したら、この状態を記録しておきます。Vagrantでインストールしたsaharaプラグインを利用し、スナップショットを作ります。

```
$ vagrant sandbox on
```

続いて、作業ディレクトリに`nodes/vagrant.json`ファイルが生成されているので、次のように編集し`run_list`に `"recipe[sample]"`を追加します。

```json
{
  "run_list": [
    "recipe[sample]"
  ],
  "automatic": {
    "ipaddress": "vagrant"
  }
}
```

設定が終わったら、`knife solo`コマンドを実行し、sampleクックブックを実行します。

```
$ knife solo cook vagrant
```

以下の様な出力が最終行に出て、0個のリソースの更新、つまり何も実行しなかったことを示しています。

```
Chef Client finished, 0/0 resources updated in 7.901792483 seconds
```

レシピを更新して再実行
----------------------

sampleクックブックのレシピを更新します。 `site-cookbooks/sample/recipes/default.rb` をエディタで開き、以下を追記します。

```ruby
directory "/tmp/hoge" do
  action :create
end

file "/tmp/hoge/ipaddress.txt" do
  content node[:ipaddress]
end

file "/tmp/hoge/time.txt" do
  content Time.now.to_s
end
```

先程と同じく実行してみると、今度は3個のリソースが更新されたと表示されます。

```
$ knife solo cook vagrant
...
Chef Client finished, 3/3 resources updated in 8.175327603 seconds
```

実際にログインしてみると、ディレクトリとファイルが生成され、IPアドレスや作業した時刻が記録されていることがわかります。

再実行と冪等性
--------------

さて、同じ処理をもう一回実行してみます。

```
$ knife solo cook vagrant
...
Chef Client finished, 1/3 resources updated in 6.992058771 seconds
```

リソースがひとつ更新されたとあります。
`time.txt`ファイルに記載された時間は実行時の時間であるため毎回異なり、chefがそれを検知したため更新を実行しました。

ファイルが既にある場合は更新しないようにしてみましょう。
`not_if`を追加して条件をみたす場合は適用しないようにします。

```diff
 file "/tmp/hoge/time.txt" do
   content Time.now.to_s
+  not_if { ::File.exists?("/tmp/hoge/time.txt”) }
 end
```

同じく実行してみると、`file[/tmp/hoge/time.txt] action create (skipped due to not_if)`という表示が出て、今度はファイルの更新は実行されませんでした。
結果に表示されるリソースの総数はスキップした分減りますが、スキップしたことがログに残ります。

まとめ
------

「既にあるリソース（ファイルやサービスの状態）に対して既に同じ内容であるかを確認して、もし差異があれば処理を実行する」というのがChefの冪等性という概念です。

今回は変更が行われる例として時間を設定しましたが、通常は設定ファイル内の項目や値が古い場合や、稼働しているはずのサービスが稼働していない、アップデートで削除されるはずのファイルが存在する、などの適用する状況による差異が生まれます。
Chefではこのような「定義された状態」になっていない部分にのみ変更を加え、一定の状態に「収束(converge)」させることを目指します。

この「状態を定義」するのがChefのレシピです。