Appearance
要實現 Kubernetes 零停機更新倚賴健康檢查的功能,Kubernetes 提供的健康檢查(Health Check)有兩種 Liveness
及 Readiness
,兩種檢查的語法設定完全相同,但有不同的意義,本篇僅介紹 Readiness
。
readinessProbe
用來探測容器是否已經準備好接受流量,表示一個就緒的狀態。
試想當我們部署一個新的版本,如果直接將外部流量導向新的版本,此時容器的啟動指令 command
可能才運行到一半,或因為一些例外無法回應請求,表示這個容器還沒有準備好對外服務,如果此時 K8S 就將舊版本的 pod 換成這些新版本的 pod 作為服務提供,就會導致這個在線服務中斷。
換句話說 readinessProbe
就是用來檢查容器是否能夠處理流量,唯有確保容器是健康的(可處理請求),才將服務對外,正確設定 readinessProbe
就能實現零停機更新。
readinessProbe
使用方法
yaml
specs:
containers:
- name: server
image: your-image
readinessProbe:
httpGet:
scheme: HTTP # HTTP(default) or HTTPS
path: /healthy
port: 8080
httpHeaders:
- name: Host
value: example.com
initialDelaySeconds: 10
periodSeconds: 5
上例 readinessProbe
使用 HTTP 請求 /healthy
(port 8080),如果 HTTP 回應狀態碼為 200-400
,表示檢測成功,容器可以對外服務。initialDelaySeconds
表示容器啟動後多久開始檢測,可以設定一個相對長的時間,因為我們可預期容器的啟動指令不會立刻能夠處理請求,periodSeconds
表示檢測的間隔時間。httpHeaders
不是必要的設定,可以用來設定向容器發出檢測請求的 headers,例如:你的服務需要驗證 Host
標頭,就可以透過此方法設定。
使用指令檢測
除了 httpGet
以外 readinessProbe
還支援使用指令檢測,例如:
yaml
specs:
containers:
- name: server
image: your-image
readinessProbe:
exec:
command: ["curl", "https://www.google.com"]
initialDelaySeconds: 10
periodSeconds: 5
curl https://www.google.com
可以替換成任何指令。
成功的定義為指令執行結束的狀態碼為 0
,如果指令返回非零的狀態碼,表示檢測失敗,會等 5 秒後再次檢測。
優雅的關機與 Pod 終止
運用前面提到的 readinessProbe
我們可以確保 pod 在真正能處理請求的時候才對外公開,但要做到零停機更新還有一個問題需要克服,這個問題發生在更新版本時舊版 Pod 的終止流程,Kubernetes 在終止 Pod 以前會向主程序發出 SIGTERM
信號,讓主程序得以進行優雅關機,正在進行優雅關機的主程序無法再對外服務,但仍可能收到外部流量導致服務中斷,把問題講得白話一點「舊 Pod 的程序已經進入無法處理請求的狀態,但仍處於對外服務的狀態」,這個問題跟 Pod 終止的生命週期有關。
Pod 終止的生命週期
- Pod 被設為
Terminating
狀態,並從 Service Endpoints 移除。 此時 Pod 不會再收到外部流量,但 Pod 內部的 Container 本身可能還未終止。 - Pod 的
preStop
hook 被調用,如果沒設定就略過,接著會向主程序發出SIGTERM
信號,讓主程序可以進行優雅的關機。 - Pod 依照
terminationGracePeriodSeconds
的設定(預設 30 秒),給予主程序有限的時間去處理優雅關機的工作。preStop
的運行時間也包含在這時間內。 - 如果在超時以前,主程序就完成優雅的關機而結束,Pod 就會被移除;如果超時後主程序仍未結束,就會收到
SIGKILL
信號遭強制刪除,接著 Pod 被移除。
步驟 1 與 2 是非同步進行,步驟 1 確實比步驟 2 先發生,但步驟 2 不會等步驟 1,因此兩步驟存在 race condition,舉一個實際的例子會比較好理解,假設事件發生的順序如下:
- Pod 被設為
Terminating
,發出「從 Service Endpoints 移除的請求」。 - Pod 收到
SIGTERM
信號,開始處理優雅關機。 - 使用者的流量抵達,被轉交給 Pod,此時 Pod 已經不具備處理請求的能力,使用者最後會收到 5xx 系列的錯誤回應。
- Pod 真正的從 Service Endpoints 中被移除。
一個避免服務中斷的方法是藉由 preStop
hook 來延緩 Pod 收到 SIGTERM
信號的時間:
yaml
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
概念上就是給步驟 1 有 10 秒的緩衝時間,讓「從 Service Endpoints 移除的請求」這件事有足夠的時間被完成。 當 Pod 真的與外接脫鉤的時候,才允許 Pod 的主程序進入結束的程序(收到 SIGTERM
信號),這樣就可以避免服務中斷的問題。