読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

ChefでRails環境を構築(Ruby+Nginx+Unicorn+PostgreSQL+Redis+Fluentd)

インフラ

f:id:nipe880324:20150720234335p:plain

ローカルの仮想マシンにChefでRails環境を構築する方法を説明します。
Chefで構築しているので、AWSやVPSなどのsshやrubyがインストールできる環境であれば同じ環境を構築できます。

具体的には、次のようなものを使用するようにしています。

  • CentOS 6.6
  • Ruby(rbenv)
  • Nginx
  • Unicorn
  • PostgreSQL
  • Redis
  • Fluentd(td-agent)

Chefのソースはnipe0324/chef - GitHubに配置しています。
また、インストール方法はREADME - install processに記載しています。


動作確認

  • Ruby 2.2.2
  • Vagrant 1.7.2
  • Chef 12.4.1

目次

1. Chefのあれこれ基本
2. Chefのインストール
3. chefリポジトリを作成
4. クックブックを作成
4.1. サーバーのタイムゾーンのクックブック
4.2. ユーザーのクックブック
4.3. Nginxの設定ファイルのクックブック
4.4. Rubyのクックブック
5. コミュニティクックブックのダウンロード
6. ロールを設定
7. environmentsを設定
8. ノードを設定
9. 仮想マシンの作成
10. 仮想マシンにChefの適用
11. 動作確認テスト



1. Chefのあれこれ基本

Chefを少し使ったことがある人を前提にあれこれ基本をメモ的に記載します。

・Chefのリポジトリ、クックブック、レシピの関係
「リポジトリ(キッチン) > クックブック > レシピ」の順に階層構造のような関係になっています。
リポジトリがあり、
そこにapacheやrubyなどのクックブックやリポジトリの属性設定ファイルなどがあって、
クックブックの中にレシピやクックブックの属性設定ファイルなどがあります。

リポジトリ(キッチン)のファイル説明
  • Berksfile ... 他の人が公開しているクックブック(コミュニティクックブック)の依存関係を管理する設定ファイル
  • Vagrantfile ... Vagrantで仮想サーバを起動するためのVagrantの設定ファイル
  • cookbooks ... コミュニティクックブックの配置場所
  • data_bags ... クックブック単位ではなく、リポジトリ全体に設定したい変数をJSONファイルで格納する場所
  • environments ... 開発用・本番用で設定を分けたい場合に各種変数などを格納する場所
  • nodes ... Nodeオブジェクトを記述したJSONファイルの格納する場所。ノードとはChefで管理するサーバーのこと
  • roles ... ロールの設定ファイルを格納する場所。ロールとはWebサーバとDBサーバといったような役割の違うサーバを扱いたい場合に使う
  • site-cookbooks ...自分で作ったクックブックの配置場所


クックブック
  • attributes ... 変数の初期値を定義したファイルを格納する(特定のノードだけ変更したい場合、Nodeオブジェクトで上書きする)
  • definitions ...リソースを自分で拡張したい場合に、拡張定義スクリプトを格納する場所
  • files ... cookbook_fileリソースで扱う静的ファイルを格納する場所
  • recipes ... レシピの置き場所
  • templates ... 設定のテンプレートの置き場所
  • etc ...


レシピ
packageserviceリソースを記載し、パッケージのインストールやサービスの起動などの状態を記載


attributeの優先度
「クックブック内のattributeファイル > レシピ内で定義されたattribute > environments > roles > nodes」

Chefの実行順序
1. chef solo cook [node名]などでnodeを実行
2. nodeのJSONに記載されている環境名でenvironments内の環境設定JSONが呼ばれる
3. node内のrun_listに記載されている、roleやrecipeが実行される
4. role内のrun_listに記載されている、recipeが実行される




2. Chefのインストール

bundlerをインストールします。

# rubyのバージョン
$ ruby -v
ruby 2.2.2p95

# bundlerのインストール
$ gem install bundler --no-ri --no-rdoc

Gemfileを作成します。

# Gemfile
source 'https://rubygems.org'

gem 'chef'
gem 'knife-solo'  # ローカルからリモートのchef-soloを実行できるツール
gem 'berkshelf'   # コミュニティクックブックの依存関係の管理ツール

gemをインストールします。

$ bundle install


3. chefリポジトリを作成

chef solo用のリポジトリを作成します。
rails newのようなものです。

$ bundle exec knife solo init .
Creating kitchen...
Creating knife.rb in kitchen...
Creating cupboards...
Setting up Berkshelf...

4. クックブックを作成

4.1. サーバーのタイムゾーンのクックブック

クックブックのテンプレートを作成します。

