- 本站文章除注明转载外,均为本站原创或者翻译。
- 本站文章欢迎各种形式的转载,但请18岁以上的转载者注明文章出处,尊重我的劳动,也尊重你的智商;
- 本站部分原创和翻译文章提供markdown格式源码,欢迎使用文章源码进行转载;
- 本博客采用 WPCMD 维护;
- 本文标题:使用 git post-receive 钩子部署服务端代码
- 本文链接:http://zengrong.net/post/2221.htm
在 git 中提交服务器源码的时候,如果能够直接更新到测试服务器,并且重启服务使其生效,会节省懒惰的程序员们大量的时间。
git 的 Server-side hook (服务端钩子/挂钩)可以用来做件事。
本文以部署基于 OpenResty 的服务端程序为例来介绍我的做法。
技术信息
- OS: CentOS 6.3
- 服务器软件: OpenResty
- 开发语言: Lua
名词解释
- 服务器: 服务器硬件 + OS
- 服务端程序: OpenResty 在服务器中的进程
- 服务端代码: 部署在 OpenResty 中的 Lua 源程序
一、git 服务端钩子类型
Pro git 中介绍了 git 钩子的几种类型,其中和服务端相关的有:
- pre-receive
在客户端推送时最先执行,可以用它来拒绝客户端的推送。 - update
与 pre-receive 类似,但会在每个分支都执行一次。 - post-receive
在客户端推送完成后执行。
根据我的需求,我选择 post-receive
钩子来做这件事。因为我不希望拒绝客户端的推送(那样程序员们可能不知道该怎么办)。推送总是会成功的,只是 命令 不成功而已。碰到 命令 不成功的情况,客户端只需要再次推送一个正确的 命令 即可。
关于 命令 的配置,后面会详述。
二、git repostories
我建立了2个 git 仓库来完成这个任务。分成2个仓库的好处是,更新服务端代码和控制服务端程序互不干扰。
在开发服务器上,我可以将 OpenResty 的 lua file 缓存关闭。这样服务端代码更新后会立刻生效,不必重启服务端程序。
而如果服务端程序出现错误,只需要更新它的状态(reopen/reload 等)即可,不必更新服务端代码。
server.git
这个仓库保存服务端逻辑代码,客户端的文件夹结构如下:
server ├── README.md └── src └── main.lua
每次提交代码的时候,在提交信息中可以包含特定的 命令 ,在推送这次提交时,git 服务端钩子就会起作用,将提交的代码更新到合适的地方。
如果提交信息中没有特定的 命令 ,那么这就是一次普通的推送而已。
在本例中,钩子所做的事情就是将 src/
文件夹中的所有代码更新到服务端程序中。
serverctrl.git
这个仓库是空的,永远不会有内容。通过在提交信息中包含特定的 命令,git 服务器钩子会对我们的服务端程序进行给定的操作。
三、使用钩子重启服务端程序
OpenResty 的进程控制
使用 nginx 自己提供的 -s
参数来控制服务端程序:
nginx -s [stop|quit|reopen|reload] -p /path/to
不带 -s
参数,就是 start 功能了:
nginx -p /path/to
命令
serverctrl
是个空项目,不可能有任何内容。因此我规定提交信息中直接写操作命令即可。
要控制服务端程序重启,只需要进行这样的提交和推送:
git commit --allow-empty -m ‘reopen‘ && git push origin zrong
执行 [start|stop|quit|reload]
也是一样道理。
具体代码
下面的代码展示了 serverctrl
项目中 post-receive
钩子的内容。钩子可以用操作系统能够识别的任意脚本语言来撰写。这里我使用的是 Python (2.7和3.4通用)。
下面的代码和注释已经非常清楚了,我说一下几点要注意的。
- 在 callnginx 方法中,需要把
subprocess.check_output
方法的stderr
参数设置为 STDOUT 。因为如果执行 nginx 出错,出错信息默认是写入到 STDERR 中的,如果不进行这样的修改,出错时返回的输入信息就是空的。 - 需要把 nginx 程序以及 ‘/opt/openresty/nginx’ 整个文件夹和其下文件的 owner 设置为 git 用户,否则钩子将没有权限启动 nginx 进程。
post-receive
钩子本身的 owner 也要设置成 git 用户,并给予执行权限。- 如果已经有一个
-p
参数(prefix)相同的 nginx 进程在运行了,注意先将其结束。否则 git 用户可能无权关闭这个进程。
–
#!/usr/bin/env python
import sys
import os
import subprocess
# prefix 配置路径
openresty = ‘/opt/openresty/nginx‘
# 执行文件路径
nginx = openresty + ‘/sbin/nginx‘
# 能够识别的信号
signals = (‘start‘, ‘stop‘, ‘quit‘, ‘reopen‘, ‘reload‘)
# 支持的分支(用户)
branches = (‘master‘, ‘allen‘, ‘zrong‘, ‘xiefei‘, ‘zm‘)
def getgitargs(*args):
if args:
return [‘git‘, ‘--bare‘, ‘--git-dir‘, os.getcwd()] + list(args)
return []
def callgit(*args):
return subprocess.check_output([‘env‘, ‘-i‘] + getgitargs(*args),
universal_newlines=True)
def callnginx(action):
args = [nginx, ‘-p‘ ,openresty]
if action != ‘start‘:
args += [‘-s‘, action]
return subprocess.check_output(args,
stderr=subprocess.STDOUT, universal_newlines=True)
# 钩子会将信息从 STDIN 写入,将这些信息读入变量
oldrev,newrev,refname = sys.stdin.readline().strip().split(‘ ‘)
# 对我们的程序而言,只有 branch 名称有用
branch = refname.split(‘/‘)[-1]
print(‘oldref:%s, newrev:%s, refname:%s‘%(oldrev, newrev, refname))
if not branch in branches:
print(‘The branch "%s" is not in available list! ‘
‘The list is %s.‘%(branch, str(branches)))
exit(1)
try:
# 得到当前提供的 branch 下的最新提交信息
commitmsg = callgit(‘log‘, branch, ‘--oneline‘, ‘-1‘)[8:-1]
print(branch+‘ ‘+commitmsg)
if commitmsg