使用 git post-receive 钩子部署服务端代码

  • 本站文章除注明转载外,均为本站原创或者翻译。
  • 本站文章欢迎各种形式的转载,但请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 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 命令先将仓库中的代码导出成一个包,然后再解压这个包到合适的部署路径即可实现服务端代码更新。

下面的代码做这样几件事:

  1. 找到 server.git 这个 bare 仓库;
  2. 处理 zrong 分支;
  3. 将 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 待验证的代码的更多相关文章

  1. 使用Windows下的git工具往github上传代码 踩坑记录

    使用Windows下的git工具往github上传代码 踩坑记录 背景 由于以前接触的项目都是通过svn进行版本控制,现在公司项目使用git,加上自己平时有一个练手小项目,趁着周末试着把项目上传到自己 ...

  2. 项目git commit时卡主不良代码:husky让Git检查代码规范化工作

    看完 <前端规范之Git工作流规范(Husky + Commitlint + Lint-staged) https://www.cnblogs.com/Yellow-ice/p/15349873 ...

  3. git用法之[回滚代码]

    我们在写代码的任何过程中,都有可能出错,任何过程都有可能要!回!滚!代!码!事关重大!一定要详细讲讲. 一.关于 工作区.暂存区.本地分支: 工作区:即自己当前分支所修改的代码,git add xx ...

  4. js 表单验证控制代码大全

    js表单验证控制代码大全 关键字:js验证表单大全,用JS控制表单提交 ,javascript提交表单:目录:1:js 字符串长度限制.判断字符长度 .js限制输入.限制不能输入.textarea 长 ...

  5. 使用 Git 来管理 Xcode 中的代码片段

    使用 Git 来管理 Xcode 中的代码片段 代码片段介绍 xcode4 引入了一个新 feature: code snippets,在整个界面的右下角,可以通过快捷键:cmd + ctrl + o ...

  6. jquery中邮箱地址 URL网站地址正则验证实例代码

    QQ网站有一个网站举报的功能,看了一些js代码觉得写得很不错,我就拿下来了,下面是一个email验证与url网址验证js代码,分享给大家 email地址验证 复制代码代码如下: function ch ...

  7. Ajax 表单验证 实现代码

    兼容: opera 9.6 + chrome 2.0 + FF 3 + IE 6 效果:一边输入一边实现验证 image 环境:ruby 1.8.6 + rails 2.1.0 + windows 核 ...

  8. php接口数据加密、解密、验证签名代码实例

    php接口数据加密.解密.验证签名 代码非常easy,这里就不多废话了,直接奉上代码 <?php /** * 数据加密.解密.验证签名 * @edit http://www.lai18.com ...

  9. 解决因为本地代码和远程代码冲突,导致git pull无法拉取远程代码的问题

    一.问题 当本地代码和远程代码有冲突的时候,执行git pull操作的时候,会提示有冲突,然后直接终止本次pull,查了些资料没有找到强制pull的方式,但是可以使用如下方式解决. 二.解决思路 可以 ...

随机推荐

  1. [Java.Web][Servlet]常用请求头

    response.setStatus(302); response.setHeader("location", "/day04/1.html"); 这段代码可以 ...

  2. Web项目中定时任务无法绑定SessionFactory的问题解决

    正常我们在web开发中,由于需要在页面上或者脱离事务时使用到懒加载对应的对象,一般都采用Open Session In View模式.   Open Session In View   OpenSes ...

  3. IP 地址 与 DNS

    IP地址转化 192.168.10.1 十进制,点分十进制地址 32位二进制数字序列,四段,八位 十进制与二进制转换00000000 = 000000001 = 2 * 0 = 100000010 = ...

  4. MySQL 库、表

    1.库 1.库的基本操作 1.查看已有的库 show databases; 2.创建库(指定默认字符集) create database 库名 default charset=utf8; 3.查看创建 ...

  5. ALSA声卡08_从零编写之框架_学习笔记

    1.整体框架 (1)图示((DAI(全称Digital Audio Interface)接口)) 在嵌入式系统里面,声卡驱动是ASOC,是在ALSA驱动上封装的一层,包括以下三大块 (2)程序框架 m ...

  6. ALSA声卡笔记4-----体验声卡

    1 .配置内核支持UDA1341 (1)内核 解压内核并打上补丁 配置内核 platform 需要设置哪些配置项,先看一下platform,需要把S3c24xx-i2s.c文件配置上去,dma.c也要 ...

  7. GNU/Linux操作系统总览

    计算机科学本科的专业课包括高等数学.离散数学.模拟电子技术.数字电子技术.微机原理.汇编语言原理.高级程序语言.操作系统原理.高级编译原理.嵌入式原理.网络原理.计算机组成与结构等诸多科目.GNU计算 ...

  8. deep learning and machine learning

    http://blog.csdn.net/xiangz_csdn/article/details/54580053

  9. t讯src的一点小秘密

    1.腾讯网首页发表评论未做限制 风险url:http://coral.qq.com/2774166934 使用burp的intruder模块生成payload 未做任何限制导致可批量提交大量的评论…… ...

  10. KNN笔记

    KNN笔记 先简单加载一下sklearn里的数据集,然后再来讲KNN. import numpy as np import matplotlib as mpl import matplotlib.py ...