標籤:

通過Git Hooks實現自動部署

Linus開發的Git不只是提供了多人合作開發的新方式,還提供了自動化部署的優秀(快糙猛)解決方案。

為什麼需要自動化部署?

  • 當在本地計算機完成伺服器應用程序開發之後,需要把程序安裝到伺服器上,這樣的安裝過程一般稱之為部署。
  • 部署一般分為文件複製、重啟服務、安裝依賴等(PHP是世界上最好的語言!)。
  • 每次開發完成一個版本都需要部署一次。而部署工作屬於多次重複勞動。
  • 身為合格的程序員,應該把一切能夠自動化的勞動自動化。

通過Git Hooks實現的自動化部署,將實現敲入git push命令後,自動完成整個部署過程。

什麼是Hook?

很多人把Hook翻譯成「鉤子」(計算機行業很多中文譯名都難以理解),但根據維基百科,Hook一般指攔截軟體組件或操作系統之間的通信信息,並進行處理的代碼。那麼對應到Git是怎樣的呢?Git Hooks提供了多種形式的Hook,以pre-commit為例,該Hook將攔截git commit操作,運行名叫pre-commit的腳本,且僅當腳本返回值為0時進行真正的commit操作。

那麼自動部署所需使用的Hook名為post-receive. 該Hook將在伺服器端的bare repository接收到push信息並完成push操作後,進行執行;無法中斷客戶端(Client)的push過程。

可能瀏覽完上面的介紹,還是不太明白Hook是什麼。簡單地說,Hook是一種特殊的腳本(代碼),僅在滿足特定條件時執行。Git Hooks分別有對應各種操作的Hook,可以在git repository的.git/hooks目錄下看到。

$ lsapplypatch-msg.sample pre-commit.sample prepare-commit-msg.samplecommit-msg.sample pre-push.sample update.samplepost-update.sample pre-rebase.samplepre-applypatch.sample pre-receive.sample

以上的腳本文件(可以用編輯器打開)就是Hook了。可以看到腳本文件的後綴名都是sample,也就是說,這些都是Git自帶的Hook示例,並不會真正地被執行,真正被執行的Hook是沒有後綴的。若要啟用pre-push的Hook(在push操作前執行腳本,腳本返回值為0時執行push操作),在hooks目錄下新建一個pre-push的文件(沒有後綴名)。

在腳本中,你可以寫Bash、Python、JavaScript等代碼,Git通過Shebang來選擇執行代碼的解釋器。如果要寫Bash,Shebang可以是這樣:

#!/usr/bin/bash

使用Windows的讀者請注意,如果腳本文件含有BOM(位元組序標識符),可能會導致一些問題。

當完成腳本編寫後,別忘了添加「可執行」的許可權:

sudo chmod +x ./pre-push

如果之前的步驟都沒出問題,那麼一個Hook就基本完成了。

正確的(行為符合預期的)Git Hook需要具備的

  • 正確無誤的文件名:pre-receive、commit-msg等
  • Hook腳本文件具備「可執行」許可權
  • bug free的腳本代碼,以及腳本解釋器被正確引入、

最後一項顯然要困難得多,那麼進入下一話題。

如何測試Git Hooks

編寫Hook並非一蹴而就,其中可能遇到各種各樣的問題。那麼我們需要一種方式來測試Git Hooks,基本思想是先進行一次Git操作,記錄下腳本運行期間的上下文(詳細來說就是環境變數,用戶輸入等,上下文是它們的抽象)。

下面以測試post-receive為例,進行測試環境的搭建。根據資料和實踐,post-receive Hook將對每個commit讀取三個變數,第一個是上一個commit的ID,第二個是當前commit的ID,最後一個是當前commit的分支。

下面的命令基於Bash,將建立一個remote.git(bare repository)和一個local(repository)目錄,Git遠程庫的命名一般使用.git和其他目錄區分,但並非強制。

$ git init --bare remote.git$ git clone remote.git local$ lslocal remote.git

使用以下代碼獲取三個變數(注意文件名和執行許可權)

#!/bin/bash# 使用while循環是有必要的,因為一次push可能含多個commitwhile read oldrev newrev refname do echo oldrev: $oldrev echo newrev: $newrev echo refname: $refnamedone

之後在local中進行git操作,就能看到三個變數。得到三個變數後,就能單獨執行post-receive了。實際上是有模擬用戶輸入的方法的(使用expect),但post-receive不需要那麼複雜(省得又學一個工具不是美滋滋),直接利用Unix管道即可。

echo "129aoisdj zkjcnaxj master" | ./post-receive

上面的命令執行,對於Hook而言,和收到包含一個commit的push等價,免去了假裝add commit push的麻煩。

Git Hooks與root命令

有時在部署中需要用到root許可權,例如重啟應用;安全的方法是將root命令放在獨立的腳本中,然後設置文件許可權,允許腳本無密碼運行。

# 假設腳本為/home/production/restart.shsudo chown root:root /home/production/restart.shsudo chmod 700 /home/production/restart.sh

然後執行sudo visudo,在打開的文件中加入以下一行:

production ALL=(ALL) NOPASSWD: /home/production/restart.sh

示例:Python Web應用利用post-receive進行自動部署

#!/bin/bashWORKTREE=/home/production/websiteCONFIG=requirements.txtwhile read oldrev newrev ref do if [[ $ref =~ .*/master$ ]]; # 僅允許master分支部署 then echo "Pull to worktree..." #echo "$oldrev $newrev" cd $WORKTREE unset GIT_DIR git pull &> /dev/null # install PyPI packages git diff --quiet $oldrev $newrev -- $CONFIG source $WORKTREE/venv/bin/activate echo "virtualenv activated" if [ "$?" -eq "1" ] # 當requirements.txt被修改時,安裝依賴 then echo "requirements.txt changed" export LC_ALL=C echo "install packages..." pip3 install -r requirements.txt else echo "requirements.txt does not changed" fi sudo /home/production/restart.sh # 重啟服務 echo "deployment complete" else echo "This is not master branch, and it will not be deployed" fidone

推薦閱讀:

說說伺服器部署那些事兒
一位老極客的眼中的開發和部署
數人云|聽說大神都在用這25種軟體部署工具,你用過幾種?
Python項目自動化部署之一:舉個栗子

TAG:Git | 部署 |