title: 在被驅逐中倖存:如何在 GKE 上建構可抵禦中斷的 AI 工作負載
published: true
description: 學習如何在 Google Kubernetes Engine(GKE)上建構可抵禦中斷的 AI 工作負載策略。
tags: kubernetes, ai, gke, googlecloud
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ht3q28btunq9lofna1k1.png
你已經把所有事情都做對了。你已將龐大的模型訓練工作容器化,部署到 Google Kubernetes Engine(GKE),還巧妙地把它導向 Spot VM 節點池,節省高達 90% 的運算成本。
一切順利運作了 38 小時。接著,一位優先順序更高的隨需客戶需要容量,Google Cloud 回收了你底層的 Spot VM,而你的節點就此消失。
無論你是使用可搶占的 Spot VM 來省錢,還是利用 動態工作負載排程器(Dynamic Workload Scheduler, DWS) 來排隊取得稀缺的 GPU,你都是建立在短暫存在的運算資源之上。這些硬體終究會被拿走。若要在未承諾容量上成功執行關鍵 AI 工作負載,你的應用程式架構必須假設失敗是必然會發生的。
以下是一份在 GKE 上建構可被中斷工作負載的實用指南。
當 Google Cloud 回收 Spot VM 時,並不是直接立刻拔掉電源線。它會向底層節點發送一個 ACPI 訊號,開始關機流程。Kubernetes 會攔截這個訊號,並將其轉換為直接傳送給你執行中容器的 SIGTERM 訊號。
在 SIGTERM 與最終致命的 SIGKILL 之間,你有一段寬限期(非系統 Pod 最長可達 15 秒)。
你的應用程式必須明確監聽這個訊號。當接收到時,程式應立即停止接受新的批次,完成目前的迴圈,將所有記憶體中的資料寫入磁碟,並以 0(成功)的狀態碼結束。
以下是一個用 Python 接收此訊號的簡單範例:
import signal
import sys
import time
def handle_sigterm(signum, frame):
print("Received SIGTERM. Initiating graceful shutdown...")
# 1. 停止處理新資料
# 2. 將記憶體資料寫入持久化儲存空間
# 3. 儲存最後的檢查點
print("State saved. Exiting cleanly.")
sys.exit(0)
# 註冊訊號處理器
signal.signal(signal.SIGTERM, handle_sigterm)
# 你的主要訓練迴圈
print("Starting training loop...")
while True:
# 訓練模型...
time.sleep(1)
如果你的容器死亡,裡面的本機檔案系統也會一併消失。為了在中斷後仍能存活,你必須定期將進度(模型權重、最佳化器狀態、epoch 計數器等)儲存到外部儲存位置。
Cloud Storage(GCS) 是 Google Cloud 上常見的解決方案。
「冪等性」聽起來很專業,但意思就是:做同一件事兩次,結果會和做一次一樣。
想像一個批次推論工作,它會讀取一張圖片、進行處理,然後將結果寫入資料庫。如果你的 Pod 在寫入資料庫之後的幾毫秒被搶占,但在能將任務標記為完成之前就中斷了,那麼重新排程後的 Pod 很可能會再次處理那張圖片。
如果你的資料庫只是單純地插入新列,那你現在就會得到非預期的重複資料。
若要建立冪等的管線:
如果你要在數千個檔案上執行大型批次處理或推論工作,不要寫一支單體式 Python 腳本去迭代一份靜態的 CSV 清單。如果節點在第 5,000 列時掛掉,要管理從哪裡重新開始的狀態會非常痛苦。
相反地,請將工作負載解耦:
如果 Spot 節點在推論途中被搶占,worker 就會在送出 ACK 之前死亡。經過短暫逾時後,Pub/Sub 會自動讓那則特定訊息再次可用。另一個仍存活的 worker Pod 會無縫接手。沒有資料遺失,也不需要人工介入。
在 Spot VM 這類短暫存在的運算資源上執行,不只是基礎架構選擇,更是一種設計選擇。透過處理終止訊號、積極將檢查點儲存到 GCS、確保操作具備冪等性,以及將佇列解耦,你就能在不犧牲可靠性的前提下,釋放巨大的成本節省,並善用稀缺的 GPU 資源池。