プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

入門Chef-Soloを片手にRailsアプリを動作させるところまでやってみた

Chefについては前々から気にはなっていたけどまとまった情報があまりなく、中々じっくりと取り組めていなかったのですが、ちょうど次のプロジェクトから導入しようとしていたところに、『入門Chef Solo - Infrastructure as Code』というありがたいまとめ本が出版されたので、それを片手に色々と実験してみました。


入門Chef Soloはよくまとめられていて非常に助かったのですが、Kindleで見ると目次がなくて逆引き的に利用しながら構築するのが結構大変でしたので、自分用に手順書としてまとめてみました。(目次に関しては現在修正版が出ているようです)


とりあえずRailsのサービス開発プロジェクトで使いたいので、以前にさくらVPSの設定で行ったような感じでrvm, nginx, unicorn, mysqlRailsアプリが動くところまでやってみました。もう1年以上前ですが、前回はこんな感じで設定していました。

Vagrantを使って仮想サーバーを立てる

まずは気軽に実験できるように仮想サーバーを立てます。


1. OracleVirtualBoxをインストール


2. Vagrantをインストール

$ gem install vagrant 


3. Vagrantで利用するOSイメージの取得
こちらでイメージ一覧が公開されていますが、今回は書籍にあったものをそのまま指定。

$ vagrant box add base http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.3-x86_64-v20130101.box


4. 適当なディレクトリでinitを実行して仮想サーバーの初期化します。

$ vagrant init
(Vagrantfileが生成されます)


5. Vagrantfileのネットワーク設定を追加します。

...
config.vm.network :private_network, ip: "192.168.50.12"
...


6. 仮想サーバーを起動します。

$ vagrant up


7. SSHで接続できればOK。

$ vagrant ssh


8. SSHの設定も追加しておきます。

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


9. 不要になったらこれで停止、削除できます。

$ vagrant halt
$ vagrant destroy


その他メモ
vagrantの初期rootパスワードは 'vagrant'


knife-soloのインストールと設定

1. knife-soloを使うと、ローカルマシンからリモートサーバー(vagrant)に対してChef Soloを実行できるようになります。

$ gem install knife-solo


2. knifeの初期設定を行います。表示される質問は全てデフォルトでOK。

$ knife configure


3. サードパーティのクックブックを利用するために設定しておきます。OPSCODEでユーザー登録を行い、プロフィールページにある get private key から秘密鍵を取得。先ほどのknife configureで生成した ~/.chef/knife.rb の client_key にこの秘密鍵を指定しておきます。

...
client_key '/Users/ntaku/.chef/ntaku.pem'
...

Chefリポジトリの作成

knifeを使ってリポジトリを作成します。(ここではgitコマンドは省いています)

$ knife solo init chef-repo
$ cd chef-repo

Berkshelfのインストール

1. サードパーティのクックブックを効率よく管理するためにBerkshelfを使いたいので、こちらも先に設定しておきます。

$ gem install berkshelf


2. chef-repo/Berksfileを作成します。yum以外は自分でクックブックを作りたいところですが、まだそこまできちんと書けないので、とりあえず既存のクックブックを利用します。

site :opscode
cookbook 'yum'
cookbook 'mysql'
cookbook 'rvm', git:'https://github.com/fnichol/chef-rvm.git'
cookbook 'nodejs', git:'https://github.com/mdxp/nodejs-cookbook.git'


3. Berksfileで指定したクックブックを取得します。--pathオプションでクックブックの保存先を指定できます。指定しない場合は、~/.berkshelf/cookbooksに取得したファイルが保存されます。

$ berks --path cookbooks

クックブックとレシピの作成

オリジナルのクックブックを作成していきます。
とは言っても今回追加するのは2つだけです。

$ cd chef-repo
$ knife cookbook create iptables -o site-cookbooks
$ knife cookbook create nginx -o site-cookbooks
iptables

設定ファイルは実際に使っているものをそのまま持って来ました。
これで設定ファイルがそのままvagrantへ転送され、iptablesが再起動されます。


/chef-repo/site-cookbooks/iptables/recipes/default.rb

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

template "/etc/sysconfig/iptables" do
  source "iptables"
  owner "root"
  group "root"
  mode 0600
  notifies :restart, 'service[iptables]'
end


/chef-repo/site-cookbooks/iptables/templates/default/iptables

*filter
:INPUT   ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT  ACCEPT [0:0]
:RH-Firewall-1-INPUT - [0:0]
-A INPUT -j RH-Firewall-1-INPUT
-A FORWARD -j RH-Firewall-1-INPUT
-A RH-Firewall-1-INPUT -i lo -j ACCEPT
-A RH-Firewall-1-INPUT -p icmp --icmp-type any -j ACCEPT
-A RH-Firewall-1-INPUT -p 50 -j ACCEPT
-A RH-Firewall-1-INPUT -p 51 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp --dport 5353 -d 224.0.0.251 -j ACCEPT
-A RH-Firewall-1-INPUT -p udp -m udp --dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -p tcp -m tcp --dport 631 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# SSH, HTTP
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

