Chef memo3


[Chef実践入門 ~コードによるインフラ構成の自動化](http://www.amazon.co.jp/dp/477416500X) のまとめノート

# 3 レシピの書き方

## 3.1 リソースとは
* service package templateといった部品。
* Chefのレシピを書くということは、「Chefというフレームワークが用意したserviceやpackageといったDSLでリソースを定義する」ということ。

## 3.2 td-agentのレシピを読む

* 複数のリソースをうまく使ったレシピ例を見る

### groupとuser

* それぞれOSのグループとユーザを定義するためのリソース。
* アクションとして:createが定義されている。

```
group 'td-agent' do
  group_name 'td-agent'
  gid        403
  action     :create
end

user 'td-agent' do
  comment  'td-agent'
  uid      403
  group    'td-agent'
  home     'var/run/td-agent'
  shell    'bin/false'
  password nil
  supports :manage_home => true
  action   [:create, :manage]
end

```


* userリソースいろいろ

```
user リソース名 do
　action
　　:create（作成：デフォルト）
　　:remove（削除）
　　:modify（変更：該当ユーザーが存在しない場合エラー）
　　:manage（変更：該当ユーザーが存在しない場合何もしない）
　　:lock（ロック）
　　:unlock（ロック解除）
　　:nothing（何もしない）
　username "ユーザー名（デフォルト値：リソース名）"
　comment "コメント（デフォルト値：nil）"
　uid uid（デフォルト値：nil）
　gid "グループ名（デフォルト値：nil）"
　home "ホームディレクトリーのパス（デフォルト値：nil）"
　system
　  nil（デフォルト）
　  true（システムユーザー）
　  false（非システムユーザー）
　shell "デフォルトシェルのパス（デフォルト値：nil）"
　password "パスワードのハッシュ値（デフォルト値：nil）"
　supports
　　:manage_home => 
　　  false（ホームディレクトリーを作成しない：デフォルト）
　　  true（ホームディレクトリーを作成する）},
　　:non_unique =>
　　  false（uidの重複を認めない：デフォルト）
　　  true（uidの重複を認める）
　retries リトライ回数（デフォルト値：0）
　retry_delay リトライ間隔[秒]（デフォルト値：2）
　ignore_failure
　  false（エラー時に終了：デフォルト）
　  true（エラー時も継続） 
end
```

### directory

* 任意のディレクトリを定義するためのリソース

```
directory '/etc/td-agent' do
  owener 'td-agent'
  group  'td-agent'
  mode   '0755'
  action :create
end
```

### caseによるOSの判定

* node['platform']という変数に入っている値を軸にcase文で分岐処理を行っている。
* ubuntu系とRedHat系の場合で分けている。

```
case node['platform']
when "ubuntu"
  dist = node['lsb']['codename']
  source = (dist == 'precise') ? "http://packages.treasure-data.com/precise/" : "http://packages.treasure-data.com/debian/"
  apt_repository "treasure-data" do
    uri source
    distribution dist
    components ["contrib"]
    action :add
  end
when "centos", "redhat"
  yum_repository "treasure-data" do
    url "http://packages.treasure-data.com/redhat/$basearch"
    action :add
  end
end
```

### AttributeとOhai

#### Attrbute

* テンプレートやレシピの中から参照できるkey-valueの値を管理する仕組み。 
* node['platform']はまさにそのAttrbuteで、ubuntuやredhatといった値が入ってくる。
* Attrbuteはnodeオブジェクトに自分で設定できる。
* Chefがシステムからあらかじめ抽出した値も入ってくる。

### Ohai

* ChefはOSから各種情報を集めてAttrbuteを構成するのにOhiとういRubyのライブラリを使う(Chefにpreinstall済み)。
* データをJSON形式で生成してくれる。

```
# Ohaiの実行委結果
$ ohai | head
{
  "cpu": {
    "real": 2,
    "total": 4,
    "mhz": 2400,
    "vendor_id": "GenuineIntel\n",
    "model_name": "Intel(R) Core(TM) i5-2435M CPU @ 2.40GHz\n",
    "model": 42,
    "family": 6,
    "stepping": 7,

# 引数にキーを指定すると任意の値のみ取得できる
$ ohai platform
[
  "mac_os_x"
]
```

* Ohaiの値をChefで実行したい場合には、レシピ内で node[:platform]のように書いて取得できる。

### template、package、service

```
template "/etc/td-agent/td-agent.conf" do
  mode "0644"
  source "td-agent.conf.erb"
end

package "td-agent" do
  options value_for_platform(
    ["ubuntu", "debian"] => {"default" => "-f --force-yes"},
    "default" => nil
  )
  action :upgrade
end

service "td-agent" do
  action [ :enable, :start ]
  subscribes :restart, resources(:template => "/etc/td-agent/td-agent.conf")
end
```

* 以上がtd-agentのレシピ。


## 3.3 主要なリソース解説

* Chefが標準で提供しているリソースのうちよく使うものを中心に説明する。

### package

#### 基本的な使い方

* OSのソフトウェアパッケージを扱うためのリソース。
* プラットフォームにあわせてパッケージを選択してくれる。

```
package "git" do
  action :install
end
```

#### 複数パッケージをインストールする

* Rubyのシンタックス使う。

```
%w{gcc make git}.each do |pkg|
  package pkg do
    action :install
  end
end
```

#### バージョンを指定する

* バージョン指定でバージョンを固定できる。
* action:installを:upgradeにすると、レシピを複数回実行したとき既存のパッケージが古い場合に最新版に入れ替える。

```
package "perl" do
  action :install
  version "5.10.1"
end
```


#### パッケージを削除する

```
package "perl" do
  action :remove
end
```

#### パッケージを指定する

* パッケージを指定したファイルからインストールする

```
package "tar" do
  action :install
  source "/tmp/tar-1.16.1-1.rpm"
end
```


#### オプションを指定する

```
package "debian-archive-keyring" do
 action :install
 options :"--force-yes"
end
```

### service

* Webサービスやデータベースのような「サービス」の状態を記述するのがserviceリソース。

#### 基本的な使い方

* Apacheを起動する
* action行でサービスを有効かつ起動している。
* support行でstatus,restart,reloadを有効にしている。

```
service "httpd" do
  action [ :enable, :start ]
  supports :status => true, :restart => true, :reload => true
end
```

#### Notificationとserviceを組み合わせる

* httpd.confが更新された場合にreloadする
* notifiesは第一引数にアクション、第二引数にresource_type[resource_name]の形式で記述する。
* notifies(通知先)に指定できるのはserviceに限らない。以下のようにexecuteリソースも実行できる。  
notifies :run, "execute[test-nagios-config]"

```
service "httpd" do
  supports :status => true, :restart => true, :reload => true
  action [ :enable, :start ]
end

template "httpd.conf" do
  path "/etc/httpd/conf/httpd.conf"
  owner "root"
  group "root"
  mode 0644
  notifies :reload, 'service[httpd]'
end
```

#### Notificationのタイミング

* Notificationの実行は、Chef全体の実行の限りなく終盤。
* これにより通知を送る側はあまり順番を気にしなくていい。
* notifiesの第三引数に :immediately を渡すと即座に実行される。

#### Subscribe

* なんらかのリソースに変化があったらアクションに利用するのがSubscribe。
* 以下は、td-agent.confが更新されたらサービスをrestartするというアクション。

```
service "td-agent" do
  support :status => true, :restart => true, :reload => true
  action [ :enable, :start]
  subscribes :restart, "template[ts-agent.conf]"
end
```

### template

* 設定ファイルなど外部ファイルを使うリソース。
* Attrbuteの値をテンプレート内で展開したい場合に利用する。
* Attrbuteを使わなければ静的ファイルを操作する目的のcookbook_fileを使って定義する。

#### 基本的な使い方

* httpd.confをテンプレートから生成する。
* テンプレートはクックブックディレクトリ内のtemplates/default/httpd.conf.erbが利用される。

```
template "httpd.conf" do
  path "/etc/httpd/conf/httpd.conf"
  source "httpd.conf.erb"
  owner "root"
  group "root"
  mode 0644
end

// 簡略化
template "/etc/httpd/conf/httpd.conf" do
  source "httpd.conf.erb"
  owner "root"
  group "root"
  mode 0644
end

// さらに簡略化(templates/default/httpd.conf.erbが利用される)
template "/etc/httpd/conf/httpd.conf" do
  owner "root"
  group "root"
  mode 0644
end
```

#### テンプレート内ではAttrbuteが使える

* たとえば、node[:platform] は　<%= node[:platform] %> と書く。
* ChefのtemplateリソースはERBが前提。

### userとgroup

#### user

* ユーザー定義の例

```
user "naoya" do
  comment "naoya"
  home "/home/naoya"
  shell "/bin/bash"
  password nil
  supports :manage_home => true // ユーザを新規作成した時にホームディレクトリを一緒に作る
  action   [:create, :manage]
end
```

#### group

* グループ追加の例

```
group "webdb" do
  gid 999
  members ['naoya', 'inao']
  action :create
end
```

* 既存グループへのユーザ追加の例

```
group "admin" do
  action :modify
  members [ 'dikeda' ]
  append true
end
```

### directory

* ディレクトリの定義

```
directory '/etc/td-agent/' do
  owner  'td-agent'
  group  'td-agent'
  mode   '0755'
  action :create
end
```

### cookbook_file

* cookbook_fileを使うと、クックブックに同梱したファイルを任意のパスへ転送できる。
* templateはAttrbuteを使いたい場合に。cookbook_fileは静的なファイルを扱いたい場合に。

#### 基本

* RPMファイルをテンポラリディレクトリに転送する
* テンポラリディレクトリのパスはハードコードせずにChefが設定で持っているパスを使用する。

```
cookbook_file "#{Chef::Config[:file_cache_path]}/supervisor-3.0a12-2.el6.noarch.rpm" do
  mode 00644
end
```

* ソースファイルを明示的に指定する。(転送先で名前を変更する)

```
cookbook_file "#{Chef::Config[:file_cache_path]}/supervisor-3.0.rpm" do
  source "supervisor-3.0a12-2.el6.noarch.rpm"
  mode 00644
end
```

#### チェックサムを利用する

* 破損や改さん防止にチェックサムを使う。
* チェックサムが合致しない場合は実行時エラーになる。
* ChefのファイルチェックサムはSHA-256が前提。

```
cookbook_file "#{Chef::Config[:file_cache_path]}/supervisor-3.0a12-2.el6.noarch.rpm" do
  source "supervisor-3.0a12-2.el6.noarch.rpm"
  mode 00644
  checksum "012f34db9e08f67e6060d7ab8d16c264b93cba82fb65b52090f0d342c406fbf7"
end
```

### インフラレイヤーのリソース

#### ifconfig

```
ifconfig "192.168.30.1" do
  device "eth0"
end
```

#### mount

```
mount "/mnt/volume1" do
  device "volume1"
  device_type :label
  fstype "xfs"
  options "rw"
end
```

### script

* scriptリソースは内部に定義したシェルスクリプトなどのスクリプトをroot権限で実行できる。
* 冪等性は自分で保証する必用がある。

#### script(bash)

* perbrewをインストールする際にcurlでインストーラーを実行するが、シェルスクリプトによるインストーラーの扱いがpackageでは難しいので以下のように記述する。

```
# shellの場合
$ curl -kL http://install.perlbrew.pl | bash
```

```
// Chefの場合
bash "install perlbrew" do
  user 'vagrant'
  group 'vagrant'
  cwd '/home/vagrant' // カレントワーキングディレクトリ
  environment "HOME" => '/home/vagrant' // 環境変数
  // ヒアドキュメントでscriptを定義
  code <<-EOC
    curl -kL http://install.perlbrew.pl | bash
  EOC
  creates "/home/vagrant/perl5/perlbrew/bin/perlbrew"　  // このコマンドがこのファイルを作成するであろうことを指示し、かつすでにそのファイルがある場合はこのコマンドを実行しない
end
```

* perlbrewの実行ファイルが存在するかどうかをインストールの有無の判定に使っているのが欠点。

#### not_if, only_if

* not_if: 指定した条件が真でないならコマンドを実行する。
* only_if: 指定した条件が真のときのみコマンドを実行する。
* たとえば、createsと同じ事は、  
not_if {File.exists?("/home/vagrant/perl5/perlbrew/bin/perlbrew")}  
と記述することもできる。


```
// not_ifの使用例
// rubybuildの存在有無をガード節に使っている
bash "install-rubybuild" do
  not_if 'which ruby-build'
  code <<-EOC
    cd /tmp/ruby-build
    ./install.sh
  EOC
end
```

```
// not_ifやonly_ifでの条件判定の例
not_if <<-EOC, :user => 'vagrant', :environment => { 'HOME' => '/home/vagrant' }
    略
EOC
```

## 3.4 そのほかのリソース

* ドキュメント参照

## 3.5 AttributeとData Bag

* Attribute: ノードに紐つく属性のうち、固定的にあつかうものではないもの。
* Data Bag: ユーザの各種データなどノードの属性でもない、リソース属性でもないものを定義する。

### Attribute

* Nodeオブジェクトの記述例

```
{
  "httpd" : {
    "port" :80
  },
  "run_list": [
    "recipe[git]",
    "recipe[apache]",
    "recipe[mysql]"
  ]
}
```

* node['httpd']['port']のようにキーに文字列を使うのが慣習。

### Attributeの初期値

* Attributeはデフォルト値を決めておくことができる。
* 初期値があらかじめクックブック内に定義しておく
* JSONファイルでノードごとに値を定義しておく

```
default["apache"]["dir"] = "/etc/apache2"
default["apache"]["listen_ports"] = [ "80", "443" ]
```

### Data Bag

* 各ノードで共有するようなデータはDataBagを使って定義する。
* 複数ユーザがいる状態を定義したい場合など。

#### 各ノードで共有したいデータを準備する

* DataBagで扱うデータは＜リポジトリ＞/data_bagsディレクトリに置く。

```
# Data Bagディレクトリの構造例
├─ Berksfile
├─ Vagrantfile
├─ cookbooks
├─ data_bags
    ├─ users
        ├─ naoya,json
        ├─ inao.json

```
{
  "id" : "naoya",
  "username" : "naoya",
  "home" : "/home/naoya",
  "shell" : "/bin/bash"
}
```

```
{
  "id" : "inao",
  "username" : "inao",
  "home" : "/home/inao",
  "shell" : "/bin/inao"
}
```

#### データを利用する

* Data Bagからの値の取得例

```
# login_users/recipes/default.rb
data_ids = data_bag('users')

data_ids.each do |id|
  u = data_bag_item('users', id)
  user u['username'] do
    home  u['home']
    shell u['shell']
  end
end
```

#### データを暗号化する

* Data Bagにはパスワードや鍵等のデータを暗号化して格納しセキュアに扱う方法もある。
  * chef solo encrypted data bags
  * knife solo data bag


## 3.6 クックブックのディレクトリレイアウト

* ドキュメント参照