PHPの環境構築手順(Chef+VirtualBox+Vagrant)

##概要

###Vagrant+VirtualBox+Chefで開発環境を構築する

|    分類    | 構築する環境 |   バージョン   |
|:-------:|:------------:|:------------:|
|  OS  |  centos  |  6.5  |
| WEBサーバー |  Nginx  |    1.0.15    |
| 言語       |  PHP |  5.5.34 |
| DB         |  PostgreSQL       |  8.4.20  |

###前提

+ ホストOSがOSX(mac)
+ ホストOSにRubyがインストールされていること


##ホストOSの環境作成

[Vagrantのインストール](https://www.vagrantup.com/)
[VirtualBoxのインストール](https://www.virtualbox.org/)

```bash:ホスト
$ sudo gem install bundler
$ bundle init
```

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

gem 'chef'
gem 'knife-solo'
gem 'berkshelf'

```

```bash:ホスト
$ mkdir php_env_by_chef
$ cd php_env_by_chef
```

```bash:ホスト
$ bundle install
```

##ChefSoloの為のファイル作成

###Vagrantfileの作成と初期設定

```bash:ホスト
$ vagrant plugin install vagrant-omnibus
$ vagrant init
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
	config.vm.hostname = "guest"
	# config.vm.box = "ボックス名"
	# config.vm.box_url = "ボックスURL"
	config.vm.box = "opscode-centos-6.5"
	config.vm.box_url = "http://opscode-vm-bento.s3.amazonaws.com/vagrant/virtualbox/opscode_centos-6.5_chef-provisionerless.box"

	# IPアドレスの設定
	config.vm.network "private_network", ip: 	"192.168.33.10"
	
	# vagrant-omnibusの有効化
	config.omnibus.chef_version = :latest	
end
```

(注)以下のエラーが出たら

```
The following berks command failed to execute:

    /usr/local/bin/berks --version --format json

The stdout and stderr are shown below:

    stdout:
    stderr: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/dependency.rb:296:in `to_specs': Could not find 'berkshelf' (>= 0) among 58 total gem(s) (Gem::LoadError)
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/dependency.rb:307:in `to_spec'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_gem.rb:47:in `gem'
	from /usr/local/bin/berks:22:in `<main>'
```

Vagrantfileに以下を追加
(参考)http://qiita.com/futoase/items/a1b5f7e32da847ba6595

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
	config.berkshelf.enabled = false
end
```

###ChefSoloに必要なファイルを展開する

```bash:ホスト
$ knife solo init .
```

##Nginxの導入

###クックブックの作成

```bash:ホスト
$ bundle exec knife cookbook create nginx -o ./site-cookbooks
```

###レシピの作成

```bash:ホスト
$ vi site-cookbooks/nginx/recipes/default.rb
```

```ruby:site-cookbooks/nginx/recipes/default.rb
include_recipe "yum-epel"

package "nginx" do
	action :install
end

service "nginx" do
	action [ :enable, :start ]
	supports :status => true, :restart => true, :reload => true
end
```
###Berksfileの追加とクックブックの更新

```bash:ホスト
$ vi Berksfile
```

```ruby:Berksfile
source "https://api.berkshelf.com"

cookbook "yum-epel"
cookbook "nginx", path: "./site-cookbooks/nginx"
```

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###run_listの作成

```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
	...
	config.vm.provision :chef_solo do |chef|
		chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
		chef.run_list = %w[
			recipe[yum-epel]
			recipe[nginx]
		]
	end
end
```

###Vagrantの起動とプロビジョンの実行

```bash:ホスト
$ vagrant up --provision
```

##PHPを導入

###クックブックの作成

```bash:ホスト
$ bundle exec knife cookbook create php-env -o ./site-cookbooks
```

###レシピの作成

```bash:ホスト
$ vi site-cookbooks/php-env/recipes/default.rb
```

```ruby:site-cookbooks/php-env/recipes/default.rb
%w{php-fpm}.each do |pkg|
	package pkg do
		action :install
		notifies :restart, "service[php-fpm]"
	end
end

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

template "php.ini" do
	source "php.ini.erb"
	path "/etc/php.ini"
	user node['php_env']['user']
	group node['php_env']['user']
	mode 0644
	notifies :restart, "service[php-fpm]"
end
```

###PHPの設定ファイルの作成

(注)今回の場合、事前にPHPのプロビジョンを実行しphp.iniのファイルを以下ディレクトリにコピーしている

```bash:ホスト
$ vi site-cookbooks/php-env/templates/default/php.ini.erb
```

```erb:site-cookbooks/php-env/templates/default/php.ini.erb
...
; PHP's default character set is set to empty.
; http://php.net/default-charset
// チェックを外す
default_charset = "UTF-8"

; Always populate the $HTTP_RAW_POST_DATA variable. PHP's default behavior is
; to disable this feature. If post reading is disabled through
; enable_post_data_reading, $HTTP_RAW_POST_DATA is *NOT* populated.
...
```

###Attributeの初期値を設定

```bash:ホスト
$ vi site-cookbooks/php-env/attributes/default.rb
```

```site-cookbooks/php-env/attributes/default.rb
default["php_env"]["user"] = "root"
```



###Berksfileの追加とクックブックの更新

```bash:ホスト
$ vi Berksfile
```

```ruby:Berksfile
source "https://api.berkshelf.com"
...
cookbook "php-env", path: "./site-cookbooks/php-env"
```

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###run_listの追加


```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
...
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
    chef.run_list = %w[
      recipe[yum-epel]
      recipe[nginx]
      recipe[php-env]
    ]
  end
...
end
```

###プロビジョンの実行

```bash:ホスト
$ vagrant provision
```

##PHP-FPMとNginxを連携する

###Nginxレシピの編集

```bash:ホスト
$ vi site-cookbooks/nginx/recipes/default.rb
```

```ruby:site-cookbooks/nginx/recipes/default.rb
...
template 'nginx.conf' do
	path '/etc/nginx/nginx.conf'
	source "nginx.conf.erb"
	owner 'root'
	group 'root'
	mode '0644'
	notifies :reload, "service[nginx]"
end
```

###Nginxの設定ファイルの作成

```bash:ホスト
$ vi site-cookbooks/nginx/templates/default/nginx.conf.erb
```

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

error_log  /var/log/nginx/error.log warn;
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;

    server {
        listen       80 default_server;
        server_name  _;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm index.php;
        }

        error_page  404              /404.html;
        location = /404.html {
            root   /usr/share/nginx/html;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
        <% if node['nginx']['env'].include?("php") %>
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
        <% end %>
    }
}
```

###Attributeの初期値の設定

```bash:ホスト
$ vi site-cookbooks/nginx/attributes/default.rb
```

```ruby:site-cookbooks/nginx/attributes/default.rb
default['nginx']['env'] = []
```

###クックブックの更新

```bash:ホスト
$ bundle exec berks vendor cookbooks
```


###Attributeの追加

```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|
...
  config.vm.provision :chef_solo do |chef|
    
    chef.json = {
      nginx: {
        env: ["php"]
      }
    }
    ...
  end

end

```

###プロビジョンの実行

```bash:ホスト
$ vagrant provision
```

##OPcacheを導入(php-pecl-zendopcacheパッケージの追加)

###php_envレシピの編集

```bash:ホスト
$ vi site-cookbooks/php-env/recipes/default.rb
```

```ruby:site-cookbooks/php-env/recipes/default.rb
...
# php-pecl-zendopcacheを追加
%w{php-fpm php-pecl-zendopcache}.each do |pkg|
	package pkg do
		action :install
		notifies :restart, "service[php-fpm]"
	end
end
...
```

###クックブックの更新

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###プロビジョンの実行

```bash:ホスト
$ vagrant provision
```

##PHPの接続確認

###ゲストOSにログインしてPHPファイルの作成

```bash:ホスト
$ vagrant ssh
```

```bash:ゲスト
$ sudo vi /usr/share/nginx/html/index.php
```

```php:/usr/share/nginx/html/index.php
<?php
phpinfo();
?>
```

ブラウザで確認
http://192.168.33.10/index.php
(注)80番ポートが解放されていない場合一旦iptablesを停止して確認


```bash:ゲスト
$ sudo /etc/rc.d/init.d/iptables stop
```

##PHP5.5をインストール

###レシピの作成

```bash:ホスト
$ vi site-cookbooks/php-env/recipes/php55.rb
```

```ruby:site-cookbooks/php-env/recipes/php55.rb
yum_repository 'remi' do
  description 'Les RPM de Remi - Repository'
  baseurl 'http://rpms.famillecollet.com/enterprise/6/remi/x86_64/'
  gpgkey 'http://rpms.famillecollet.com/RPM-GPG-KEY-remi'
  fastestmirror_enabled true
  action :create
end

yum_repository 'remi-php55' do
  description 'Les RPM de remi de PHP 5.5 pour Enterprise Linux 6'
  baseurl 'http://rpms.famillecollet.com/enterprise/6/php55/$basearch/'
  gpgkey 'http://rpms.famillecollet.com/RPM-GPG-KEY-remi'
  fastestmirror_enabled true
  action :create
end

%w{php php-fpm php-opcache}.each do |pkg|
  package pkg do
    action :install
    notifies :restart, "service[php-fpm]"
  end
end

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

template "php.ini" do
	source "php.ini.erb"
	path "/etc/php.ini"
	user node['php_env']['user']
	group node['php_env']['user']
	mode 0644
	notifies :restart, "service[php-fpm]"
end
```

###クックブックの更新

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###run_listの追加

```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|

  config.vm.provision :chef_solo do |chef|
    
    chef.run_list = %w[
      recipe[yum]
      recipe[yum-epel]
      recipe[nginx]
      recipe[php-env::php55]
    ]
    
  end

end

```

###プロビジョンの実行

(注)先にインストールしたPHPを削除している

```bash:ホスト
$ vagrant ssh -c "sudo yum remove php* -y"
$ vagrant provision
```

##PostgreSQLの導入

###クックブックの作成

```bash:ホスト
$ bundle exec knife cookbook create postgresql -o ./site-cookbooks
```

###レシピの作成

(参考)[DBユーザーの作成](https://gist.github.com/exoer/1063712)

```bash:ホスト
$ vi site-cookbooks/postgresql/recipes/default.rb
```

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

package "postgresql-server" do
  action :install
end

execute "postgresql-init" do
  not_if "test -f /var/lib/pgsql/data/postgresql.conf"
  command "service postgresql initdb"
  action :run
end

service "postgresql" do
  action [:enable, :start]
end

execute "create-database-user" do
  user "postgres"
  exist = <<-EOH
  psql -U postgres -c "select * from pg_user where usename='#{node['postgresql']['user']}'" | grep -c #{node['postgresql']['user']}
  EOH
  command "createuser -U postgres -SdR #{node['postgresql']['user']}"
  not_if exist
end
```

###Attributeの初期値の設定

```bash:ホスト
$ vi site-cookbooks/postgresql/attributes/default.rb
```

```ruby:site-cookbooks/postgresql/attributes/default.rb
default['postgresql']['super_user'] = "postgres"
default['postgresql']['user'] = "vagrant"
```

###Berksfileの追加とクックブックの更新

```bash:ホスト
$ vi Berksfile
```

```ruby:Berksfile
...
cookbook "postgresql", path: "./site-cookbooks/postgresql"
```

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###run_listの追加

```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|

  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["./cookbooks", "./site-cookbooks"]
    chef.json = {
      nginx: {
        env: ["php"]
      }
    }
    chef.run_list = %w[
      recipe[yum]
      recipe[yum-epel]
      recipe[nginx]
      recipe[php-env::php55]
      recipe[postgresql]
    ]
    
  end

end

```

###プロビジョンの実行

```bash:ホスト
$ vagrant provision
```

##PHPとPostgreSQLの接続

###php55レシピの更新(php-pgsqlパッケージの追加)

```bash:ホスト
$ vi site-cookbooks/php-env/recipes/php55.rb
```

```ruby:site-cookbooks/php-env/recipes/php55.rb
...
#php-pgsqlを追加
%w{php php-fpm php-opcache php-pgsql}.each do |pkg|
   package pkg do
     action :install
end
...
```

###postgresqlレシピにテンプレートの追加

```bash:ホスト
$ vi site-cookbooks/postgresql/recipes/default.rb
```

``````ruby:site-cookbooks/postgresql/recipes/default.rb
...
template "pg_hba.conf" do
  path "/var/lib/pgsql/data/pg_hba.conf"
  source "pg_hba.conf.erb"
  user node['postgresql']['super_user']
  mode "0644"
  notifies :restart, "service[postgresql]"
end
```

###PostgreSQLのクライアント認証設定ファイルの作成

(参考)[pg_hba.confファイル](http://www.dbonline.jp/postgresql/ini/index2.html)

```bash:ホスト
$ vi site-cookbooks/postgresql/templates/default/pg_hba.conf.erb
```

```erb:site-cookbooks/postgresql/templates/default/pg_hba.conf.erb
# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD

# "local" is for Unix domain socket connections only
local   all         all                               trust
# IPv4 local connections:
host    all         all         127.0.0.1/32          trust
# IPv6 local connections:
host    all         all         ::1/128               trust
```

###クックブックの更新

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###プロビジョンの実行

```bash:ホスト
$ vagrant provision
```

###接続テスト

```bash:ホスト
$ vagrant ssh
```

```bash:ゲスト
$ createdb test
$ vi /usr/share/nginx/html/pgsql_connection_test.php
```

```rb:/usr/share/nginx/html/pgsql_connection_test.php
<?php
$dsn = 'pgsql:dbname=test host=localhost port=5432';
$user = 'vagrant';
$password = '';

try{
    $dbh = new PDO($dsn, $user, $password);
    echo 'PostgreSQLと接続成功';
}catch (PDOException $e){
    echo('Error:'.$e->getMessage());
    die();
}
```

ブラウザで確認
http://192.168.33.10/pgsql_connection_test.php

##ホストOSとゲストOSのディレクトリを同期する(rsync)

(参考) [Rsync Synced Folder](https://tech.recruit-mp.co.jp/dev-tools/vagrant-rsync-synced-folder/)

###クックブックの作成

```bash:ホスト
$ bundle exec knife cookbook create rsync -o ./site-cookbooks
```

###レシピの作成

```bash:ホスト
$ vi site-cookbooks/rsync/recipes/default
```

```ruby:site-cookbooks/rsync/recipes/default
package "rsync" do
  action :install
end
```

###Berksfileの追加とクックブックの更新

```bash:ホスト
$ vi Berksfile
```

```ruby:Berksfile
...
cookbook "rsync", path: "./site-cookbooks/rsync"
```

```bash:ホスト
$ bundle exec berks vendor cookbooks
```

###run_listの追加と同期するディレクトリの設定

```bash:ホスト
$ vi Vagrantfile
```

```ruby:Vagrantfile
Vagrant.configure(2) do |config|

  
 
  config.vm.provision :chef_solo do |chef|
    
    chef.run_list = %w[
      ...
      recipe[rsync]
    ]
    
  end

  config.vm.synced_folder "/Users/#{ENV['USER']}/php_app",  "/usr/share/nginx/html/", type: "rsync", create: true, owner: "vagrant", group: "vagrant", rsync__exclude: [".git/", "vendor/"]

end
```

###プロビジョニングの実行

```bash:ホスト
$ vagrant provision
```

###同期処理の自動化

```bash:ホスト
$ vagrant rsync-auto
```