これから始めるChefのチュートリアル　応用編

## はじめに
本記事は[これからはじめるChefのチュートリアル　基本編](http://qiita.com/syou007/items/70006424d66e3f41152d)の続編です。
はじめに[これからはじめるChefのチュートリアル　基本編](http://qiita.com/syou007/items/70006424d66e3f41152d)を実施してから進めてください。

## 環境構築　その５　パッケージインストール
パッケージをインストールしてみます。
今回は`nginx`を例にするので以下のクックブックを作成します。

```sh
$ bundle exec knife cookbook create nginx -o site-cookbooks/
```

内容を記載します。

```rb:site-cookbooks/nginx/recipes/default.rb
# yum install nginx
package "nginx"
```

以下のようにも記載できます。

```rb:site-cookbooks/nginx/recipes/default.rb
package "nginx" do
  action :install
end
```

`node`に追加します。

```json:nodes/chef-centos65.json
{
  "run_list": [
    "ruby",
    "nginx"
  ]
}
```

サーバーに反映します。
簡単ですね！

```sh
$ bundle exec knife solo cook chef-centos65
```

と、思ったら以下のエラーが発生しました。

```
No candidate version available for nginx
```

[前回](http://qiita.com/syou007/items/70006424d66e3f41152d#berkshelf%E3%81%AE%E8%A8%AD%E5%AE%9A)の記事で定義した`yum-epel`がここで役立ちます。

`node`に`yum-epel`を追加します。
これによって`EPELリポジトリ`が使えるようになります。

```json:nodes/chef-centos65.json
{
  "run_list": [
    "ruby",
    "yum-epel",
    "nginx"
  ]
}
```

この状態にして再実行します。

```sh
$ bundle exec knife solo cook chef-centos65
```

確認してみます。

```sh
$ ssh chef-centos65
$ service nginx status
nginx is stopped
$ exit
```

パッケージが入りました。

## 環境設定　その６　パッケージの設定
この状態では`nginx`は起動してません。
早速起動する命令を書きましょう

```rb:site-cookbooks/nginx/recipes/default.rb
package "nginx"
service "nginx" do
  # 使用できるオプションを指定
  supports status: true, restart: true, reload: true
  # 実際に行うコマンド
  action [:enable, :start]
end
```

この状態にして再実行します。

```sh
$ bundle exec knife solo cook chef-centos65
```

確認してみます。

```sh
$ ssh chef-centos65
$ service nginx status
nginx (pid  12345) is running...
$ exit
```

起動してますね。
ここから`nginx`の設定を書いていきます。

※サーバーに入り込んで設定を追加していっても良いのですが、[前回](http://qiita.com/syou007/items/70006424d66e3f41152d)にも書いたように冪等性を保てなくなります。
ここは全て`chef`でやりましょう。

まず以下のファイルを作成します。

```site-cookbooks/nginx/templates/default/nginx.conf.erb
user              nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/vhost.d/*.conf;
}
```

上記のものはインストールされた`nginx.conf`に記載されていた設定ファイルから、コメント部分を取り除いて`include /etc/nginx/vhost.d/*.conf;`を追加したものになります。

Nginxの設定内容を知りたい方は[こちら](http://qiita.com/syou007/items/3e2d410bbe65a364b603)を読んでください。
今回は設定ファイルの書き換えなどの方法を学ぶためのものなので、内容は何でも良いです。

このファイルを実際に配置します。

```rb:site-cookbooks/nginx/recipes/default.rb
package "nginx"

service "nginx" do
  supports status: true, restart: true, reload: true
  action [:enable, :start]
end

# テンプレート機能を使用
template "nginx.conf" do
  # 配置する箇所（上記の"nginx.conf"部分に以下を記載してもOK）
  path "/etc/nginx/nginx.conf"
  # 使用するテンプレートを指定
  source "nginx.conf.erb"
  # 権限を指定
  owner "root"
  group "root"
  mode 0644

  # 配置後に再起動を促します。
  notifies :reload, "service[nginx]"
end

# ヴァーチャルホストの設定を配置するディレクトリを作成しておきます。
directory '/etc/nginx/vhost.d' do
  owner 'root'
  group 'root'
  mode  0755
end
```

この状態にして再実行します。

```sh
$ bundle exec knife solo cook chef-centos65
```

確認してみます。

```sh
$ ssh chef-centos65
$ service nginx status
nginx (pid  12345) is running...
$ cat /etc/nginx/nginx.conf
上記で記載した内容が表示される。
$ ls -l /etc/nginx/
# vhost.dが指定した権限で作成されている。
drwxr-xr-x. 2 root root 4096 12月  3 09:00 2015 vhost.d
$ exit
```

このように`chef`のレシピではファイルの作成やディレクトリの作成ができます。
また、権限の指定などもできます。

### 深い階層のディレクトリを作る場合
深いディレクトリを作るときには以下の方法で行えます。

```rb
directory '/dir1/dir2/dir3' do
    owner 'root'
    group 'root'
    mode '0600'
    # このメソッドを指定すると「mkdir -p」のように作成できる。
    recursive true
end
```

ただし、この方法では最後のディレクトリのみ権限が付与されるので間のディレクトリにも権限を付与させたい場合は以下のように行う。

```rb
%w[
    /dir1
    /dir1/dir2
    /dir1/dir2/dir3
].each do |path|
    directory path do
        owner 'root'
        group 'root'
        mode '0600'
    end
end
```

## 環境構築　その７　設定ファイルの汎用化
`ruby`を知っている人は気づいたかもしれませんが、`template`で使用しているファイルは`erb`ファイルになってます。
なので、この中でもプログラムを書くことができます。

今回は変更できる事を確認するために`worker_processes`を変更してみます。

```json:nodes/chef-centos65.json
{
  "nginx": {
    "worker_processes": 2
  },
  "run_list": [
    "ruby",
    "yum-epel",
    "nginx"
  ]
}
```

`template`を一部変更します。

```site-cookbooks/nginx/templates/default/nginx.conf.erb
user              nginx;
worker_processes  <%= node["nginx"]["worker_processes"] %>;
...(略)
```

テンプレートの中身を変えて`chef`を実施します。

```sh
$ bundle exec knife solo cook chef-centos65
...
    --- /etc/nginx/nginx.conf	2015-12-02 09:14:33.395852109 +0000
    +++ /etc/nginx/.nginx.conf20151203-15729-niu7bb	2015-12-03 01:51:58.174817582 +0000
    @@ -1,5 +1,5 @@
     user              nginx;
    -worker_processes  1;
    +worker_processes  2;
     
     error_log  /var/log/nginx/error.log;
     pid        /var/run/nginx.pid;
...
```

このように変更点を教えてくれます！
素晴らしいですね。

実際に確認してみます。

```sh
$ ssh chef-centos65
$ cat /etc/nginx/nginx.conf
user              nginx;
worker_processes  2;
...
$ exit
```

変更されてますね。

あとは、未設定の場合の対応を行います。

```rb:site-cookbooks/nginx/attributes/default.rb
default["nginx"]["worker_processes"] = 1
```

だいぶ色々な事ができるようになってきました。
ここまでできればほとんどのサーバー構築はできるようになっているかと思います。

## 環境構築　その８　ユーザー作成
初めのユーザーをそのまま使っても良いのですが、chefが操作するユーザーは強力なのでできれば他のユーザーを用意したいところです。

なので、ユーザーを追加するレシピを作成します。
ここでは鍵認証で接続できるユーザーを作成します。

### 鍵の作成
まずは鍵を作成します。
鍵を作るときに`~/.ssh/id_rsa`を上書きしないように注意してください。
上書きすると取り返しがつかない事になると思いますので、注意してください。

```sh
# 鍵を配置するフォルダを作成します。
$ mkdir ssh
# 鍵を作成します。
$ ssh-keygen -t rsa
# この質問に対してEnterを押さないこと。「ssh/id_rsa」を入力してください。
Enter file in which to save the key (/Users/ユーザー名/.ssh/id_rsa): ssh/id_rsa
# パスフレーズは不要です。Enterを押してください。
Enter passphrase (empty for no passphrase): 
Enter same passphrase again:
# 公開鍵を表示します。
$ cat ssh/id_rsa.pub
ssh-rsa AAAAB3***.local
```

作成した鍵は後で使用します。
`ssh-rsa AAAAB3***.local`と書かれた長い文字列をコピーしておきます。

### ユーザー作成のレシピを作成
ユーザー作成用のレシピを作成します。

```
$ bundle exec knife cookbook create user -o site-cookbooks/
```

ユーザー作成のレシピを作成します。

```rb:site-cookbooks/user/recipes/default.rb
# 適当なグループを作成します。
group "test_grp" do
  gid 403
end

# SSHでログインするためのユーザーを作成します。
user "ssh_user" do
  uid 403
  gid 403
end

# 公開鍵を設置するフォルダを作成します。
directory "/home/ssh_user/.ssh" do
  mode 0755
  owner "ssh_user"
end

# 公開鍵を設置します。
file "/home/ssh_user/.ssh/authorized_keys" do
  mode 0644
  owner "ssh_user"
  # 先ほどの公開鍵を貼り付けます。
  content "ssh-rsa AAAAB3***.local"
end

# オーナーを変更しておきます。
directory "/home/ssh_user" do
  mode 0755
  owner "ssh_user"
end
```

作成したレシピをnodeに追加します。

```json:nodes/chef-centos65.json
{
  "run_list": [
    "user",
    "ruby",
    "yum-epel",
    "nginx"
  ]
}
```

それでは`chef`を実施してみます。

```sh
$ bundle exec knife solo cook chef-centos65
```

現在作成したユーザーがログインできるか確認します。

```sh
$ ssh ssh_user@192.168.33.10 -i ssh/id_rsa
[ssh_user@localhost ~]$
$ exit
```

ログインできることが確認できました。
これで、ユーザー作成も完成です。

※今回はパスワードを設定しないので`ruby-shadow`は使用してません。

### 汎用化
今回は説明を割愛しますが、今まで同様で必要に応じて汎用化しておくとレシピが使いやすくなります。

#### Capistranoを使う方へ
`Capistrano`を使う方は、ここで作成した鍵を使用する事になると思います。
なので、ディレクトリ構成も少し考えると良いと思います。

例えば、以下のような構成にするなどの工夫をすると環境用構築一式を管理できるようになります。

```
・アプリ名のディレクトリ(git管理)
　∟chef・・・アプリのサーバーを構築するchefレシピを保管
　∟capistrano・・・アプリをデプロイするcapistranoを保管
　∟user・・・デプロイユーザーの鍵を保管
```

## sudoやssh制限について
今までの知識があれば`/etc/sudoers`を変更する事も簡単に行えると思います。
また、rootユーザーのSSH禁止（`/etc/ssh/sshd_config`）などもできるようになっていると思います。
本記事では扱いませんが、必要に応じてやってみてください。

いままではSSH制限やrootユーザーに関わる変更はとても怖い変更でした。
しかし`chef`を使っていれば、事前に仮想OSで実験する事ができる上にその後失敗する事がありません。
こういう利点を生かして`chef`をどんどん使っていけると良いですね。

## クックブックの探し方
ここまでできればサーバー構築で困ることはほとんどないはずです。
その気になれば自前で全て書くこともできると思います。
しかし、それは`車輪の再発明`になるかもしれません。

世の中のクックブックはここから探せます。
[CHEF SUPERMARKET](https://supermarket.chef.io/cookbooks-directory)

ここまで作成した過程で`Berkshelf`を入れているので、積極的に活用していきましょう！

## roleの使い方
最後になりますが、roleの説明を簡単にします。
今までのチュートリアルのやり方だと`node`ファイルが膨大になる可能性があります。

小規模で使うなら`role`は不要ですが、大規模で使用する場合は`role`を使うとうまくまとめてレシピを管理することができます。

例えば以下のような構成のときに微妙に違う`node`を書く必要が出てきます。

```
webサーバー２台・・・ユーザー設定とapacheやtd_agentなどを入れたい。
アプリサーバー2台・・・ユーザー設定とnginxやruby、mysql::clientなどを入れたい。
DBサーバー１台・・・ユーザー設定とmysql::serverなどを入れたい。
監視サーバー・・・ユーザー設定とnginxやrubyなどを入れたい。
```

このようなサーバー構築をすると、基本的に導入するレシピは同じだけど上乗せしているレシピがちょっとづつ違うなどで`node`を書くのが大変になってくる。

例えば以下のような長い`node`を書いて、各サーバーに合わせて増減する・・・なんてぞっとしないだろうか？

```json
{
  "run_list": [
    "yum",
    "yum-epel",
    "ntp",
    "monit",
    "nagios",
    ...
    "user",
    "ruby",
    "nginx",
    "apache",
    "mysql",
    "imagemagick"
  ]
}
```

このような時は`role`を使うと便利だ。
`role`はこのようなレシピをまとめてくれる存在だ。

### roleの分け方
`role`は`roles/`配下に配置できる。
例えば基本的な構成は`base.json`に書いて、Webサーバー用の構成は`web.json`に書くなどとすれば先ほどの`node`は以下のように短くなる。

```json
{
  "run_list": [
    "role[base]",
    "role[web]"
    "imagemagick"
  ]
}
```

このように目的別に`role`を作っておくとレシピの管理が楽にできる。
また、roleにも`default_attributes`と言う項目があり、汎用的に使用している変数を定義することができる。
そして、その値を`node`で上書きすることもできるので、便利だ。

### 作ってみる
とりあえず、今まで作ってたものを`role`に移してみよう。

```json:nodes/chef-centos65.json
{
  "run_list": [
    "user",
    "ruby",
    "yum-epel",
    "nginx"
  ]
}
```

あんまり恩恵がないけど、`ruby`以外のレシピを`server`ロールとして作ってみる。

```json:roles/server.json
{
  "name": "server",
  "description": "this is test",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "default_attributes": {
    "nginx": {
      "worker_processes": 3
    }
  },
  "run_list": [
    "user",
    "yum-epel",
    "nginx"
  ]
}
```

nginxのworker_processesを`3`にして`role`を作成するロールを作る。
`node`でロールを読み込む

```json:nodes/chef-centos65.json
{
  "run_list": [
    "ruby",
    "role[server]"
  ]
}
```

それでは`chef`を実施してworker_processesが変更されることを確認する。

```sh
$ bundle exec knife solo cook chef-centos65
```

中身を確認してみる。

```sh
$ ssh chef-centos65
$ cat /etc/nginx/nginx.conf
user              nginx;
worker_processes  3;
...
```

変更されてますね。
念のため`node`で上書きができるかチェックしてみます。

```json:nodes/chef-centos65.json
{
  "nginx": {
    "worker_processes": 2
  },
  "run_list": [
    "ruby",
    "role[server]"
  ]
}
```

実行すると以下のように変更されることが確認できます。

```sh
$ bundle exec knife solo cook chef-centos65
$ ssh chef-centos65
$ cat /etc/nginx/nginx.conf
user              nginx;
worker_processes  2;
...
```

### roleの設定項目について
roleの設定項目は基本的に以下の形式になると思います。
（というか、僕自身が他をしらない・・・）

```json
  "name": "server",
  "description": "this is test",
  "chef_type": "role",
  "json_class": "Chef::Role",
```

* name・・・このロールの名前を入れます。半角スペースなど使えない文字があるので注意してください。
* description・・・ロールの説明を記載します。

## まとめ
いかがだったでしょうか？
`chef`にはまだまだ様々な機能がありますが、このチュートリアルをひととり行えば大抵の事には対応できるようになると思います。

本記事が誰かの役に立てれば幸いです。

## 筆者について
筆者は[Townsoft](http://townsoft.jp)を屋号に活躍（？）しているフリーランスです。
お仕事の依頼なども受けてますので、お気軽にご連絡ください。