[料理佳餚] 用 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,從遠端的儲存庫拉拉看有沒有新的發佈檔案? 如果沒有就執行 exit 0 離開程序。

cd /home/User/test-deploy

pull_logs=`git pull`

if [[ ! $pull_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

pull_logs=`git pull`

if [[ ! $pull_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 教學