使用 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 