Capistrano 3 实现Rails自动化部署 - tianlu1677/tianlu1677.github.io GitHub Wiki

Offical Site: http://capistranorb.com/

Github: https://github.com/capistrano/capistrano

使用版本

# unicorn 4.8.3
passenger
rails 4.1.4
ruby 2.1.2
capistrano 3.4.1

1.安装:

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

  Gemfile

group :development do
  gem 'capistrano', '3.4.1'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  #gem 'capistrano-rbenv'
  # Add this if you're using rvm
  gem 'capistrano-rvm'
end

这个时候bundle一下

2.项目中初始化 Capistrano

$ cap install

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

├── Capfile
├── config
   ├── deploy
      ├── production.rb
      └── staging.rb
   └── deploy.rb
└── lib
    └── capistrano
        └── tasks

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

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,其它用到的,后面再讲。

3.配置 Capistrano

3.1 在Capfile里开启要用到的一些插件,下面这些可能都是注释的取消注释就可以了

your_app/Capfile

require 'capistrano/setup'
require 'capistrano/deploy'

#require 'capistrano/rbenv'
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }

3.2 Deploy.rb

# config valid only for Capistrano 3.1
lock '3.4.1'

set :application, 'qdaily4_app'

#if ENV['branch']
#  set :application, "cms_#{ENV['branch']}"
#end

set :rails_env, 'production'

set :scm, :copy
# set :repository, "."
# set :deploy_via, :copy

# set :repo_url, '[email protected]:kaikeba/cms.git'

# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call

# Default deploy_to directory is /var/www/my_app
set :deploy_to, "/home/qdaily4_app/projects/#{fetch(:application)}"

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# set :linked_files, %w{config/database.yml}

# Default value for linked_dirs is []
# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# deploy.rb or stage file (staging.rb, production.rb or else)
#set :rvm_type, 'kkb'                     # Defaults to: :auto
# set :rvm_ruby_version, '2.1.1p76'      # Defaults to: 'default'
#set :rvm_custom_path, '~/.rvm'  # only needed if not detected

set :linked_files, %w{Gemfile.lock config/database.yml config/settings.yml config/sunspot.yml public/robots.txt}
set :linked_dirs, %w{log tmp vendor/bundle public/system public/uploads } #app/assets/subsystems/website/node_modules

#set



SSHKit.config.command_map[:rake]  = "bundle exec rake"
SSHKit.config.command_map[:rails] = "bundle exec rails"

# Default value for default_env is {}
# set :default_env, { path: "/home/kkb/.rvm/rubies/default/bin:/home/kkb/.rvm/bin:/home/kkb/.rvm/gems/ruby-2.1.1/bin:$PATH" }

set :default_shell, '/bin/bash -l'
set :bundle_flags, '--quiet'

# Default value for keep_releases is 5
# set :keep_releases, 5


namespace :deploy do
  desc 'grunt'

  task :grunt  do
    on roles(:app), in: :sequence, wait: 5 do
      grunt_dirs = %w{app/assets/subsystems/website/node_modules app/assets/subsystems/website/bower_components app/assets/subsystems/website/05-resource/lib} #app/assets/subsystems/website/node_modules
      grunt_dirs.each do |dir|
        execute " ln -nfs /home/kkb/projects/#{fetch(:application)}/shared/#{dir} /home/kkb/projects/#{fetch(:application)}/current/#{dir}"
      end
      execute "cd /home/kkb/projects/#{fetch(:application)}/current/app/assets/subsystems/website && bower update 'kkb-restful-service' --verbose && grunt" #, raise_on_non_zero_exit: false
    end
  end
  # after "assets:precompile", :grunt

  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
      # execute "cd /home/kkb/projects/#{fetch(:application)}/current/app/assets/subsystems/website && npm install && grunt"

      execute " kill -USR2 `cat /home/kkb/projects/#{fetch(:application)}/current/tmp/pids/unicorn.pid` "
      execute " ln -nfs /home/kkb/projects/#{fetch(:application)}/shared/public/uploads /home/kkb/projects/#{fetch(:application)}/current/public/uploads "
      # execute :bundle, " exec unicorn_rails -c #{File.join(current_path,'config', 'unicorn.rb')} -D -E staging "
      #execute :bundle, "exec sidekiq -d -L /home/kkb/projects/cms/current/log/sidekiq.log -q mailer -q default -q often -q seldom -e production"
    end
  end
  after :publishing, :grunt
  after :publishing, :restart

  # after :restart, :clear_cache do
  #   on roles(:web), in: :groups, limit: 3, wait: 10 do
  #     # Here we can do anything such as:
  #     within release_path do
  #       execute :rake, 'cache:clear'
  #     end
  #   end
  # end

  after :finishing, "deploy:cleanup"

