第一节介绍了gitolite mirror,使得代码能够被传送到服务器组的内部网络中,但是接下来如何将代码再部署到上千台服务器上,而且还能够实现代码回滚呢?

    capistrano使用ruby语言开发,所以懂得ruby就可以熟练使用capistrano了。鉴于capistrano3.x需要使用ruby1.9以上的ruby版本,所以我还是选择了2.x。

我使用的是CentOS6.4,自带"ruby-1.8.7.352-7.el6_2.x86_64":

# yum install ruby
# yum intall rubygems
# gem install capistrano -v 2.15.5  
# gem install capistrano-ext

capistrano2.x与3.x版本有很大的不同,所以2.x上面的命令不能直接在3.x上面直接运行,比如工作目录的初始化就不同:

# mkdir /tmp/test3
# cap install 

# mkdir /tmp/test2
# cd /tmp/test2
# capify . 
# tree        这里是初始化后的工作目录,deploy.rb是主配置文件。
.
├── Capfile
└── config
    └── deploy.rb


管理端capistrano执行后,会给app服务器带来什么改变呢?

    首先会在目标目录中创建release目录,主要用来存放代码,而且所有的代码均以不同的版本号命名。另外还会创建share目录。最后创建的是current软连接,current用来指向release目录中最新的代码版本号。

# tree
.
├── current -> /data/capistrano/pandaarcher/releases/20150107020401
├── releases
│   └── 20150107020401
│       ├── a
│       ├── config.json
│       ├── db.js
│       ├── def.js
│       ├── Deploy.txt
│       ├── log -> /data/capistrano/pandaarcher/shared/log
│       ├── log.js
│       ├── panda_score.js
│       ├── public
│       │   └── system -> /data/capistrano/pandaarcher/shared/system
│       ├── REVISION
│       ├── tmp
│       │   └── pids -> /data/capistrano/pandaarcher/shared/pids
│       └── worker.js
└── shared
    ├── log
    ├── pids
    └── system

12 directories, 9 files


capistrano的回滚功能:

    当我们执行rollback以后,就会将release目录中最近一次部署的代码版本删除,同时将current重新连接到上次的版本号目录。最后还会帮助我们restart服务。所以我们要使用rollback功能,你的配置文件中必须有restart功能,否则会报错奥!


    capistrano的工作原理很简单,与ansible一样都是基于SSH从capistrano管理主机传递命令到app服务器。所以执行账户必须能够使用app服务器上面已有的账号(其shell不能是/sbin/nologin)执行命令。


    在这里明确下我们最终的需求:将程序猿提交到公司内部git服务器的代码通过gitolite mirror主动传输到线上服务器所在的内网git服务器,然后运维人员搭建对应项目的capistrano工作目录,对其做好配置,确保其能够正常工作。然后在Hudson上面建立此项目,添加此项目的程序猿的工作账号并授权。当代码需要更新时,程序猿自己就可以通过网页界面直接更新此项目,而无需去再和运维协调。


    在这里再简单的描述下Hudson,我们需要的只是Hudson可以执行shell命令的功能,它在执行后不仅可以返回执行结果,而且还可以返回详细执行过程。所以,我们让Hudson去调用capistrano中对应的项目即可,其中需要注意的是权限问题:

(1)管理机Hudson用户必须有足够的权限去进入capistrano工作目录并执行命令,所以我会直接将此工作目录的属主、属组定为Hudson用户。

(2)管理机Hudson账号必须能够通过ssh连接到app服务器指定的账号去启动,比如在app服务器建立capistrano账号,然后将管理主机的Hudson账号的公钥添加到app服务器的capistrano账号的authorized_keys中建立单向密钥认证,当然你也可以每次执行输入密码。

(3)当将代码推送到app后,所有的文件的属主、属组必然是capistrano用户的,作为web主机,一般向外提供服务时为了安全起见,是不能够让启动者有登录权限的。所以capistrano用户不能作为web服务器的启动者。所以需要赋予capistrano用户可以执行/sbin/runuser的sudo权限,使得capistrano可以调用www(web启动者)启动服务。

