使用 Capistrano 3 部署 Rails 应用

之前跑应用的时候一直是自己手动一步一步搭起来的,之前看到论坛上有好多帖子谈到自动化部署的。一直打算要在自己的项目种来实验一下的,结果一直。。。懒,今天终于下定决心,开始研究 Capistrano 的文档,开始体验一下自动化部署的。 好多人觉得部署这种事情,就一步一步跑起每个模块就好了,为什么要用这种也算挺重量级的框架来部署呢。以前我也是这么认为,但是发现随着应用的复杂度的提升,用到的东西越来越多,部署确实是一个比较大的工程了。而且自动化部署的好处是只需第一次配置好,今后的所有的部署只需根据现在的部署文件简单的修改一下就好了。反观手动部署的话,每次都要重复同样的一堆指令,开始还好,久了就真的难以忍受了。现在开始进入正题。

The Stack

  • Nginx 作为 web server
  • Unicorn 作为 app server

步骤

在 Gemfile 中添加 gems
gem 'capistrano', '~> 3.2.0'

# rails plugins
gem 'capistrano-rails', '~> 1.1.0'

gem 'capistrano-bundler'
gem 'capistrano-rvm'

# Use unicorn as app server
gem 'unicorn'

接着执行bundle install来安装所需的 gem 包。

在项目中倒入 Capistrano 的配置文件

在终端执行命令bundle exec cap install,接着项目中就会生成以下的文件:

├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
                └── tasks
修改 Capfile 配置文件
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
Dir.glob('lib/capistrano/**/*.rb').each { |r| import r }

这些是我们在后面的配置中要自己写一些 tasks,默认情况下导入的是lib/capistrano/tasks/*.rake文件。

同时还要加入下面的一些插件:

require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/rails/migrations'

第一个插件是保证我们在部署的时候,gems也会自动的安装,capistrano/rvm是用来为我们控制 Ruby 的版本的,这样可以保证部署时使用的 Ruby 的版本是我们所需要的。migrations是保证在进行部署的时候,migrations这些操作也会自动进行。

部署的配置信息

在将配置信息之前,先要说说 Capistrano 中 stage 这个概念。个人感觉它和 Rails 中的运行环境类似。默认拥有两个 Stage,stagingproduction

我们在config/deploy.rb中定义公用的变量,比如一些公用的task,还有scmgit_repo之类的变量。而和不同运行环境相关的变量则放到相应的 Stage 配置文件中,如config/deploy/production.rb

config/deploy.rb
# config valid only for Capistrano 3.2
lock '3.2.1'

set :application, 'deploy_with_capistrano'
set :deploy_user, 'teddy'

# setup repo details
set :scm, :git
set :repo_url, 'git@github.com:teddy1004/deploy_with_capistrano.git'

# setup rvm.
set :rvm_type, :user
set :rvm_ruby_version, '2.0.0-p353'

# how many old releases do we want to keep
set :keep_releases, 5

# Rails 中一些公用的配置文件(包含重要信息),同时我们要将这些文件添加到 .gitignore 中
set :linked_files, %w{config/database.yml config/secrets.yml}

# dirs we want symlinking to shared
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# which config files should be copied by deploy:setup_config
set(:config_files, %w(  
  nginx.conf
  database.yml.example
  secrets.yml.example
  unicorn.rb
  unicorn_init.sh
))

# which config files should be made executable after copying
# by deploy:setup_config
set(:executable_config_files, %w(
  unicorn_init.sh
))

# files which need to be symlinked to other parts of the
# filesystem. For example nginx virtualhosts, log rotation
# init scripts etc.
# The full_app_name variable isn't available at this point
# so we use a custom template  tag and then add it at 
# run time
set(:symlinks, [
  {
    source: "nginx.conf",
    link: "/etc/nginx/sites-enabled/"
  },

  {
    source: "unicorn_init.sh",
    link: "/etc/init.d/unicorn_"
  }
])

namespace :deploy do
  # 在本地编译静态文件然后上传
  after 'deploy:symlink:shared', 'deploy:compile_assets_locally'
  after :finishing, 'deploy:cleanup'

  # 在配置之前,删除 nginx sites-enabled 目录下 default 文件,因为
  # 它有可能和我们的配置信息发生冲突
  before 'deploy:setup_config', 'nginx:remove_default_vhost'

  # 当配置完成之后,重启 Nginx 服务器
  after 'deploy:setup_config', 'nginx:reload'

  # As of Capistrano 3.2, the `deploy:restart` task is not called
  # automatically.
  after 'deploy:publishing', 'deploy:restart'
end

这里面有两个比较重要的概念,linked_fileslinked_dirs

解释他们之前,先得说 Capistrano 中shared目录这个概念。下面是一个多次 deploy 之后服务器生成的目录结构:

.
├── current -> /home/user/apps/appname/releases/20140618072703
├── releases
│   ├── 20140618072703
│
├── repo
│   ├── branches
│   ├── config
│   ├── description
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── hooks
│   ├── info
│   ├── objects
│   ├── packed-refs
│   └── refs
├── revisions.log
└── shared
    ├── bin
    ├── bundle
    ├── config
    ├── log
    ├── public
    ├── tmp
    └── vendor

shared目录就是用来保存项目中共享的一些内容,它们不是随着每次新的发布而同时发生变动的,比如数据库的配置文件,还有session信息等。linked_dirslinked_files就是将制定的文件、目录链接到shared目录中。

linked_dirs

它是将项目制定的目录链接到shared目录中

linked_files

这个刚好和linked_dirs相反,它是将shared目录中的文件链接到当前发布的项目中。如果shared目录中没有我们指定的文件,那么在部署的过程中会报错。我们经常会将database.ymlsecrets.yml这些存有敏感信息放在这个目录中,同时将项目中这些文件加入到.gitignore文件中。

修改 Production 环境下的配置信息

接着我们来在config/deploy/production.erb文件下配置remote_server的具体信息

# Capistrano 中 stage 默认包括了三种环境, 也是 test, development, production
set :stage, :production
set :branch, "master"

set :full_app_name, "#{fetch(:application)}_#{fetch(:stage)}"
set :server_name, "www.example.com"

server 'www.example.com', user: 'deploy', roles: %w{web app db}, primary: true

set :deploy_to, "/home/#{fetch(:deploy_user)}/apps/#{fetch(:full_app_name)}"
set :rails_env, :production
set :unicorn_worker_count, 5
set :enable_ssl, false

这里有疑问的地方就是关于 Capistrano 中roles这个变量,我还不是很了解,但是目前按照默认配置的都是可以工作的。当我们执行cap stage_name task_name命令的时候,Capistrano 会按路径config/deploy/stage_name.rb找到这个文件,并且在deploy.rb之后加载这个文件

向服务器上传 config 文件

运行cap production deploy:setup_config指令,这一步是将部署所需的配置文件上传到服务器端。注意deploy:setup_config这些都是我们自己写的task,在目录lib/capistrano目录下。具体的代码可以参见:Deploy with Capistrano

部署
cap proudction deploy

第一次部署会慢一些,因为要安装相应的 gems,以后就会快很多了。

总结

这只是对 Capistrano 的一次大概的探索,还有很多概念没有理解透彻。这些自动化的工具都是需要一定的学习成本的,所以经常会懒得学,宁可一行一行敲命令(应该不止我一个人吧)。但是只要花一些时间去学习、使用,就会发现能极大的提升效率。所以在以后的项目中,要坚持使用自动化部署工具来部署,好像有个叫Mina的也挺火,先把 Capistrano 玩熟了就去试试那个。