Maintenance Mode of API Server
在曉數碼 Server Application 主要的服務就是提供 API 服務給遊戲的 Client 端,而隨著營運與版本更新的安排,遊戲伺服器就會有所謂「進入維護模式」的情況。本文稍微介紹了各時期敝團隊在 Server Maintenance Mode 的幾種實作方法。
Maintenance Switch at Application Layer
做為 Server 端開發的工程師,可以很簡單地在 Controller 的 super class 加上一個 filter 來進行檢查 — 就如同檢驗每個 request 是否來自正確的 session 一樣。當 filter 判斷目前系統處於維護模式,即提前返還帶有維護資訊內容的 Response 給 Client 端。
Example in Rails:
class ApiController < ApplicationController
before_action :check_maintenance def check_maintenance
render json: Maintenance.to_json if Maintenance.on?
end
end
再搭配建置後台,就可以讓營運人員可以開啟/關閉維護模式
然而這種方法下,每個 Request 仍然會對 Server 造成 overheads,在進入維護模式後其實仍然要維持服務,甚至還要有承受一定程度負載的能力
換言之,Application Service 其實還是在服務 Client,不論背後是要部署程式或改動資料庫等操作,都要顧及不能有 downtime 或炸掉 application 的事情發生 — 視 controller/filter 依賴什麼配置而定,更不用說要去變動 Infrastructure 之類的。(對,維護模式其實維護的不是伺服器)
Maintenance at Web Server
另一種常見方式是透過 web server (ex: Nginx) 設定規則來實現,常見的方式就是檢查某個代表維護模式的 flag (例如某個本地檔案是否存在),成立的話就直接回應或導向正在維護的訊息給 client,這樣就完全不需要進到 application level,大大減少資源的浪費。
Example in Nginx:
server {
listen 80;
//.... location / {
if (-f $document_root/maintenance.json) {
return 503;
}
... # the rest of your config goes here
}
error_page 503 @maintenance; location @maintenance {
rewrite ^(.*)$ /maintenance.json break;
}
}
這樣 Client Request 尚未進到 Application Layer 就被回應了, 不會受到 Application Service 的狀態影響。
但在 Large Scale 的架構下,會有多台的 Server 來處理 Client 的請求,如此就必須使每一台 Server 都讓規則成立,例如若是用本地檔案判斷,那就必須在每台 server 上都存在該檔案(或是讓各台 web server 共享 storage 取得維護標記與內容)
此做法讓 Application 被隔離且可以中斷服務,但此維護運作與原本運行的 Server 仍有依賴,至少跑在同機器上的 web server 不能掛掉。當需要變動底層 Infra. 時依然可能遇到麻煩。
Maintenance with Load Balancer
為了要讓伺服器真正進入可被維護的狀態,我們直接在 Load Balancer 進行操作。
在早期使用 AWS Classic Load Balancer 的時候,我們是直透過用替換 Load Balancer 中的 server,來實現隔離 Application Server 的維護模式
作法是另準備一台獨立的 server,上面跑的 http service 就僅單純地回覆代表維護資訊的 static content。在當開啟維護模式的時候,將該 Server Instance 加到 Load Balancer 中,然後將原本 Load Balancer 中的所有 Application Server 移出。
如此在維護期間,將由單純回覆維護訊息的 server 負責回應 client 的請求,主要的 Application 環境此時完全與外界分離,這時想怎麼操作它都可以了。
不過要將大量機器移出 Load Balancer 也是需要一點點時間的…
Loader Balancer Listener Rule
後來升級到 Application Load Balancer,有了 Listener & Target Group 的機制,更可以直接控制 Listener Rule 來實現所需的維護模式
作法一:導向不同的 Target Group
與 Classic Load Balancer 作法類似,但讓 Application Server 與負責回應維護資訊的 Web Server 分處兩組不同的 Target Group。
在啟用維護模式時,直接把所有 Request 改成導向放置 static content 的 Web Server 所在的 Target Group 就行,如此一來,變更 Rule 所需的時間遠小於 ELB 替換 server 的法作,而且穩定許多。
作法二:使用 Rule Condition + Fixed Response Action
ALB 的 Listener Rule 可以直接設定成回應固定內容,搭配 Rule Condition,這樣連額外的 static server 都不用準備了。
例如只要設定 Request 條件為所有路徑 ( path=/*) 即回應帶有維護資訊的 json 內容即可以了
但在初期 ALB Listener Rule 支援的 Condition 類型太少了,因為我們 API 的設計必須依 Request Header 來判斷回應的 content 的語系(所以才先採用了方法一)。
而現在, ALB Listener 已經支援 Http Header Condition 了。
直接呼叫 AWS CLI 就可以簡單完成了
e.g.:
message=$(cat maintenance_message_zh.json)aws elbv2 create-rule \
--listener-arn $YOUR_LISTENER_ARN \
--conditions 'Field=path-pattern,Values=/*' 'Field=http-header,HttpHeaderConfig={HttpHeaderName=X-Language,Values="zh"}' \
--priority 99 \
--actions 'Type=fixed-response,FixedResponseConfig={MessageBody="'"$message"'",StatusCode=503,ContentType=application/json}
- Conditions: 針對 1. 所有路徑 2. Header 帶有 X-Language 值為 zh 的 Request
- Actions: 回覆固定內容,並指定 Body (內容來自 .json 檔) + 503 Status+ ContentType 為 application/json
- Priority: 99(蓋過原本正常的 Forwarding rule)
要注意的是 AWS CLI 的版本需要更新 (我是升到 aws-cli/1.16.208) 才行,不然它會出現不支援 http-header 參數的錯誤
https://forums.aws.amazon.com/thread.jspa?messageID=897663
而結束維護模式,只要把剛建立的 Rule (priority=99) 刪除即可
e.g.:
rule_arn=$(aws elbv2 describe-rules --listener-arn "$YOUR_LISTENER_ARN" | jq -r '.Rules | select(.Priority == "99") | .RuleArn')
aws elbv2 delete-rule --rule-arn "$rule_arn"
結語
這是為了把應用層環境與外部隔絕,讓工程師可以放心地對伺服器們做照顧。我們的維護模式的一路實作演進。
當然在不同的環境架構(例如用 API Gateway 的情況,或在 Kubernetes 用 Ingress?)、因應不同需求,相信都有不同的選擇與作法。
若有何任想法或問題,歡迎留言討論指教~