Capistrano 3 实现 Rails 自动化部署

cap

Capistrano 3.1 相对于之前的 2.x 有着很大不同,本想用自动化发布,应该是分分种搞定的事情,没想到找文档,看说明,花了一天也没看出个什么,于是晚上花时间把 2.x 和 3.1 的代码过了下,做了个对比。

使用版本
1
2
3
4
unicorn 4.8.3
rails 4.2.0
ruby 2.2.0
capistrano 3.3.5


安装::

Gemfile 中添加 Capistrano 和其它用到的插件

Gemfile
1
2
3
4
5
6
7
8
group :development do
gem 'capistrano'
gem 'capistrano-bundler'
gem 'capistrano-rails'
gem 'capistrano-rbenv'
# Add this if you're using rvm
# gem 'capistrano-rvm'
end

- 初始化 Capistrano

1
$ cap install

会生成如下目录文件,Capfile 用来配置 Capistrano,deploy.rb是一些共用 task 的定义,而 production.rb/staging.rb 用来定义具体的 stage 的 tasks。

Cap init file structure
1
2
3
4
5
6
7
8
9
├── Capfile
├── config
│ ├── deploy
│ │ ├── production.rb
│ │ └── staging.rb
│ └── deploy.rb
└── lib
└── capistrano
└── tasks

安装完成之后,通过 cap -vT 来查看当前项目的可执行任务列表。

1
2
3
4
5
6
7
8
9
cap bundler:install # Install the current Bundler environment
cap deploy # Deploy a new release
cap deploy:check # Check required files and directories exist
cap deploy:check:directories # Check shared and release directories exist
cap deploy:check:linked_dirs # Check directories to be linked exist in shared
cap deploy:check:linked_files # Check files to be linked exist in shared
cap deploy:check:make_linked_dirs # Check directories of files to be linked exist in shared
...
cap install # Install Capistrano, cap install STAGES=staging,production

这些命令其中就包括 刚刚执行的 cap install,其它用到的,后面再讲。


配置 Capistrano

- 在 Capfile 里开启要用到的一些插件

Capfile
1
2
3
4
5
6
7
8
9
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

- Deploy.rb

本文件中用来配置共用变量。

deploy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# config valid only for Capistrano 3.1
lock '3.3.5'
set :application, 'appname'
set :deploy_user, 'deployer'
set :scm, :git
set :repo_url, 'git@github.org:lanvige/railsapp.git'
# rbenv
set :rbenv_type, :user
set :rbenv_ruby, '2.2.0'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
# how many old releases do we want to keep, not much
set :keep_releases, 5
# files we want symlinking to specific entries in shared
set :linked_files, %w{config/database.yml config/application.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}
namespace :deploy do
after :finishing, 'deploy:cleanup'
end
  • rbenv 的配置见这里
  • linked_files & linked_dirs这里

- 配置Stage

关于 Stage,详见:

一个很重要的配置是 Role、Server(User)以及其对应关系,为了方便,Cap3 中提供了多种配置形式,各有不同的侧重,但用途都是一样的,见下面。

stage中的role/server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 以role为中心的写法
role :app, %w{deploy@example.com, deploy@example.local}
role :web, %w{deploy@example.com}
role :db, %w{deploy@example.com}
# 以 server 为中心的写法,上面的写法可以用以下写法代替:
server 'example.com', user: 'deploy', roles: %w{web app db}
server 'example.local', user: 'deploy', roles: %w{app}
# 如果要对某服务器配置 SSH 等更多时,也可以用这种写法:
server 'example.com',
user: 'user_name',
roles: %w{web app},
ssh_options: {
user: 'user_name', # overrides user setting above
keys: %w(/home/user_name/.ssh/id_rsa),
forward_agent: false,
auth_methods: %w(publickey password)
# password: 'please use keys'
}

Stage 示例

test.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
set :stage, :test
set :branch, 'develop'
server '192.168.1.1', user: 'deploy', roles: %w{web app db}
set :deploy_to, "/home/#{fetch(:deploy_user)}/apps/appname"
# dont try and infer something as important as environment from
# stage name.
set :rails_env, :test
# number of unicorn workers, this will be reflected in
# the unicorn.rb and the monit configs
set :unicorn_worker_count, 5
# whether we're using ssl or not, used for building nginx
# config file
set :enable_ssl, false


部署

- 验证 Git server

部署需要 Remote sever 从 Git 服务器上拉代码,这里需要验证 Git server,不然会有 Agent admitted failure to sign using the key 的Error。

1
$ ssh -T git@github.com

- 开始部署:

1
2
$ cap production deploy --dry-run
$ cap production deploy

输入服务器用户密码后就 deploy 就进行了。不想每次都输密码,ssh-copy-id 可能是你想要的。

- 服务器生成目录详解

运行多次deploy之后会生成这样的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.
├── current -> /home/deploy/apps/appname/releases/20140325071623
├── releases
│   ├── 20140325065734
│   ├── 20140325071310
│   ├── 20140325071623
│   └── 20140325074922
├── repo
│   ├── branches
│   ├── config
│   ├── description
│   ├── FETCH_HEAD
│   ├── HEAD
│   ├── hooks
│   ├── info
│   ├── objects
│   ├── packed-refs
│   └── refs
├── revisions.log
└── shared
├── bin
├── bundle
├── config
├── log
├── public
├── tmp
└── vendor
  • release 每次发布都会产成一个目录,该目录下存放着Rails项目源码,多个目录是为了rollback而设。
  • current 是指当前版本,软链接到release下的某个版本目录。
  • repo 存的是项目的 .git 目录
  • shared 是项目中共享的内容,如 config 文件,不随每次发布而改动。