(4)但是权限问题依然存在,由于代码的属主、数组都属于capistrano,所以www能不能进入代码目录并执行代码依然是个问题。同时由于需要对代码可以进行回滚,capistrano用户也必须同时具有代码的RWX权限。所以sgid和setfacl功能是解决此问题的好办法。

(5)考虑到app会产生日志,所以我们还需要额外建立log目录,并设定其属主、属组为www。


    回到capistrano,默认情况下,它会一次性并发向所有的服务器发送对应的指令,此过程是阻塞的,也就是我们会一直等待capistrano给我们返回结果。对于update代码,对线上的业务是没有影响的,但是对于restart操作,那么如果你指定了LVS后面的所有的app服务器,那么此app在此期间是完全不可用的。我的处理方法是写多个stage文件,然后分别执行,或者在restart代码中加上一些很丑的代码。

具体环境和操作方法如下:

    10.0.5.90:公司内部git服务器,本机root管理gitolite-admin

    10.0.5.91:线上gitolite mirror服务器,本机root管理gitolite-admin;同时也是capistrano+Hudson服务器

    10.0.6.153:线上app服务器

    我们希望将代码部署到app服务器的/data/capistrano/car/目录中,然后实际的对外发布的目录是/data/html/。而且这里启动服务是以capistrano用户启动的,暂时不使用上面提到的www用户。

at 10.0.6.153:
# useradd capistrano
# mkdir /data/capistrano/car
# mkdir /data/car
# mkdir /data/logs/car
# chown capistrano.capistrano /data/capistrano/car
# chown capistrano.capistrano /data/html/
# chown capistrano.capistrano /data/logs/car
# sudo su - capistrano
# ssh-keygen -b 2048
# scp /home/capistrano/.ssh/id_rsa [email protected]:/root/10.0.6.153.capistrano.pub
        赋予capistrano用户访问git仓库的权限
at 10.0.5.91:
# rpm -ivh hudson
# usermod -s /bin/bash hudson
# sudo su - hudson 
# ssh-keygen -b 2048
# scp /var/lib/hudson/.ssh/id_rsa.pub [email protected]:/root/10.0.5.91.hudson.pub
        hudson用户在调用capistrano时也必须具有访问git仓库的权限
# exit
# cd /root/gitolite-admin
# mv /root/{10.0.5.91.hudson.pub,10.0.6.153.capistrano.pub} key/
# vim conf/gitolite.conf
   repo gitolite-admin
       RW+    =    admin        
    
   repo car
       RW+    =    server-zhu   
       R      =    10.0.6.153.capistrano   
       R      =    10.0.5.91.hudson
       option mirror.master    =    zhu
       option mirror.slaves    =    cong


所以capistrano项目部署如下:

set :application, "car"        
        app服务器代码部署目录
set :repository,  "[email protected]:car"   
        指定git服务器,
role :app, "10.0.6.153","10.0.6.155"
        指定app服务器的ip
        这里可以指定多个role
set :keep_releases, 30
        指定保存多少个版本,默认是5个
set :scm, :git
        这里可以使用git、svn等。甚至可以不使用,也就是从file调用
set :deploy_to, "/data/capistrano/#{application}"
        部署到我们指定的位置
set :user, "capistrano"
        指定在app服务器上的用户
set :runner, "capistrano"
        指定运行用户
default_run_options[:pty] = true 
        当我们下面需要sudo时,就必须使用一个pty。
set :use_sudo, false
        这个与执行命令中的sudo无关。
        加上后,app主机中的capistrano执行的操作都是以sudo权限运行的。
set :normalize_asset_timestamps, false