$ bundle exec knife cookbook create time-zone -o site-cookbooks

タイムゾーンを設定するレシピを記載します。

# site-cookbooks/time-zone/recipes/default.rb
# タイムゾーン変更
execute "change-server-localtime" do
  user "root"
  command "cp -p /usr/share/zoneinfo/UTC /etc/localtime"
  action :run
end

# clockをUTCに固定
cookbook_file "/etc/sysconfig/clock" do
  owner "root"
  group "root"
  mode 0755
  source "clock-utc"
end

レシピ内で使用している静的ファイルを作成します。

# site-cookbooks/time-zone/files/default/clock-utc
ZONE="UTC"

time-zoneディレクトリ配下のattributesdefinitionsなどの使用していないディレクトリは削除しておきます。


4.2. ユーザーのクックブック

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

レシピを作成

# site-cookbooks/user/recipes/defaut.rb

group node["user"] do
  group_name node["user"]
  action     [:create]
end

user node["user"] do
  comment  "#{node["user"]} user"
  group    node["user"]
  home     "/home/#{node["user"]}"
  supports :manage_home => true
  action   [:create, :manage]
end

userディレクトリ配下のattributesdefinitionsなどの使用していないディレクトリは削除しておきます。


4.3. Nginxの設定ファイルのクックブック

$ bundle exec knife cookbook create nginx-conf -o site-cookbooks

レシピの作成

# site-cookbooks/nginx-conf/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.confの設定を実施する。

# site-cookbooks/nginx-conf/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;

  <% if node['nginx']['env'].include?('ruby') %>
  upstream unicorn {
    server unix:/tmp/unicorn.sock;
  }
  <% end %>

  server {
    listen    80 default_server;
    server_name _;

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

    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?('ruby') %>
    location /unicorn {
      rewrite ^/unicorn/(.+) /$1 break;
      proxy_pass http://unicorn/$1;
    }
    <% end %>
  }
}

nginx-confディレクトリ配下のattributesdefinitionsなどの使用していないディレクトリは削除しておきます。


4.4. Rubyのクックブック

$ bundle exec knife cookbook create ruby-env -o site-cookbooks

レシピの作成をします。

# site-cookbooks/ruby-env/recipes/default.rb

# install openssl-devel and sqlite-devel
%w{openssl-devel sqlite-devel}.each do |pkg|
  package pkg do
    action :install
  end
end

# rbenv
git "/home/#{node['ruby-env']['user']}/.rbenv" do
  repository node['ruby-env']['rbenv_url']
  action :sync
  user  node['ruby-env']['user']
  group node['ruby-env']['group']
end

template ".bash_profile" do
  source ".bash_profile.erb"
  path   "/home/#{node['ruby-env']['user']}/.bash_profile"
  mode   0655
  owner  node['ruby-env']['user']
  group  node['ruby-env']['group']
  not_if "grep rbenv ~/.bash_profile", environment: { :'HOME' => "/home/#{node['ruby-env']['user']}" }
end

# ruby
directory "/home/#{node['ruby-env']['user']}/.rbenv/plugins" do
  mode   0755
  owner  node['ruby-env']['user']
  group  node['ruby-env']['group']
  action :create
end

git "/home/#{node['ruby-env']['user']}/.rbenv/plugins/ruby-build" do
  repository node['ruby-env']['ruby-build_url']
  action :sync
  user   node['ruby-env']['user']
  group  node['ruby-env']['group']
end

# install ruby
execute "rbenv install #{node['ruby-env']['version']}" do
  command "/home/#{node['ruby-env']['user']}/.rbenv/bin/rbenv install #{node['ruby-env']['version']}"
  user   node['ruby-env']['user']
  group  node['ruby-env']['group']
  environment 'HOME' => "/home/#{node['ruby-env']['user']}"
  not_if { File.exists?("/home/#{node['ruby-env']['user']}/.rbenv/versions/#{node['ruby-env']['version']}") }
end

# set rbenv global
execute "rbenv global #{node['ruby-env']['version']}" do
  command "/home/#{node['ruby-env']['user']}/.rbenv/bin/rbenv global #{node['ruby-env']['version']}"
  user   node['ruby-env']['user']
  group  node['ruby-env']['group']
  environment 'HOME' => "/home/#{node['ruby-env']['user']}"
end

# install rbenv-rehash and bundler gem
%w{rbenv-rehash bundler}.each do |gem_name|
  execute "gem install #{gem_name}" do
    command "/home/#{node['ruby-env']['user']}/.rbenv/shims/gem install #{gem_name}"
    user   node['ruby-env']['user']
    group  node['ruby-env']['group']
    environment 'HOME' => "/home/#{node['ruby-env']['user']}"
    not_if "/home/#{node['ruby-env']['user']}/.rbenv/shims/gem list | grep #{gem_name}"
  end