-A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited
COMMIT 
nginx

設定ファイルが2つと、レシピファイルが1つです。
こちらもiptablesと同じ感じで。


/chef-repo/site-cookbooks/nginx/recipes/default.rb

package "nginx" do
  action :install
end

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

template "/etc/nginx/nginx.conf" do
  source "nginx.conf.erb"
  owner "root"
  group "root"
  mode 0644
  notifies :reload, 'service[nginx]'
end

directory "/etc/nginx/sites-enabled" do
  owner "root"
  group "root"
  mode 0644
  action :create
end

template "/etc/nginx/sites-enabled/default.conf" do
  source "sites-enabled.conf.erb"
  owner "root"
  group "root"
  mode 0644
  notifies :reload, 'service[nginx]'
end


/chef-repo/site-cookbooks/nginx/templates/default/nginx.conf.erb

user nginx;
worker_processes 1;

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

events {
  worker_connections  1024;
}

http {
  include mime.types;
  default_type application/octet-stream;
  sendfile on;
  keepalive_timeout  65;

  gzip on;
  gzip_disable "msie6";
  gzip_proxied any;
  gzip_min_length 500;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

  # virtual Hosts
  include /etc/nginx/sites-enabled/*;
} 


/chef-repo/site-cookbooks/nginx/templates/default/sites-enabled.conf.erb

upstream unicorn_rails_proxy {
  server unix:/tmp/unicorn_<%= node['nginx']['application'] %>.sock fail_timeout=0;
}

server {
  listen <%= node['nginx']['port'] %>;
  server_name _;

  root /var/www/<%= node['nginx']['application'] %>/current;
  access_log /var/log/nginx/<%= node['nginx']['application'] %>_log;
  error_log /var/log/nginx/<%= node['nginx']['application'] %>_error_log;
  rewrite_log on;

  location / {
    proxy_pass  http://unicorn_rails_proxy;
    proxy_redirect     off;

    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

    client_max_body_size       10m;
    client_body_buffer_size    128k;
    proxy_connect_timeout      90;
    proxy_send_timeout         90;
    proxy_read_timeout         90;
    proxy_buffer_size          4k;
    proxy_buffers              4 32k;
    proxy_busy_buffers_size    64k;
    proxy_temp_file_write_size 64k;
  }

  location ~ ^/(images|javascripts|stylesheets|system)/  {
    root /var/www/<%= node['nginx']['application'] %>/current/public;
    expires max;
    break;
  }
}


<%= node['nginx']['application'] %>は後からnodeファイルで値を指定します。

nodeファイルの編集

最後にnodeファイルでノードに反映させるレシピを指定します。


/chef-repo/nodes/vagrant01.json

{
  "rvm": {
    "rubies"       : ["ruby-1.9.3-p392"],
    "default_ruby" : "ruby-1.9.3-p392"
  },

  "mysql": {
    "server_root_password": "test",
    "server_repl_password": "test",
    "server_debian_password": "test"
  },

  "nginx":{
    "application" : "chef_rails_template",
    "port" : 80
  },

  "run_list":[
    "recipe[yum::epel]",
    "recipe[rvm::system]",
    "recipe[mysql::server]",
    "recipe[nginx]",
    "recipe[iptables]",
    "recipe[nodejs]"
  ]
}

Chefの実行

いよいよChef Solo実行です。(実際は行ったり来たりなので、ここまでに何度も実行していますが..)


1. prepareを実行して指定したホストにChefの実行環境を準備します。(初回のみ実行する)

$ cd chef-repo
$ knife solo prepare vagrant01


2. 適応したいホストを指定してChef Soloを実行します。レシピに問題がなければこれでサーバーの環境構築は全て完了です。

$ knife solo cook vagrant01

Railsデプロイ

ここからはおまけみたいなものなので、簡単にできるようにサンプルプロジェクトをgithubに作りました。こちらのプロジェクトをcapistranoを使ってvagrantへデプロイします。


1. vagrant上に予めproduction用のDBを作っておきます。MySQLのパスワードはvagrant01.jsonで指定したtestです。

$ ssh vagrant01
$ mysql -u root -p
mysql> create database chef_rails default character set=utf8;


2. githubからプロジェクトを取って来てデプロイ & 起動します。(ローカルマシン)

$ git clone git@github.com:ntaku/chef_rails_template.git
$ cd chef_rails_template
$ cap deploy:setup
$ cap deploy
$ cap deploy:start


3. アプリケーションにアクセスします。

http://192.168.50.12/ にアクセスして「TOP PAGE」と出れば全て正しく動作しています。

まとめ

『入門Chef Solo - Infrastructure as Code』のおかげで大分Chefと仲良くなれました。
この本がなかったら3倍は時間がかかったと思います..