namespace :deploy do
    task :default, :roles => :app do
            这里":roles => :app",可以省略,但加上以后使得配置更加直观。
        update
        link
        stop
        start
    end
    
    task :link, :roles => :app do
        run "ln -nfs #{deploy_to}/current/ /data/html/car"
            强制建立/data/capistrano/car/current到/data/car的软连接。
            另外由于软连接的权限是777,所以如果你是第一次部署,其属主为root,
            capistrano也可以强制奥
            另外,由于ruby会自动的在#{deploy_to}/current/下面生成一些没用的东西,
            所以如果想要/data/html/car比较干净的话,可以在程序猿一开始提交时,
            就提交一整个的目录,而不是散列的东西到git仓库。
            然后做软连接时,可以是ln -nfs #{deploy_to}/current/realcode /data/html/car
    end
    
    task :start, :roles => :app do
         run "cd /data/html/#{applicate}  && (nohup node car.js >/dev/null 2>&1 &)" 
            这里是设定启动car服务的命令
            如果car服务是启动在1024以下的端口,那么必须以root用户启动了,stop亦然
    end  
    
    task :stop, :roles => :app do
         run "ps aux | grep car | grep -vw grep | awk '{print $2}' | xargs sudo /bin/kill && sleep 3"             
    end
      
    task :restart, :roles => :app do
        find_servers_for_task(current_task).each do |server|
            run "ps aux | grep car | grep -vw grep | awk '{print $2}' | xargs sudo /bin/kill && sleep 3 && cd /data/html/#{application}  && (nohup node car.js >/dev/null 2>&1 &)",:hosts => server.host
        end
            以上命令只是上面的stop,start重新写了一遍,
            如果不需要逐个主机的重启,那么我们可以直接写stop回车后start
    end

end


当你的源不是git,而是本地的文件,可以这么写:

set :repository,  "127.0.0.1:/source/"
set :deploy_via, :copy


当你需要处理git分支时:

set :branch, 'staging'


stage的多段处理:

    (1)当你一个项目中又包含多个子项目,所有子项目需要分别的管理,那么就需要stage了。

    (2)或者你不希望使用上面restart中那样格式的追个重启主机,那么你也可以使用stage将各个主机分别写入一个文件中。方法如下:

(1)修改deploy.rb文件,删除namespace以后的内容和role行,并添加如下内容:
require "capistrano/ext/multistage"
set :stages, %w(app1 app2)
set :default_stage, "app1"    制定默认,可以省略

(2)在config目录下创建deploy目录,并在在其下面创建app1.rb、app2.rb文件。
在app1.rb文件中添加namespace内容,并且添加对应的role。

执行方法:cap app1 deploy:start


cap命令:

(1)cap deploy:setup,初始化,创建release和share目录

(2)cap deploy:update,上传代码目录到release并命名为日期格式的数字名称目录名,同时创建其软连接到#{delploy_to}/current

(3)cap deploy:start,启动服务

(4)cap deploy:stop,关闭服务

(5)cap deploy:rollback,回滚

(6)cap deploy:check,检查


最后说Hudson,Hudson功能很强大,但是我们只利用其shell功能。同样,Hudson是安装在管理主机10.0.5.91上面的。

# rpm -ivh hudson-3.2.1-1.1.noarch.rpm

然后使用浏览器访问:http://10.0.5.91:8080

(1)登录后,首先会让我们在线安装Hudson组件,这里只选核心组件。安装过程十分漫长。

(2)用户授权:

        首先需要注册一个用户,这第一个用户其实就是具有supper权限,然后"系统管理"→"Configure Security”→“Hudson专有用户数据库”→"项目矩阵授权策略"


capistrano+hudson_第1张图片

 上面的user1、user2也是先注册后授权的奥!


(3)对项目car进行用户权限管理

wKioL1Ss3P6yD6I3AACTdCm1dQA501.jpg

    这样,管理员新建的这个项目car,在只授予user1权限时,其他用户是看不到此项目的

    而且,此项目只能被执行,不能被修改

capistrano+hudson_第2张图片



(4)最后是如何让Hudson执行命令:

capistrano+hudson_第3张图片

(5)让程序猿进入此项目,直接点击构建就可以更新代码了。