end

テンプレートのファイルを作成します。

# site-cookbooks/ruby-env/templates/default/.bash_profile.erb

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
  ~/.bashrc
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

ruby-envディレクトリ配下のattributesdefinitionsなどの使用していないディレクトリは削除しておきます。



5. コミュニティクックブックのダウンロード

Berksfileにコミュニティクックブックを記載します。

# Berksfile
source "https://supermarket.chef.io"

cookbook 'selinux'
cookbook 'sudo'
cookbook 'git'
cookbook 'ruby_build'
cookbook 'ruby-env', path: './site-cookbooks/ruby-env'
cookbook 'nodejs'
cookbook 'nginx'
cookbook 'postgresql'
cookbook 'redis'
cookbook 'imagemagick'
cookbook 'td-agent'

berksコマンドでBerksfileに記載したコミュニティクックブックをダウンロードします。

$ bundle exec berks vendor ./cookbooks

cookbooks配下にコミュニティクックブックがダウンロードされます。



6. ロールを設定

ロールはWebサーバ、DBサーバなどのサーバの役割に応じた単位で作成します。

サーバ共通用ロール(roles/base.json)

{
  "name": "base",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "run_list": [
    "recipe[selinux::disabled]",
    "recipe[time-zone]",
    "recipe[user]",
    "recipe[git]",
    "recipe[td-agent]"
  ]
}


Webサーバ用ロール(roles/web.json)

{
  "name": "web",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "default_attributes": {
    "nginx": {
      "env": ["ruby"]
    },
    "ruby-env": {
      "version": "2.2.2",
      "rbenv_url":      "https://github.com/sstephenson/rbenv",
      "ruby-build_url": "https://github.com/sstephenson/ruby-build"
    }
  },
  "run_list": [
    "recipe[yum-epel]",
    "recipe[nginx]",
    "recipe[nginx-conf]",
    "recipe[nodejs]",
    "recipe[ruby-env]",
    "recipe[postgresql]",
    "recipe[imagemagick]"
  ]
}

default_attributesは、ロールのデフォルト値を設定します。
environments配下の方が優先度が高いので、開発や本番環境などで設定を変えたいときは、この値を上書きできます。


DBサーバ用ロール(roles/db.json)

{
  "name": "db",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "run_list": [
    "recipe[postgresql]"
  ]
}


キャッシュサーバ用ロール(roles/cache.rb)

{
  "name": "cache",
  "chef_type": "role",
  "json_class": "Chef::Role",
  "run_list": [
    "recipe[redis]"
  ]
}

7. environmentsを設定

environmentsを使えば、開発、ステージング、本番環境など環境ごとに変数を設定できる。

ローカル開発環境用の設定(environments/local-development.json)

{
  "name":        "local-development",
  "description": "local virtual machine development environment",
  "chef_type":   "environment",
  "json_class":  "Chef::Environment",
  "default_attributes": {
    "user": "vagrant",
    "ruby-env": {
      "user":    "vagrant",
      "group":   "vagrant"
    }
  },
  "override_attributes": {}
}


8. ノードを設定

ノードはChefではサーバを表し、サーバに適用するレシピやロールを記載します。

ローカル環境のWebサーバ用(nodes/local-development.json)

{
  "environment": "local-development",
  "run_list": [
    "role[base]",
    "role[web]",
    "role[db]",
    "role[cache]"
  ]
}

9. 仮想マシンの作成

Vagrant入門を参考に、ローカルに仮想マシンを作成します。
途中の手順で、vagrant ssh-config --host web >> ~/.ssh/configvagrant ssh-config --host local-development >> ~/.ssh/configで実行しておいてください。



10. 仮想マシンにChefの適用

# リモートサーバー(仮想マシン)にchef-soloのインストール
$ bundle exec knife solo prepare local-development

# リモートサーバーにレシピを適用する
$ bundle exec knife solo cook local-development

11. 動作確認テスト

chef/samplesに動作確認用のrailsアプリ(unicornの設定もあり)を作成していますので、それを使ってRailsの画面が開けるか確認します。

cp -rp samples/sample_rails synced_folder/.
vagrant ssh
> cd synced_folder/sample_rails
> bundle install
> bundle exec rake db:migrate
> bundle exec unicorn -c config/unicorn.rb

ローカルマシンからブラウザで`http://192.168.33.10/unicorn`にアクセスすると、Railsの画面が表示されます。
f:id:nipe880324:20150720235203p:plain


以上です。

参考資料

  • Chef実践入門