Chefでnginx + php(php-fpm) + mariadb + wordpressを設置する。


## 関連記事

* [Chefを使ってより効率的にサーバを管理しよう。まずは用語から](http://qiita.com/park-jh/items/9e2e358d2e82a7736cf7)
* [Berkshelfを使って外部クックブックをノードに適用する。](http://qiita.com/park-jh/items/751464a4cc6d8abcd6fd)
* [クックブックを作成してノードに適用する。- その１](http://qiita.com/park-jh/items/8d641809850b5930d39c)
* [クックブックを作成してノードに適用する。- その２](http://qiita.com/park-jh/items/88237342cfd202e6893f)
* [ChefでCentOS 6.7 + rbenv + ruby 2.3.0の環境を構築する。](http://qiita.com/park-jh/items/f249a18793899f2eb4bf)
* [ChefでCentOS 6.7 + Vim 7.4 + luaを設置する。](http://qiita.com/park-jh/items/371d179d5e71618a1f14)
* [ChefでVimプラグインマネージャーNeoBundleを設置する。](http://qiita.com/park-jh/items/ec09e40f9b4aaf9fedfd)
* [Chefのレシピをデバッグしてみよう。](http://qiita.com/park-jh/items/96a076e167327a6a5a39)
* [ChefでCentOS 6.7 + nginxを設置する。- 外部クックブック](http://qiita.com/park-jh/items/3013c49494e524fced31)
* [ChefでCentOS 6.7 + nginxを設置する。- カスタムクックブック](http://qiita.com/park-jh/items/43451cea21ee1724c24b)
* [ChefでCentOS 6.7 + nodejs + npmを設置する。](http://qiita.com/park-jh/items/96089e7e1c97b0fe3a6c)
* [ChefでVimプラグインマネージャーvim-plugを設置する。](http://qiita.com/park-jh/items/ba27ad412ce9926fd94d)
* [ohaiのカスタムプラグインを作ってみよう。](http://qiita.com/park-jh/items/34be55abb7c63b36cddb)
* [Chef SoloからChef Zeroへ](http://qiita.com/park-jh/items/44c249c9de768c53b423)
* Chefでnginx + php(php-fpm) + mariadb + wordpressを設置する。

## サーバ構成

wordpressのサーバを構築するには次のような方法がある。

* apacheの場合
  - phpをモジュール（mod_php）として組み込む。
  - またはphpをCGI（Common Gateway Interface）として動作させる。
* nginxの場合
  - apacheのCGI版と同様にWebサーバと別のプロセス（php-fpm）でphpを起動させる。
  - nginxとの通信はTCPソケットやUNIXドメインソケットを用いる。

## apacheよりnginx

* apacheはリクエストの処理をスレッドで行う。
* nginxは非同期のアーキテクチャを用いるイベントループモデルを採用している。
* 多数のリクエストが来た場合に実行スタックをコピーする必要があるスレッドモデルと違って１プロセスで複数のリクエストを処理できる。
* そのため、CPUの負荷やメモリの消費量が少ない。
* nginxの設定ファイルがapacheより分かりやすい。

## 構築手順

1~3までは以前作成したレシピをそのまま利用する。4~5は新規で作成する。

1. yum-epel、yum-updateレシピを実行
2. mariadbレシピを実行
3. nginxレシピを実行
4. php-envレシピ実行
5. wordpressレシピを実行

## 既存レシピの修正

### nginx

* レシピ
  - nginxをtarballで設置するので、コンパイルのためにgcc、gcc-c++、openssl-develなどが必要
  - 以前はruby-envというレシピの中で書いていたので、nginxのレシピには書かなかった。
  - 今度はruby-envレシピは使わないため、nginxのレシピに追加した。
  - pcre、pcre-develはHTTP rewrite moduleを使うために必要

```diff
% git --no-pager diff site-cookbooks/                                          (git)-[master] <U>
diff --git a/site-cookbooks/nginx/recipes/default.rb b/site-cookbooks/nginx/recipes/default.rb
index f7b4d95..e6f4f9e 100644
--- a/site-cookbooks/nginx/recipes/default.rb
+++ b/site-cookbooks/nginx/recipes/default.rb
@@ -12,7 +12,7 @@ nginx_url = node['nginx']['url']
 nginx_filename = "nginx-#{node['nginx']['ver']}.tar.gz"
 src_filepath  = "#{Chef::Config['file_cache_path'] || '/tmp'}/#{nginx_filename}"

-%w(pcre pcre-devel).each do |pkg|
+%w(gcc gcc-c++ openssl-devel pcre pcre-devel).each do |pkg|
   package pkg do
     action :install
   end
```

* テンプレート（site-cookbooks/nginx/templates/default/default.erb）
  - バーチャルホストとして設定されていないIPアドレスまたはドメイン名へのリクエストは拒否するように修正
  - そうしないと複数のバーチャルホストが設定されている場合、あるバーチャルホストに問題が発生するとデフォルトのサイトが表示される。

```diff
diff --git a/site-cookbooks/nginx/templates/default/default.erb b/site-cookbooks/nginx/templates/default/default.erb
index 9be8bc7..9a13cf0 100644
--- a/site-cookbooks/nginx/templates/default/default.erb
+++ b/site-cookbooks/nginx/templates/default/default.erb
@@ -1,11 +1,5 @@
 server {
-  listen   <%= node['nginx']['port'] %>;
-  server_name  dev;
-
-  access_log  /var/log/nginx/localhost.access.log;
-
-  location / {
-    root   /var/www/nginx-default;
-    index  index.html index.htm;
-  }
+  listen   <%= node['nginx']['port'] %> default_server;
+  server_name _;
+  deny all;
 }
```

* テンプレート（site-cookbooks/nginx/templates/default/nginx.conf.erb）
  - mainのログフォーマットを設定してaccess_logの出力に利用する。

```diff
diff --git a/site-cookbooks/nginx/templates/default/nginx.conf.erb b/site-cookbooks/nginx/templates/default/nginx.conf.erb
index c1ae863..8fbbb9a 100644
--- a/site-cookbooks/nginx/templates/default/nginx.conf.erb
+++ b/site-cookbooks/nginx/templates/default/nginx.conf.erb
@@ -13,7 +13,10 @@ http {
   include       /etc/nginx/mime.types;
   default_type  application/octet-stream;

-  access_log    /var/log/nginx/access.log;
+  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;
   tcp_nopush on;
```

### yum-update

* レシピ
  - telnetとbind-utilsをyumで設置するように追加する。
  - 障害発生原因を調べるとき有用である。

```diff
diff --git a/site-cookbooks/yum-update/recipes/default.rb b/site-cookbooks/yum-update/recipes/default.rb
index 4056ce8..099d1ae 100644
--- a/site-cookbooks/yum-update/recipes/default.rb
+++ b/site-cookbooks/yum-update/recipes/default.rb
@@ -7,7 +7,16 @@
 # All rights reserved - Do Not Redistribute
 #

-execute 'yum-update' do
+%w(telnet bind-utils).each do |pkg|
+  execute "install #{pkg}" do
+    user 'root'
+    command "yum -y install #{pkg}"
+    action :run
+    not_if "yum list installed | grep #{pkg}"
+  end
+end
+
+execute 'update yum' do
   user 'root'
   command 'yum -y update'
   action :run
```

## php-env

php開発環境を構築する。

### php関連パーケージを設置

* php-fpm：nginxでphpを利用するために必要
* php-mysql：データベースとしてmysql（またはmariadb）を利用するために必要
* php-mbstring：マルチバイト（日本語など）を扱うために必要
* php-gd：GDライブラリで画像を扱うために必要

### 注意事項

* phpは設置しない。
* centosの場合、yumでphpを設置すると依存関係にあるhttpd（apache）も一緒に設置される。
* 今度はnginxを利用するので、apacheの設置は不要だ。
* php-fpmなどのパーケージを設置するときphp-commonのパーケージが一緒に設置されるので、phpを使うことには問題がない。

### クックブックの生成

* 雛形生成

```sh
% bin/knife cookbook create php-env -o site-cookbooks
```

* レシピ（site-cookbooks/php-env/recipes/default.rb）

```ruby
%w(php-fpm php-mysql php-mbstring php-gd).each do |pkg|
  package pkg do
    action :install
    notifies :restart, "service[php-fpm]"
  end
end

template '/etc/php-fpm.d/www.conf' do
  source 'www.conf.erb'
  owner 'root'
  group 'root'
  mode '0644'
  notifies :restart, 'service[php-fpm]'
end

service "php-fpm" do
  action [:enable, :start]
end
```

* テンプレート（site-cookbooks/php-env/templates/default/www.conf.erb）

```ruby
# TCPソケットではなくunixドメインソケットを利用する。
listen = /var/run/php-fpm.sock

;listen.allowed_clients = 127.0.0.1

listen.owner = <%= node['nginx']['user'] %>
listen.group = <%= node['nginx']['group'] %>
listen.mode = 0666

user = <%= node['nginx']['user'] %>
group = <%= node['nginx']['group'] %>
```

## wordpress

### クックブックの生成

* 雛形生成

```sh
% bin/knife cookbook create wordpress -o site-cookbooks
```

* アトリビュート（site-cookbooks/wordpress/attributes/default.rb）

```ruby
default['wordpress']['url'] = 'http://wordpress.org/latest.tar.gz'
default['wordpress']['name'] = 'wordpress'
default['wordpress']['port'] = '80'
default['wordpress']['sql'] = <<-EOH
  grant all privileges on #{node['wordpress']['name']}.* to "#{node['wordpress']['name']}"@"localhost" identified by "hogehoge"
EOH
```

* ファイル（site-cookbooks/wordpress/files/default/got_rewrite.php）

```
% vi site-cookbooks/wordpress/files/default/got_rewrite.php

<?php
add_filter('got_rewrite','__return_true');
?>
```

* レシピ（site-cookbooks/wordpress/recipes/default.rb）

```ruby
wordpress_url = node['wordpress']['url']
wordpress_name = node['wordpress']['name']
wordpress_filename = wordpress_url.split("/").last
src_filepath  = "#{Chef::Config['file_cache_path'] || '/tmp'}/#{wordpress_filename}"

remote_file wordpress_url do
  source wordpress_url
  path src_filepath
  backup false
end

bash "install #{wordpress_name}" do
  cwd File.dirname(src_filepath)
  code <<-EOH
    mkdir -p /var/www
    tar zxf #{wordpress_filename} -C /var/www
    chown #{node['nginx']['user']}:#{node['nginx']['group']} -R /var/www/#{wordpress_name}
  EOH
  not_if { File.exists?("/var/www/#{wordpress_name}") }
end

cookbook_file "/var/www/#{wordpress_name}/wp-content/plugins/got_rewrite.php" do
  source 'got_rewrite.php'
  owner node['nginx']['user']
  group node['nginx']['group']
  mode '0644'
  not_if { File.exists?("/var/www/#{wordpress_name}/wp-content/plugins/got_rewrite.php") }
end

bash "create database to use #{wordpress_name}" do
  cwd File.dirname(src_filepath)
  code <<-EOH
    mysqladmin -uroot create #{wordpress_name}
    mysql -uroot #{wordpress_name} -e '#{node['wordpress']['sql']}'
  EOH
  only_if "ps aux | grep mysqld | grep -v grep"
  not_if "mysqlshow -uroot | grep #{wordpress_name}"
end

template "/etc/nginx/sites-available/#{wordpress_name}" do
  source "#{wordpress_name}.erb"
  owner 'root'
  group 'root'
  mode '0644'
end

link "/etc/nginx/sites-enabled/#{wordpress_name}" do
  to "/etc/nginx/sites-available/#{wordpress_name}"
  notifies :reload, 'service[nginx]'
end
```

* テンプレート（site-cookbooks/wordpress/templates/default/wordpress.erb）

```ruby
% vi site-cookbooks/wordpress/templates/default/wordpress.erb

server {
  listen   <%= node['wordpress']['port'] %>;
  server_name  wordpress.example.com;
  root   /var/www/<%= node['wordpress']['name'] %>;
  index  index.php index.html index.htm;
  charset utf-8;

  access_log  /var/log/nginx/<%= node['wordpress']['name'] %>.access.log main;
  error_log   /var/log/nginx/<%= node['wordpress']['name'] %>.error.log warn;

  location ~ \.php$ {
    try_files $uri =404;

    expires off;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;

    fastcgi_buffers 16 16k;
    fastcgi_buffer_size 32k;
    fastcgi_busy_buffers_size 32k;
    fastcgi_temp_file_write_size 256k;
  }

  location ~ /\. {
    deny all;
  }
}
```

### 注意事項

* locationディレクティブには優先順位がある。
* 直接マッチを正規表現マッチより上に記述しても正規表現のほうが先に読み込まれる。
* 次の場合、正規表現マッチが先に読み込まれて$document_rootが認識されず*.phpページが404で表示される。

```ruby
location / {
  root   /var/www/<%= node['wordpress']['name'] %>;
  index  index.php index.html index.htm;
  charset utf-8;
}

location ~ \.php$ {
  try_files $uri =404;

  expires off;
  fastcgi_split_path_info ^(.+\.php)(/.+)$;
  fastcgi_pass unix:/var/run/php-fpm.sock;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  include fastcgi_params;

  fastcgi_buffers 16 16k;
  fastcgi_buffer_size 32k;
  fastcgi_busy_buffers_size 32k;
  fastcgi_temp_file_write_size 256k;
}
```

* 基本的なroot、index、charsetなどはlocationディレクティブの中ではない外で書いたほうがいいだろう。
* ここのレシピではmariadb(mysql)のrootのパスワードが設定されてない。
* 実際本番環境で使う場合はパスワードを設定して使う。

## roleの生成

* wordpressというroleを生成して必要なクックブック（レシピ）を追加する。

```sh
% bin/knife role create wordpress
```

* roles/wordpress.json
* nginxとphp-fpmのworkerプロセスを実行するユーザとグループを指定する。

```json
{
  "name": "wordpress",
  "default_attributes": {
    "nginx": {
      "user": "neowiz",
      "group": "neowiz"
    }
  },
  "run_list": [
    "recipe[yum-epel]",
    "recipe[yum-update]",
    "recipe[mariadb]",
    "recipe[nginx]",
    "recipe[php-env]",
    "recipe[wordpress]"
  ]
}
```

* nodeのrun_listに該当roleを追加する。

```sh
# vagrantで生成したnodeの名前がwpの場合
% bin/knife node run_list add wp 'role[wordpress]' -z
```

## 適用

* chef-zero（chef clientをlocalモードで実行することによってchef soloと同様な結果を出す）を使ってnodeへ適用する。

```sh
# vagrantで生成したnodeの名前がwpの場合
% bin/knife zero converge 'name:wp' --sudo
```

## 確認

* ブラウザからwordpress.example.com（/etc/hostsに設定が必要）を入力する。
* wordpressの初期設定画面（言語を選択する画面）が表示される。

## 参考

* http://blog.livedoor.jp/saba_nano/archives/28213171.html
* https://ja.wordpress.org/
* WordPressプロフェッショナル養成読本