end

3.3 配置config/deploy/production.rb文件

# 假设你服务器的ip是 67.87.98.78, 用户名是jack
# role :app, %w{server}
# role :web, %w{server}
# role :db,  %w{server}

role :app, %w{67.87.98.78}
role :web, %w{67.87.98.78}
role :db,  %w{67.87.98.78}
# server 'server', user: 'usernam', roles: %w{web app}, my_property: :my_value
server '67.87.98.78', user: 'jack', roles: %w{web app}, my_property: :my_value

你先要有服务器上的 key 值 cap production

3.4 配置Stage(可选,可以选择直接往服务器上部署)

关于Stage,详见:

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

stage中的role/server

# 以role为中心的写法
role :app, %w{[email protected], [email protected]}
role :web, %w{[email protected]}
role :db,  %w{[email protected]}

# 以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

set :stage, :test
set :branch, 'develop'

server '192.168.1.1', user: 'ares', 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

4.开始部署::

– 部署

$ cap production deploy --dry-run
$ cap production deploy

输入服务器用户密码后就deploy就进行了。不想每次都输密码,ssh-copy-id可能是你想要的。 如果过程中出现问题,请注意查看提示

5.相关内容讲解

5.1 服务器生成目录详解

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

.
├── current -> /home/ares/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文件,不随每次发布而改动。

5.2 回滚 Rollback

$ cap production deploy:rollback

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

5.3关键词解析::

– Cap Flow

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

deploy:starting
deploy:started
deploy:reverting  revert server(s) to previous release
deploy:reverted  reverted hook
deploy:publishing
deploy:published
deploy:finishing_rollback  finish the rollback, clean up everything
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主要是保证每一个命令都能被执行到,但不会在服务器上产生任何改动。
bash $ 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)。可通过设置进行映射。

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.yml,secret.yml这样的敏感文件。对于这些文件最好的做法就是从Git中过滤掉,然后每个开发者和服务器都单独配置。

– MySQL

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

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

####– Ask

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

ask :branch, ‘my_default_branch’

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

####– Role

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

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

– Tasks

查看官网的wifi中 task

– 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

ares ALL=NOPASSWD:/usr/sbin/service, /bin/ln
#也可以将所有程序都设置为不要密码,不过太不安全,不建议
#ares 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

$ ssh-keygen

$ ssh-copy-id [email protected]

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

$ brew install ssh-copy-id

-linked_file内容详解

参考链接

Railscasts Video: deploying-to-a-vps base on 2.x
基于Capistrano工具的Rails程序部署方案
Happycasts Capistrano
Capistrano 3 Upgrade Guide
Capistrano 3 Tutorial with Unicorn **
ow to Deploy a Rails 4 App With Git and Capistrano 2.x
Capistrano 3.1 + Rails 4 + RVM Simple Single-Stage Configuration
Capistrano Version 3 Guide
How-To Deploy Rails Applications Using Capistrano 3.1 and Windows 7
How To Use Capistrano to Automate Deployments: Getting Started
How To Automate Ruby On Rails Application Deployments Using Capistrano

⚠️ **GitHub.com Fallback** ⚠️