- Rollback

1
$ cap production deploy:rollback

Rollback 其实就是把 current 目录指向到 releases 里上次发布的目录。


关键词解析::

- Cap Flow

系统默认包含下面的这些Task,Task是有顺序的,在每个Task之前和之后可以通过before, after添加自定义的Task。更多Flow介绍,见这里

  1. deploy:starting
  2. deploy:started
  3. deploy:reverting - revert server(s) to previous release
  4. deploy:reverted - reverted hook
  5. deploy:publishing
  6. deploy:published
  7. deploy:finishing_rollback - finish the rollback, clean up everything
  8. deploy:finished

在这些Flow之外也有一些常用的Task:

  • deploy:check

    对应Cap 2.x中的 deploy:setup。

    check不属于deploy flow,它的目的主要是在服务器上创建所需要的目录(主要是shared, release),然后就是对应的linked_dir & linked_files操作。

  • deploy:cold in 2.x

    新的系统中,核心的deploy task是幂等(idempotent)的,所以像cap deploy:cold这样的预执行命令(创建目录结构)就不再被需要了。

  • dry run

    dry-run主要是保证每一个命令都能被执行到,但不会在服务器上产生任何改动。

    1
    $ cap production deploy —dry-run

- Stage

Cap中有和Rails一样的运行环境(Environment)的概念(Cap中叫作Stage,默认建了2个Stage,staging, production),就是不同环境下对应不同的服务器。

deploy.rb中定义着公用的变量,对应到不同的Stage,可以定义一些专有的变量,同时也可以覆写deploy.rb中的公用变量。

注:Stage名字默认对应Rails的environment名字(development, test, production)。可通过设置进行映射。

1
set :rails_env, :test

- linked_files & linked_dirs

Capistrano使用Shared目录来管理那些在不同Release中共用的文件,最主要的一个shared/config中包含每个发布所需要的配置文件

  • linked_dirs
    是将项目的指定目录链接到shared目录中。这个操作会在从repo取下代码之后进行。
  • linked_files
    和linked_dirs相反,它是将shared中的文件链接到项目中,文件要首先存在于shared目录中,不然deploy时会报错。
    Rails项目中,主要就是database.ymlsecret.yml这样的敏感文件。对于这些文件最好的做法就是从Git中过滤掉,然后每个开发者和服务器都单独配置。

- MySQL

在尝试使用 SQLite3 进行 Cap 测试通过后,将 DB 换成 MySQL,然后又出错了。错误是数据库不存在,一直以为是在创建数据库时未指定 RAILS_EVN,后来发现 migrations 在执行前也没有运行 db:create 来创建数据库。

在运行 deploy 前,自己手工创建数据库,然后 deploy 就能正常运行了。猜想:create 属于一次性操作,所以没有将其作为 task 放入 deploy 中。

- Ask

在deploy.rb和stage文件中,我们可以设置变量的值。但有时,值会在运行时才被确定,这样就可以通过ask来设定。

1
ask :branch, ‘my_default_branch’

这样在branch变量第一次被使用时,会有输入提示,就可以动态设置该值了。

- Role

角色的目的是让Task可以运行在不同的机器上。更多

一个线上程序中有着多种服务器,像DB Server, APP server, Web Server,在不同的Server上要运行不同的部署方案。Cap也把这些也考虑到了,可以通过创建不同的Role来对这些服务器进行归类,为Task指定Role来运行。

- Tasks

如何自定义taskdoc

- Cold Start

可以在发布进行之前做一些准备工作,比如,创建 linked_files、Unicorn、Nginx 的配置及创建 Service 的工作。

现行的作法是,定制一个 task,在 Deploy 之前进行,在服务器创建目录,然后从本地将文件预先upload!)到服务器的相应目录(主要是 shared/config)。

- sudo

Capistrano 3 推荐使用 passwordless sudo。这样非root用户也可以直接使用sudo命令,而不必通过PTY来输入密码。Guide:

其实就是在系统里,给指定用户赋上某些指令的sudo权限。在Ubuntu下,修改 /etc/sudoers来添加要使用的命令。

/etc/sudoers
1
2
3
deploy ALL=NOPASSWD:/usr/sbin/service, /bin/ln
#也可以将所有程序都设置为不要密码,不过太不安全,不建议
#deploy ALL=(ALL) NOPASSWD: ALL

- PTYs

PTY就是让用户在当前Terminal中执行任务时,可以进行交互。比如说执行sudo任务时,可以远程输入密码。 更多

ps :试着启用pts,但输入密码后无反应。

- ssh-copy-id

每次运行 cap 都要输入密码,可以将本地的 ssh公钥 存到 server 上,就可以省下很多时间。

ssh-copy-id 就是这么一个将本机的公钥复制到远程机器的 authorized_keys 文件的工具,其也能让你拥有远程机器的 home, ~./ssh , 和 ~/.ssh/authorized_keys 的权利。

首先本地机器上要创建 ssh key

1
$ ssh-keygen
1
$ ssh-copy-id deploy@192.168.1.1

macOS 上 ssh-copy-id 不是默认安装的,可通过 Homebrew 进行安装

1
$ brew install ssh-copy-id

REF::