[料理佳餚] 用 Shell Script 在 CentOS 7 上實現 ASP.NET Core 的藍綠部署

在過去,發佈 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

參考資料

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學