almost 4 years ago

系統:

Mac OS X Yosemite 10.10.1
Ruby 2.1.5
Rails 4.1.8
Vagrant 1.7.1
VirtualBox 4.3.20
Virtual Machine - Ubuntu 14.04 LTS

相關連結

Chef ( https://www.chef.io/ )
Chef Documents ( http://docs.chef.io/ )
Cookbooks ( https://supermarket.chef.io/ )

設定步驟如下

1. 前言

如果要讓 knife-solo 除了配置一台 remote 主機基本環境外,再持續針對用途,例如可以執行 rails 專案的伺服器,進行更進階的配置時,就可以自己寫一本 cookbook 來客製化自已想要的主機環境。

2. 新增空白 cookbook 至 site-cookbooks

執行以下指令會自動生成一本含有基本架構的 cookbook 至 chef 專案底下的 site-cookbooks 資料夾,本篇文章以在 Vagrant 的虛擬主機上跑 rails 專案為例。

$ knife cookbook create cook_rails -o site-cookbooks

指令說明:http://docs.chef.io/chef/knife.html#cookbook
cookbook 架構說明:http://docs.chef.io/cookbook_repo.html

3. metadata.rb 設定

修改 metadata 基本資料,主要的部分就是利用 depends 新增會用到的 cookbooks。

name             'cook_rails'
...
depends 'apt'
depends 'curl'
depends 'git'
depends 'nodejs'
depends 'nginx'
depends 'postgresql'
depends 'database'
depends 'sqlite'
depends 'rbenv'
depends 'unicorn-ng'

另外,README.mdCHANGELOG.md 的內容就可以按照所提供的範例做修改。

4. 設定參數 ( attributes )

cookbook 用來設定參數的地方為 attributes/,我們可以將設定在 nodes/ 底下 json 檔中的參數拉至 attributes/default.rb 中。

...
default[:ruby][:version] = "2.1.5"
default[:nginx][:default_site_enabled] = false
default[:postgresql][:password][:postgres] = "postgres"

# server config settings for vagrant
default[:server][:user] = 'vagrant'
default[:server][:group] = 'vagrant'
default[:server][:app_name] = 'vagrant'
default[:server][:app_path] = '/vagrant'
...

參數設定好時,在整個食譜(cookbook)都是可以直接調用的,全部都是儲存在 node 的 hash 物件中。

# default[:ruby][:version] = "2.1.5"
node[:ruby][:version]
# default[:nginx][:default_site_enabled] = false
node[:nginx][:default_site_enabled]
# default[:postgresql][:password][:postgres] = "postgres"
node[:postgresql][:password][:postgres]

參數說明:http://docs.chef.io/attributes.html

5. 設定食譜 ( recipes )

配置基本環境的食譜 recipes/setup.rb

我們可以把本系列文章的 part 2 中,所使用的食譜(recipes)拉至這裏,把此檔案當作用來管理 remote 主機初始時需安裝的組件(packages)。

...
include_recipe "apt"
include_recipe "curl"
include_recipe "git"
include_recipe "nodejs"
include_recipe "nginx"
include_recipe "postgresql::server"
include_recipe "postgresql::ruby"
include_recipe "sqlite"
安裝 rbenv + ruby 的食譜 recipes/rbenv.rb

因為,cookbook rbenv 預設安裝的路徑並非筆者喜愛的路徑 ~/.rbenv,所以,筆者在食譜一開始就重新設定了安裝的路徑,當然,看個人喜好斟酌修改,不過所列的參數是不可缺少的,否則在安裝時會產生錯誤。

...
node.set[:rbenv][:user] = node[:server][:user]
node.set[:rbenv][:group] = node[:server][:group]
node.set[:rbenv][:install_prefix] = "/home/#{node[:rbenv][:user]}"
node.set[:rbenv][:root_path] = "#{node[:rbenv][:install_prefix]}/.rbenv"
node.set[:rbenv][:user_home] = "/home/#{node[:rbenv][:user]}"

include_recipe "rbenv"
include_recipe "rbenv::ruby_build"

# install Ruby
rbenv_ruby node[:ruby][:version] do
  global true
end

# install gem bundler
rbenv_gem "bundler" do
  ruby_version node[:ruby][:version]
end

# update rubygems
rbenv_gem "rubygems-update" do
  ruby_version node[:ruby][:version]
end
bash "update rubygems" do
  code "#{rbenv_shims_path}/update_rubygems"
end

# add .gemrc
file "#{node[:rbenv][:user_home]}/.gemrc" do
  content %(gem: "--no-ri --no-rdoc")
  owner node[:rbenv][:user]
  group node[:rbenv][:group]
  mode '0755'
  action :create
end
預設食譜 recipes/default.rb

接下來只要把食譜 setuprbenv 新增至食譜 default 中,這樣 基本環境配置 & rbenv & ruby 的食譜就設定好了。

include_recipe "cook_rails::setup"
include_recipe "cook_rails::rbenv"
在 Vagrant 中用 nginx + unicorn 跑 Rails Production 專案的食譜

1. 設定所需參數

attributes/vagrant.rb

...
# unicorn config settings for vagrant
default['unicorn-ng'][:config][:config_file_path] = "#{node[:server][:app_path]}/tmp/config"
default['unicorn-ng'][:config][:config_file] = "#{node['unicorn-ng'][:config][:config_file_path]}/unicorn.rb"
default['unicorn-ng'][:config][:working_directory] = node[:server][:app_path]
default['unicorn-ng'][:config][:worker_processes] = 2
default['unicorn-ng'][:config][:listen] = "/tmp/unicorn.#{node[:server][:app_name]}.sock"
default['unicorn-ng'][:config][:owner] = node[:server][:user]
default['unicorn-ng'][:config][:group] = node[:server][:group]

default['unicorn-ng'][:service][:name] = "unicorn_#{node[:server][:app_name]}"
default['unicorn-ng'][:service][:rails_root] = node[:server][:app_path]
default['unicorn-ng'][:service][:config] = node['unicorn-ng'][:config][:config_file]
default['unicorn-ng'][:service][:environment] = 'production'
default['unicorn-ng'][:service][:bundle] = "/home/#{node[:server][:user]}/.rbenv/shims/bundle"
default['unicorn-ng'][:service][:user] = node[:server][:user]
default['unicorn-ng'][:service][:owner] = node[:server][:user]
default['unicorn-ng'][:service][:group] = node[:server][:group]

# nginx config settings for vagrant
default[:cook_rails][:nginx][:config_name] = node[:server][:app_name]
default[:cook_rails][:nginx][:server_name] = node[:server][:name]
default[:cook_rails][:nginx][:root] = node[:server][:app_path]
default[:cook_rails][:nginx][:access_log_file] = "#{node[:server][:app_path]}/log/nginx.access.log"
default[:cook_rails][:nginx][:error_log_file] = "#{node[:server][:app_path]}/log/nginx.error.log"

# postgresql settings for vagrant
default[:cook_rails][:postgresql][:environment] = node['unicorn-ng'][:service][:environment]
default[:cook_rails][:postgresql][:encoding] = 'unicode'
default[:cook_rails][:postgresql][:database] = "#{node[:server][:app_name]}_#{node['unicorn-ng'][:service][:environment]}"
default[:cook_rails][:postgresql][:pool] = 5
default[:cook_rails][:postgresql][:username] = node[:server][:user]
default[:cook_rails][:postgresql][:password] = node[:server][:user]
default[:cook_rails][:postgresql][:host] = 'localhost'

2. 新增所需模板:cookbook 中放置模板的地方在 templates\

templates/default/nginx.conf.erb

upstream unicorn_<%= node[:cook_rails][:nginx][:config_name] %> {
  server unix:/tmp/unicorn.<%= node[:cook_rails][:nginx][:config_name] %>.sock fail_timeout=0;
}

server {
  listen 80;

  client_max_body_size 4G;
  keepalive_timeout 10;

  error_page 500 502 504 /500.html;
  error_page 503 @503;

  server_name <%= node[:cook_rails][:nginx][:server_name] %>;
  root <%= node[:cook_rails][:nginx][:root] %>/public;
  try_files $uri/index.html $uri @unicorn_<%= node[:cook_rails][:nginx][:config_name] %>;

  location @unicorn_<%= node[:cook_rails][:nginx][:config_name] %> {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn_<%= node[:cook_rails][:nginx][:config_name] %>;
    # limit_req zone=one;
    access_log <%= node[:cook_rails][:nginx][:access_log_file] %>;
    error_log <%= node[:cook_rails][:nginx][:error_log_file] %>;
  }

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  location = /50x.html {
    root html;
  }

  location = /404.html {
    root html;
  }

  location @503 {
    error_page 405 = /system/maintenance.html;
    if (-f $document_root/system/maintenance.html) {
      rewrite ^(.*)$ /system/maintenance.html break;
    }
    rewrite ^(.*)$ /503.html break;
  }

  if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS)$ ){
    return 405;
  }

  if (-f $document_root/system/maintenance.html) {
    return 503;
  }

}

templates/default/postgresql.yml.erb

<%= node[:cook_rails][:postgresql][:environment] %>:
  adapter: postgresql
  encoding: <%= node[:cook_rails][:postgresql][:encoding] %>
  database: <%= node[:cook_rails][:postgresql][:database] %>
  pool: <%= node[:cook_rails][:postgresql][:pool] %>
  username: <%= node[:cook_rails][:postgresql][:username] %>
  password: <%= node[:cook_rails][:postgresql][:password] %>
  host: <%= node[:cook_rails][:postgresql][:host] %>

3. 食譜設定

recipes/vagrant.rb

...
# bundle install in app_path folder on Vagrant virtual machine
bash "bundle install" do
  cwd node[:server][:app_path]
  code "#{rbenv_shims_path}/bundle install"
end

# backup origin database.yml
bash "backup database.yml" do
  cwd node[:server][:app_path]
  code "mv -f config/database.yml config/database.yml.backup"
end

# create database.yml
template "#{node[:server][:app_path]}/config/database.yml" do
  source 'postgresql.yml.erb'
  owner node[:server][:user]
  group node[:server][:group]
  mode '0644'
end

# create postgresql user and database
postgresql_connection_info = {
    host: 'localhost',
    port: node[:postgresql][:config][:port],
    username: 'postgres',
    password: node[:postgresql][:password][:postgres]
}

postgresql_database_user node[:cook_rails][:postgresql][:username] do
  connection postgresql_connection_info
  password node[:cook_rails][:postgresql][:password]
  action :create
end

postgresql_database node[:cook_rails][:postgresql][:database] do
  connection postgresql_connection_info
  owner node[:cook_rails][:postgresql][:username]
  action :create
end

# database migration
bash "rake db:migrate" do
  cwd "/vagrant"
  code "RAILS_ENV=production #{rbenv_shims_path}/rake db:migrate"
end

# assets precompile
bash "rake assets:precompile" do
  cwd "/vagrant"
  code "RAILS_ENV=production #{rbenv_shims_path}/rake assets:precompile"
end

# setup unicorn
directory node['unicorn-ng'][:config][:config_file_path] do
  owner node['unicorn-ng'][:config][:owner]
  group node['unicorn-ng'][:config][:group]
  mode "2775"
  action [:create]
end

include_recipe "unicorn-ng::default"

service node['unicorn-ng'][:service][:name] do
  action :restart
end

# setup nginx
template node['nginx']['dir'] + '/sites-available/' + node[:cook_rails][:nginx][:config_name] do
  source 'nginx.conf.erb'
  owner node[:server][:user]
  group node[:server][:group]
  mode 0644
  notifies :reload, 'service[nginx]'
end

nginx_site node[:cook_rails][:nginx][:config_name] do
  enable true
end

# store database.yml
bash "store origin database.yml" do
  cwd "/vagrant"
  code "mv -f config/database.yml.backup config/database.yml"
end

6. 設定 Secret Key Base

筆者是使用 figaro 來管理系統參數,所以,把 SECRET_KEY_BASE 設定在 config/application.ymlfigaro 教學請參考 [這裡]。

SECRET_KEY_BASE: 'get_by_rake_secret`

如果,沒有使用 figaro,那就要登入虛擬機器中執行以下指令。

$ export SECRET_KEY_BASE=get_by_rake_secret

7. 開始烹飪

最後,就是要告訴 knife 要執行哪些食譜,也就是設定在 nodes/10.10.10.10.json,最後在執行 cook 跑完,就可以在本機打開網站 http://10.10.10.10 看看是不是有跑出 rails 專案的首頁了!

{
  "run_list": [
    "recipe[cook_rails]",
    "recipe[cook_rails::vagrant]"
  ],
  "server": {
    "name": "10.10.10.10"
  },
  "automatic": {
    "ipaddress": "10.10.10.10"
  }
}
$ knife solo cook vagrant@10.10.10.10
← 使用 Vagrant 和 Chef 開發 Rails - Part 2 - Chef Post 管理 - part 4 - 支援 Markdown 和 Syntax Highlighter →
 
comments powered by Disqus