使用 git post-receive 钩子部署服务端代码
在 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 in signals:
callnginx(commitmsg)
print('======= %s %s SUCCESS ======='%(branch, commitmsg))
else:
print('The signal "%s" is not in available list! '
'The list is %s.'%(commitmsg, str(signals)))
except subprocess.CalledProcessError as err:
print('======= %s %s %s ERROR ======='%(branch, commitmsg, err.output))
exit(1) exit(0)
四、从服务器的 git bare repostory 中部署服务端代码
在这个例子中,我假设 git 仓库和测试服务器处于同一台服务器上。这也是小团队开发比较普遍的情况。 服务端代码的结构
服务端代码位于 /opt/openresty/nginx/lua 这个文件夹中。在这个文件夹中有按照用户名称(分支名称)建立的子文件夹,使用这种方式,可以让多个开发者的服务端代码互不干扰,独立运行。 下面的文件夹结构展示了目前有两个开发者 master 和 zrong 已经部署了自己的服务端代码。 /opt/openresty/nginx/lua/
├── master
│ └── main.lua
└── zrong
└── main.lua
程序员甚至可以选择部署别人的代码到自己的文件夹中。它只需要在执行命令的时候提供希望部署的用户的分支名称(或者干脆提供一个 git 的 commit sha1),就能达到这种效果。 命令
server 包含服务端代码,命令需要区分是代码内容推送还是代码部署推送,或者二者兼有。 命令的设计如下: 若提交信息的第一行包含 UP 字样,就代表是命令推送。如果同时还有其他的提交信息,可以换行写。 如果只是普通的提交,那么直接写就可以了。 假设当前的 git 库位于 zrong 分支,而且当前的 git 仓库是干净的,下面是几个例子: Sample 1
将已经提交过的代码推送到服务器,并更新服务端代码: git commit --allow-empty -m 'UP' && git push origin zrong
Sample 2
将 server 仓库中的 allen 分支的内容更新到 /opt/openresty/nginx/lua/zrong : git commit --allow-empty -m 'UP allen' && git push origin zrong
Sample 3
将 sha1 为 7ebbf4f3151e2dfd5bdcbe9fe276fc9b6afd25e7 的提交中的内容更新到 /opt/openresty/nginx/lua/zrong : git commit --allow-empty -m 'UP 7ebbf4f' && git push origin zrong
导出一个 bare 仓库中的内容
位于服务器上的 git 仓库,通常是 bare 的,它没有 work-tree ,不能直接通过复制的方式来更新服务端代码。 但我们可以使用 git archive 命令先将仓库中的代码导出成一个包,然后再解压这个包到合适的部署路径即可实现服务端代码更新。 下面的代码做这样几件事: 找到 server.git 这个 bare 仓库;
处理 zrong 分支;
将 src/ 文件夹打包成 src.tar 文件。
– git --git-dir server.git archive -o src.tar zrong src/
如果只想要其中的一个文件,我们可以用 git show 把这个文件的内容输出到一个文件: git --git-dir server.git show zrong:src/main.lua > main.lua
具体代码
下面的代码展示了 server 项目中 post-receive 钩子的内容。 #!/usr/bin/env python
import sys
import re
import os
import subprocess
import shutil luahome = '/opt/openresty/nginx/lua' # 使用正则获取命令代码和要处理的分支信息
actionfmt = r'^UP ?(\w+)?$'
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, stderr=subprocess.STDOUT) # 从提交信息中得到要处理的真正分支
def getref(branch):
commitmsg = callgit('log', branch, '--oneline', '-1')[8:-1]
matchobj = re.search(actionfmt, commitmsg)
if matchobj:
if matchobj.group(1):
return matchobj.group(1)
return branch
return None # 将 server/src 备份到一个文件
def archive(refname):
tarfile = '%s/%s.tar'%(luahome, refname)
callgit('archive', '-o', tarfile, refname, 'src/')
return tarfile # 解压缩备份文件到正确的文件夹
def tarxf(tarfile, refname, branch):
refdir = os.path.join(luahome, branch)
if os.path.exists(refdir):
shutil.rmtree(refdir)
args = ['tar', 'xf', tarfile, '-C', luahome]
output = subprocess.check_output(args,
stderr=subprocess.STDOUT, universal_newlines=True)
shutil.move(os.path.join(luahome, 'src/'), refdir)
os.remove(tarfile)
return output oldrev,newrev,refname = sys.stdin.readline().strip().split(' ')
print('oldref:%s, newrev:%s, refname:%s'%(oldrev, newrev, refname)) branch = refname.split('/')[-1]
if not branch in branches:
print('The branch "%s" is not in available list! '
'The list is %s.'%(branch, str(branches)))
exit(1) try:
refname = getref(branch)
if refname:
tarfile = archive(refname)
succ = tarxf(tarfile, refname, branch)
print('update [%s] by [%s] success.'%(branch, refname) )
print('======= update [%s] by [%s] SUCCESS ======='%(branch, refname))
except subprocess.CalledProcessError as err:
print('update [%s] by [%s] error: %s'%(branch, refname, err.output))
print('======= update [%s] by [%s] ERROR ======='%(branch, refname))
exit(1) exit(0)
使用 git post-receive 钩子部署服务端代码的更多相关文章
- 创建APPID&&部署服务端教程
创建APPID&&部署服务端 一.创建APPID 1.打开https://console.developers.google.com ,左击顶部Project,然后左击创建项目 2.输 ...
- Socket通信客户端和服务端代码
这两天研究了下Socket通信,简单实现的客户端和服务端代码 先上winfrom图片,客户端和服务端一样 服务端代码: using System; using System.Collections.G ...
- 使用Jacoco统计服务端代码覆盖情况实践
一.背景 随着需求的迭代,需求增加的同时,有可能会伴随着一些功能的下线.如果不对系统已经不用的代码进行梳理并删除不需要的代码,那么就会增加系统维护成本以及理解成本.但经历比较长的迭代以及系统交接,可能 ...
- openssl实现双向认证教程(服务端代码+客户端代码+证书生成)
一.背景说明 1.1 面临问题 最近一份产品检测报告建议使用基于pki的认证方式,由于产品已实现https,商量之下认为其意思是使用双向认证以处理中间人形式攻击. <信息安全工程>中接触过 ...
- 根据wsdl,apache cxf的wsdl2java工具生成客户端、服务端代码
根据wsdl,apache cxf的wsdl2java工具生成客户端.服务端代码 apache cxf的wsdl2java工具的简单使用: 使用步骤如下: 一.下载apache cxf的包,如apac ...
- IOS IAP APP内支付 Java服务端代码
IOS IAP APP内支付 Java服务端代码 场景:作为后台需要为app提供服务,在ios中,app内进行支付购买时需要进行二次验证. 基础:可以参考上一篇转载的博文In-App Purcha ...
- Photon Server 实现注册与登录(二) --- 服务端代码整理
一.有的代码前端和后端都会用到.比如一些请求的Code.使用需要新建项目存放公共代码. 新建项目Common存放公共代码: EventCode :存放服务端自动发送信息给客户端的code Operat ...
- git分布式的理解----简单服务端搭建
Git是分布式的,并没有服务端跟客户端之分,所谓的服务端安装的其实也是git.Git支持四种协议,file,ssh,git,http.ssh是使用较多的,下面使用ssh搭建一个免密码登录的服务端. 1 ...
- Kickstart + http Linux自动化部署服务端
设备需要开启Network Boot功能.具体PXE技术就另外提,本文主要讲解配置. 在搭建该服务器之前需要关闭SELinux和iptables不然可能dhcp服务都起不来,客户端收不到IP地址,无法 ...
随机推荐
- 丑女贝蒂第一至四季/全集Ugly Betty迅雷下载
本季第一至四季 Ugly Betty (2006-2009)看点:<丑女贝蒂>在Betty Suarez的生命始终只有一个目标:加入到时尚行业中去.尽管要变得很聪明,工作很卖力而且要多产, ...
- sys.stdout.flush()以及subprocess的用处
sys.stdout.flush()立即把stdout缓存内容输出. subprocess与shell进行交互,执行shell命令等. 执行shell命令集合: subprocess.check_ou ...
- SVN OPS发布总结
提示 不需要手动将branch合并到trunk, 我们自己没有这个权限, 合并的操作是在beta版本发布完成以后, 使用这个btag来发布ops 问题 1. 由于trunk版本长时间没有发不过ops版 ...
- JAVA NIO non-blocking模式实现高并发服务器(转)
原文链接:JAVA NIO non-blocking模式实现高并发服务器 Java自1.4以后,加入了新IO特性,NIO. 号称new IO. NIO带来了non-blocking特性. 这篇文章主要 ...
- Windows8.1 关机异常的解决
昨天电脑无法正常关机,关机后风扇仍然转,硬盘也在读写,等了很长时间都没有完全关机,只能强制关机.以前其他系统也遇到过这个问题,因此考虑还是驱动问题.回想了下之前装过VirtualBox,考虑到应该是V ...
- 算法: skiplist 跳跃表代码实现和原理
SkipList在leveldb以及lucence中都广为使用,是比较高效的数据结构.由于它的代码以及原理实现的简单性,更为人们所接受. 所有操作均从上向下逐层查找,越上层一次next操作跨度越大.其 ...
- Centos7 Mysql 双机热备实现数据库高可用
mysql双主热备,也称主主互备,目的是mysql数据库高可用,只支持双机,原因是mysql的复制是一主多从,但一个从服务器只能有一个主服务器. 双机热备的条件是双机mysql版本必须一致. 服务器分 ...
- python3 CERTIFICATE_VERIFY_FAILED错误 certificate verify failed
在response = request.urlopen(url)打开一个https连接时报如下错误: urllib.error.URLError: <urlopen error [SSL: CE ...
- 【Eclipse】Eclipse性能调优
Eclipse性能调优 eclipse 吃内存_百度搜索 eclipse 性能调优之内存分配 - Defonds 的专栏 - CSDN博客 优化JVM参数提高eclipse运行速度 - Java综合 ...
- SQL Server 多条查询结果组合
假如存在下面这张表users: 1.通过sql语句返回总人数.男生总数和女生总数 select count(1) '总人数', (select count(1) from users u2 where ...