由雲原生平台管理的容器化應用,無法掌控自己的生命週期。要成為稱職的雲原生公民,它們必須監聽管理平台發出的事件,並據此調整自身的生命週期。「受管生命週期」模式描述應用能夠、也應該如何回應這些生命週期事件。
問題#
「健康探針」一章說明了容器為何必須提供各種健康檢查的 API。那些健康檢查 API 是唯讀端點,平台持續探測它們以取得應用的洞察——那是平台從應用抽取資訊的機制。
但除了監控容器狀態之外,平台有時會發出命令並期待應用做出反應。受政策與外部因素驅動,雲原生平台可能在任何時刻決定啟動或停止它所管理的應用。至於哪些事件值得回應、以及該如何回應,則由容器化應用自己決定。
說到底,這是一組平台用來與應用溝通、下達命令的 API。應用可以選擇從生命週期管理中獲益,也可以在不需要這項服務時直接忽略它。
解法#
我們看過:光檢查行程狀態不足以說明應用是否健康,所以才需要各種監控容器健康的 API。同理,光用行程模型來啟動與停止行程也不夠好——真實世界的應用需要更細緻的互動與生命週期管理能力:有些應用需要協助暖機,有些需要溫和而乾淨的關閉程序。

圖 5-1:受管的容器生命週期
應用的部署單元是 Pod,而 Pod 由一個或多個容器組成。在 Pod 層級還有 init container(見「初始化容器」)等建構,以及撰寫本書時仍在提案階段的 defer-container,可協助管理容器生命週期。但本章描述的事件與掛鉤,全都套用在個別容器層級,而非 Pod 層級。
SIGTERM 訊號#
每當 Kubernetes 決定關閉一個容器——無論是因為它所屬的 Pod 正在關閉,或只是存活探針失敗導致容器要被重啟——該容器都會收到 SIGTERM 訊號。
SIGTERM 是在 Kubernetes 送出更粗暴的 SIGKILL 之前,溫和地戳一下容器,請它乾淨地關閉。收到 SIGTERM 之後,應用應盡快關閉:對某些應用來說可能是瞬間終止;另一些應用則可能需要完成處理中的請求、釋放開啟的連線、清理暫存檔,因而稍微花點時間。
無論哪種情況,回應 SIGTERM 都是乾淨關閉容器的正確時機。
SIGKILL 訊號#
若容器行程在收到 SIGTERM 後仍未關閉,接下來的 SIGKILL 會強制關閉它。Kubernetes 不會立刻送出 SIGKILL,而是在發出 SIGTERM 後預設等待 30 秒的寬限期。
這個寬限期可用 .spec.terminationGracePeriodSeconds 逐 Pod 定義,但不保證一定被遵守,因為對 Kubernetes 下指令時可以覆寫它。
目標應該放在:把容器化應用設計、實作成**短暫(ephemeral)**的,具備快速的啟動與關閉流程。
postStart 掛鉤#
只靠行程訊號管理生命週期有其侷限,因此 Kubernetes 另外提供了 postStart 與 preStop 這兩個生命週期掛鉤。
apiVersion: v1
kind: Pod
metadata:
name: post-start-hook
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
lifecycle:
postStart:
exec:
command:
- sh
- -c
# 這裡等 30 秒。sleep 只是在模擬任何可能耗時的啟動程式碼;
# 同時用一個觸發檔案,與平行啟動的主應用同步。
- sleep 30 && echo "Wake up!" > /tmp/postStart_donepostStart 指令在容器建立之後執行,且與主容器行程非同步進行。雖然許多應用初始化與暖機邏輯都可以寫在容器啟動步驟裡,postStart 仍涵蓋了一些使用場景:
- 延後容器的啟動狀態。
postStart動作是阻斷式呼叫:在處理器完成之前,容器狀態維持Waiting,Pod 狀態也因而維持Pending。可以利用這個特性拖延容器的啟動狀態,好讓主容器行程有時間初始化。 - 在 Pod 未滿足前置條件時阻止容器啟動。 例如當
postStart掛鉤回傳非零結束碼表示錯誤時,主容器行程會被 Kubernetes 殺掉。
postStart 與 preStop 的呼叫機制類似健康探針,支援兩種處理器型別:
exec:直接在容器內執行指令。httpGet:對某個 Pod 容器開啟的埠發出 HTTP GET 請求。
在
postStart掛鉤中執行關鍵邏輯務必非常謹慎,因為它的執行沒有任何保證:
- 掛鉤與容器行程平行執行,因此有可能在容器啟動之前就被執行。
- 掛鉤採「至少一次」(at-least-once)語意,實作必須自行處理重複執行。
- 對於未抵達處理器的失敗 HTTP 請求,平台不會重試。
preStop 掛鉤#
preStop 是容器被終止前送出的阻斷式呼叫,語意與 SIGTERM 相同。當「回應 SIGTERM」不可行時,就該用它來啟動容器的優雅關閉。preStop 動作必須先完成,刪除容器的呼叫才會送到容器執行期、進而觸發 SIGTERM 通知。
apiVersion: v1
kind: Pod
metadata:
name: pre-stop-hook
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
lifecycle:
preStop:
httpGet: # 呼叫應用內部的 /shutdown 端點
port: 8080
path: /shutdown其他生命週期控制手段#
本章目前聚焦於「容器生命週期事件發生時執行指令」的掛鉤。但還有另一個機制運作在 Pod 層級而非容器層級,用來執行初始化指令:init container。
與一般應用容器不同,init container 依序執行、執行到完成為止,且在 Pod 內任何應用容器啟動之前執行完畢。這些保證讓它適合承擔 Pod 層級的初始化工作。
| 面向 | 生命週期掛鉤 | Init Container |
|---|---|---|
| 觸發於 | 容器生命週期階段 | Pod 生命週期階段 |
| 啟動階段動作 | 一道 postStart 指令 | 一串待執行的 initContainers |
| 關閉階段動作 | 一道 preStop 指令 | 目前尚無對等功能 |
| 時序保證 | postStart 指令與容器的 ENTRYPOINT 同時執行 | 所有 init container 必須全部成功完成,應用容器才能啟動 |
| 使用場景 | 執行容器特定、非關鍵的啟動/關閉清理 | 用容器執行工作流式的循序操作;重用容器來執行任務 |
除非你需要特定的時序保證,否則選用哪個機制並沒有嚴格規則。以下是三種取徑,依序需要更多投入,但也提供更強的保證與更好的重用性:
- 完全跳過生命週期掛鉤與 init container,改用 bash 腳本在容器啟動/關閉指令中執行特定動作。可行,但會讓容器與腳本緊密耦合,變成維護惡夢。
- 使用 Kubernetes 生命週期掛鉤執行動作。
- 更進一步,用 init container 執行個別動作。
理解容器與 Pod 生命週期的各個階段與可用掛鉤,是打造「能真正受益於 Kubernetes 管理」的應用的關鍵。
討論#
雲原生平台的主要好處之一,是能在可能不可靠的雲端基礎設施之上,可靠且可預期地執行與擴縮應用。這些平台為其上執行的應用提供了一組約束與契約,而應用遵守這些契約才符合自身利益——這樣才能享受平台提供的所有能力。
處理並回應這些事件,能確保你的應用優雅地啟動與關閉,把對下游服務的衝擊降到最低。就目前最基本的形式而言,這意味著容器應該表現得像任何設計良好的 POSIX 行程。
未來可能會有更多事件,提示應用「即將被擴大」或「請釋放資源以免被關閉」。關鍵是要建立這樣的心態:應用的生命週期不再由人掌控,而是由平台完全自動化。
除了管理應用生命週期,Kubernetes 這類編排平台的另一項重責大任,是把容器分派到一整批節點上。下一個模式「自動化配置」會說明如何從外部影響排程決策。
更多資訊#
- Managed Lifecycle Example
- Container Lifecycle Hooks
- Attaching Handlers to Container Lifecycle Events
- Terminating with Grace
- Graceful Shutdown of Pods with Kubernetes
- Defer Containers