背景#
公司用的是 AWS,目前主要服務是在 EC2 上,而不是 ECS 或 EKS。
發版的流程是會先將目前的程式碼和相關設定打包成一個 AMI,更新 Launch Template,再更新 Auto Scaling Group (ASG)。
即使上版前都會盡量測試,但公司的程式如果動到 legacy code,實在很難保證所有部件都沒問題。
之前甚至還有出現過 infra 異常,包出來的 ami 少安裝了 imagemagick,讓全站完全無法上傳圖片。
當發現線上程式異常時,EC2 rollback (主要是 ASG instance refresh) 一次需要五分鐘的時間,多發生幾次就覺得對心臟有點不好…
於是決定開始研究 EC2 要怎麼執行藍綠部署。
在 AWS 文件洋洋灑灑的列了一堆可用的藍綠部署。 不過去查發現 AWS 真的有做到全自動化的只有 CodeDeploy 這個選項,其他都是需要自己撰寫流程。原本想說有原生走原生就好,但 CodeDeploy 的坑實在太大,最後還是手動土炮自己的流程出來。
流程#
這個作法是使用 Load balancer 進行流量切換,簡要流程如下
- 準備兩個 ASG,各自綁定在不同 Target Group 上。我們稱線上環境為 Blue,即將部署的環境為 Green。
- 開始時,Application Load balancer (ALB) 上的某條 Rule 同時綁定這兩個 Target Group。此時 Rule 上 Blue 的權重為 100,Green 的權重為 0,也就是全流量為 Blue。
- 調整 Green 環境版號,並將數量調節成跟 Blue 一致。等待 Green Healthy。
- 在 ALB 上,一口氣將所有流量從 Blue 切到 Green。此時 Rule 上 Blue 權重為 0,Green 權重為 100。也就是全流量為 Green。
- 等待猶豫期,若流量正常,關閉 Blue 環境。若流量不正常,將流量導回 Blue,並關閉 Green 環境。
若想要做金絲雀(Canary) 部署,只要在步驟 4 稍微更改流量比重即可。
有一個重點是防呆。這個流程中最嚴重的狀況有可能為:
- 將流量導向空的 Target Group,網站會直接回傳 5xx。
- 將目前的流量導向的 ASG 關閉,沒備援的情況下會直接停機,也是 5xx。
做藍綠部署就是避免錯誤部署導致的停機行為,如果在 CI 環節中可以因手誤就引發停機錯誤,那還不如用 instance refresh 安全性高一點。
CI 實作細節#
由於流程較長,這邊只會提一些比較重要的 CLI,其餘用文字描述。
如何分辨此次部署,哪個 ASG 是 Blue,哪個 ASG 是 Green#
Ans: 觀看目前的 Target Group。
使用 AWS 的 API 去查詢目前 ALB 上的 Listener Rule,理應只有兩個 Target Group,100權重的 Target Group 對應到的 ASG 就是 Blue 環境。
防呆:若出現意外情況,例如想查詢的 Target Group 不在該 Rule 中,請直接終止部署行為。
當查詢結束後,這一整個 pipeline 就要以此為基準。因此要將其存為 gitlab artifacts 供後續流程使用。
對應的 aws cli 如下,可以靠 describe-rules 來判斷目前的 Blue 和 Green。
BLUE_TG_ARN=$(aws elbv2 describe-rules --rule-arns "$ALB_LISTENER_RULE_ARN" --query "Rules[0].Actions[0].ForwardConfig.TargetGroups[?Weight==\`100\`].TargetGroupArn" --output text)
GREEN_TG_ARN=$(aws elbv2 describe-rules --rule-arns "$ALB_LISTENER_RULE_ARN" --query "Rules[0].Actions[0].ForwardConfig.TargetGroups[?Weight==\`0\`].TargetGroupArn" --output text)Green 環境準備#
每次部署時,會將新的 launch template 版本套用在 Green ASG 上。並將目前的 Blue 的數量 (min/max/desired),更新到 Green 環境。
這邊要特別注意:aws 在更新 launch template 時,會把 override 的選項給拿掉,這些選項包含了是否要使用 spot, 要用什麼 instance type 等。所以如果有 override 的必須想辦法處理,我是更新前讀取以前的結果再塞回去。
可以靠 describe-auto-scaling-groups 拿舊資料
BLUE_ASG_INFO=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "$BLUE_ASG" --output json)
GREEN_ASG_INFO=$(aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names "$GREEN_ASG" --output json)
DESIRED_COUNT=$(echo "$BLUE_ASG_INFO" | jq -r '.AutoScalingGroups[0].DesiredCapacity')
MIN_SIZE=$(echo "$BLUE_ASG_INFO" | jq -r '.AutoScalingGroups[0].MinSize')
MAX_SIZE=$(echo "$BLUE_ASG_INFO" | jq -r '.AutoScalingGroups[0].MaxSize')
CURRENT_COUNT=$(echo "$GREEN_ASG_INFO" | jq -r '.AutoScalingGroups[0].DesiredCapacity')防呆: 若在開啟 Green 環境時,發現 Green ASG 的當前數量不為 0。請直接終止部署行為,否則有可能會有多個版本交雜在同一個 ASG 的狀況。
再靠 update-auto-scaling-group 更新 Green ASG (省略了 UPDATED_POLICY 的生成流程)
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name $GREEN_ASG \
--mixed-instances-policy "$UPDATED_POLICY" \
--min-size $MIN_SIZE --max-size $MAX_SIZE --desired-capacity $DESIRED_COUNT最後則是使用 elbvv2 wait target-in-service 等待 Target Group 變成 healthy。全 healthy 後 Green 環境才算準備完畢。
aws elbv2 wait target-in-service --target-group-arn $GREEN_TG_ARN切換流量#
當 Green 環境準備完畢後,就可以準備切換流量。
aws elbv2 modify-rule --rule-arn $ALB_LISTENER_RULE_ARN --actions "$ACTIONS" --no-cli-pager防呆:在任何切換流量的行為執行前,都應先確認即將切換的 Target Group 是否已經是非空且所有 instance 皆 healthy。
之後若要 rollback,指令基本上是一樣的。
關閉環境#
最後一個步驟為關閉環境,可以將 desired / min / max 皆設定為 0。
防呆:在關閉環境之前,都應先確認當前環境對應的 Target Group,是否正在接受 100% 的流量。
