在過去,發佈 Web 應用程式到 IIS 上,只要把新發佈的檔案覆蓋掉線上的檔案,IIS 就自動幫我們處理好新舊版的切換,現在搬到 Linux,這個新舊版切換的程序就得自己來了,第一個想到的工具就是 Shell Script。
環境說明
這邊我們不考慮 CI/CD 那一段,我們假設 CI/CD 都是正常運作的,我們使用 Git 來當作發佈檔案的儲存庫,使用 Nginx 做反向代理,部署兩個服務(一個 Hot、一個 Cold),所以大致的流程是這樣的:
我們就一個步驟一個步驟地來展示每個程序的 Shell Script,必要時做一點補充說明。
Who is Production? Who is Stage?
使用 cat
擷取 Nginx 的設定檔,搭配 Regular Expression 找出目前正在執行的是哪一個服務? 將正在執行的服務標記為 Production,另一個為 Stage。
conf=`cat /etc/nginx/conf.d/www.conf`
[[ $conf =~ localhost:([[:digit:]]+) ]]
if [ "${BASH_REMATCH[1]}" == "8001" ]
then
prod="WebApp-8001"
stage="WebApp-8002"
else
prod="WebApp-8002"
stage="WebApp-8001"
fi
Stop Stage service
我們找出 Production 及 Stage 之後,如果發現 Stage 的服務正在執行,我們必須把它停掉,避免待會兒複製檔案失敗。
if [ "`systemctl is-active $stage`" == "active" ]
then
sudo systemctl stop $stage
fi
Pull from deployment repository
到這邊我們就可以執行 git pull
將檔案從遠端的儲存庫拉下來,然後再透過 git diff
比對前後記錄有沒有新的發佈檔案? 如果沒有就執行 exit 0
離開程序。
cd /home/User/test-deploy
prev_sha=`git rev-parse HEAD`
git pull --quiet
diff_logs=`git diff --stat=1000,1000 $prev_sha`
if [[ ! $diff_logs =~ [1-9][[:digit:]]*[[:space:]]files?[[:space:]]changed ]]
then
exit 0
fi
Mirror copy deployment files to Stage folder
新的發佈檔案下載完畢後,我們利用 rsync
將檔案鏡像複製
到 Stage 的目錄去,一行指令就搞定了。
rsync -rtD --force --delete --ignore-errors /home/User/test-deploy/WebApp/ /var/www/$stage/
Start Stage service
啟動 Stage 服務,讓它呈現 Ready 的狀態,在這個步驟我們可以額外安插一些暖機或測試的指令碼進去,讓服務待會兒切換過來的時候,能夠更流暢及穩定一些。
sudo systemctl start $stage
Modify and reload Nginx's conf
接下來,我們要修改 Nginx 的設定檔,我的服務名稱即含有 Port Number,所以利用 Regular Expression 就可以找到 Production 跟 Stage 的 Port Number,使用 sed
工具修改檔案進行字串的替換,然後重新載入 Nginx 的設定檔。
[[ $prod =~ [^-]+-([[:digit:]]+) ]]
prod_authority="localhost:${BASH_REMATCH[1]}"
[[ $stage =~ [^-]+-([[:digit:]]+) ]]
stage_authority="localhost:${BASH_REMATCH[1]}"
sudo sed -i "s/${prod_authority}/${stage_authority}/g" /etc/nginx/conf.d/www.conf
sudo nginx -s reload
Stop Production service
最後將 Production 服務給關閉,但是為了避免還有一些 Request 還在處理,所以可以延遲一段時間後,才停掉服務。
sleep 30
sudo systemctl stop $prod
完整指令碼
#!/bin/bash
conf=`cat /etc/nginx/conf.d/www.conf`
[[ $conf =~ localhost:([[:digit:]]+) ]]
if [ "${BASH_REMATCH[1]}" == "8001" ]
then
prod="WebApp-8001"
stage="WebApp-8002"
else
prod="WebApp-8002"
stage="WebApp-8001"
fi
if [ "`systemctl is-active $stage`" == "active" ]
then
sudo systemctl stop $stage
fi
cd /home/User/test-deploy
prev_sha=`git rev-parse HEAD`
git pull --quiet
diff_logs=`git diff --stat=1000,1000 $prev_sha`
if [[ ! $diff_logs =~ [1-9][[:digit:]]*[[:space:]]files?[[:space:]]changed ]]
then
exit 0
fi
rsync -rtD --force --delete --ignore-errors /home/User/test-deploy/WebApp/ /var/www/$stage/
sudo systemctl start $stage
[[ $prod =~ [^-]+-([[:digit:]]+) ]]
prod_authority="localhost:${BASH_REMATCH[1]}"
[[ $stage =~ [^-]+-([[:digit:]]+) ]]
stage_authority="localhost:${BASH_REMATCH[1]}"
sudo sed -i "s/${prod_authority}/${stage_authority}/g" /etc/nginx/conf.d/www.conf
sudo nginx -s reload
sleep 30
sudo systemctl stop $prod