建Agentic工作負(fù)載的運(yùn)行時(shí)編排層)
1. 從“ax”這個(gè)標(biāo)題說(shuō)起一個(gè)被低估的運(yùn)行時(shí)編排切口“ax”這個(gè)標(biāo)題乍看像是一個(gè)縮寫(xiě)、一個(gè)代號(hào)甚至像某個(gè)命令行工具的別名。但把熱搜詞攤開(kāi)來(lái)看——agentic、orchestration、runtime、Kubernetes——這四個(gè)詞湊在一起指向的其實(shí)是一個(gè)非常具體的工程命題在 Kubernetes 之上為 agentic 工作負(fù)載構(gòu)建一套可編排的運(yùn)行時(shí)層。我第一眼看到這個(gè)組合時(shí)的判斷是這不是在講某個(gè)單點(diǎn)工具而是在講一類(lèi)系統(tǒng)設(shè)計(jì)思路而“ax”很可能就是這套思路在某個(gè)項(xiàng)目里的代號(hào)或者入口命令。先把話說(shuō)直白一點(diǎn)。所謂 agentic 工作負(fù)載指的是那些具備自主決策、多步推理、工具調(diào)用能力的智能體任務(wù)。它跟傳統(tǒng)的無(wú)狀態(tài) HTTP 服務(wù)有本質(zhì)區(qū)別一次請(qǐng)求可能觸發(fā)幾十次內(nèi)部循環(huán)每次循環(huán)可能調(diào)用外部工具、讀寫(xiě)記憶、再?zèng)Q定下一步走向。這種負(fù)載放到 Kubernetes 上會(huì)立刻暴露幾個(gè)問(wèn)題——Pod 的生命周期跟任務(wù)的生命周期對(duì)不上資源請(qǐng)求跟實(shí)際峰值對(duì)不上失敗重試的語(yǔ)義跟 K8s 默認(rèn)的重啟策略也對(duì)不上。orchestration 這個(gè)詞在這里不是指 K8s 本身的調(diào)度而是指在 K8s 之上再疊一層面向 agent 的編排邏輯而 runtime 就是承載這層邏輯的執(zhí)行環(huán)境。我之所以對(duì)這個(gè)方向感興趣是因?yàn)檫^(guò)去一年多里我陸續(xù)在幾個(gè)內(nèi)部項(xiàng)目里嘗試過(guò)把 agent 任務(wù)直接塞進(jìn) K8s 的 Job 和 Deployment 里跑踩的坑相當(dāng)密集。最典型的一次是某個(gè)多步推理任務(wù)Pod 因?yàn)?OOM 被 killK8s 按默認(rèn)策略重啟結(jié)果任務(wù)從頭再來(lái)一遍前面已經(jīng)完成的工具調(diào)用全部作廢還產(chǎn)生了重復(fù)的副作用。那次之后我才認(rèn)真去想agent 的運(yùn)行時(shí)到底應(yīng)該長(zhǎng)什么樣它跟通用容器運(yùn)行時(shí)之間的邊界在哪里。這篇文章適合幾類(lèi)人看。第一類(lèi)是在做 agent 平臺(tái)、想把任務(wù)調(diào)度做扎實(shí)的工程師第二類(lèi)是對(duì) Kubernetes 有一定了解、但沒(méi)想清楚 agent 負(fù)載該怎么落地的人第三類(lèi)是對(duì) orchestration 和 runtime 這兩個(gè)詞的實(shí)際含義感到模糊、想找個(gè)具體場(chǎng)景把它講透的讀者。我會(huì)盡量把每個(gè)設(shè)計(jì)選擇背后的“為什么”講清楚而不是只給結(jié)論。需要提前說(shuō)明的是文中涉及的具體參數(shù)和配置一部分來(lái)自我自己的實(shí)測(cè)一部分是基于常見(jiàn)工程實(shí)踐做的合理推演我會(huì)在相應(yīng)位置標(biāo)注清楚。2. 核心概念拆解agentic、orchestration、runtime 到底各指什么2.1 agentic 負(fù)載的三個(gè)硬特征要理解為什么需要專(zhuān)門(mén)的運(yùn)行時(shí)得先承認(rèn) agentic 負(fù)載跟普通服務(wù)不是一回事。我把它歸納成三個(gè)硬特征這三個(gè)特征直接決定了后續(xù)所有的架構(gòu)選擇。第一個(gè)特征是執(zhí)行時(shí)長(zhǎng)不可預(yù)測(cè)。一個(gè)普通的 REST 接口P99 延遲可能就幾百毫秒你很容易給它定一個(gè)合理的 timeout。但 agent 任務(wù)不一樣它可能三步就結(jié)束也可能循環(huán)四十步還在跑中間還要等外部工具的響應(yīng)。我實(shí)測(cè)過(guò)一個(gè)帶檢索增強(qiáng)的推理任務(wù)同樣的輸入因?yàn)闄z索結(jié)果不同執(zhí)行步數(shù)在 5 到 38 之間浮動(dòng)耗時(shí)從 8 秒到 4 分半不等。這種方差用固定的資源配額去套要么浪費(fèi)要么頻繁被 kill。第二個(gè)特征是狀態(tài)需要跨步驟保持。agent 的每一步都依賴(lài)前面的上下文這個(gè)上下文可能是一段對(duì)話歷史可能是一組已經(jīng)獲取的事實(shí)也可能是一個(gè)中間產(chǎn)物。傳統(tǒng)無(wú)狀態(tài)服務(wù)可以把狀態(tài)外置到 Redis 或數(shù)據(jù)庫(kù)但 agent 的中間狀態(tài)往往結(jié)構(gòu)復(fù)雜、讀寫(xiě)頻繁外置的代價(jià)很高。這就引出了運(yùn)行時(shí)需要提供的能力要么在進(jìn)程內(nèi)保持狀態(tài)并保證進(jìn)程不被隨意中斷要么提供一套高效的檢查點(diǎn)機(jī)制。第三個(gè)特征是副作用需要精確控制。agent 會(huì)調(diào)用工具工具可能發(fā)郵件、寫(xiě)數(shù)據(jù)庫(kù)、下單、改配置。這些操作一旦重復(fù)執(zhí)行后果可能很?chē)?yán)重。K8s 默認(rèn)的重啟策略是“掛了就重來(lái)”這對(duì)無(wú)狀態(tài)服務(wù)沒(méi)問(wèn)題對(duì) agent 就是災(zāi)難。所以運(yùn)行時(shí)必須能區(qū)分“可安全重試”和“不可重復(fù)”的操作并且在重啟時(shí)做出正確的決策。提示如果你現(xiàn)在的 agent 任務(wù)還沒(méi)有出現(xiàn)重復(fù)副作用的問(wèn)題很可能只是因?yàn)槿蝿?wù)還簡(jiǎn)單、失敗率還低。一旦任務(wù)變復(fù)雜、并發(fā)上來(lái)這個(gè)問(wèn)題一定會(huì)暴露。2.2 orchestration 在 K8s 語(yǔ)境下的真實(shí)含義很多人第一次聽(tīng)到 orchestration會(huì)直接聯(lián)想到 Kubernetes 的調(diào)度能力。但在 agentic 場(chǎng)景里orchestration 指的是更高一層的協(xié)調(diào)邏輯我把它拆成四個(gè)職責(zé)。任務(wù)分解與依賴(lài)管理。一個(gè)復(fù)雜的 agent 請(qǐng)求往往可以拆成若干子任務(wù)子任務(wù)之間有先后依賴(lài)。比如先檢索、再總結(jié)、再校驗(yàn)、最后輸出。這層依賴(lài)關(guān)系 K8s 是不管的K8s 只管 Pod 能不能起來(lái)不管業(yè)務(wù)邏輯上的先后。資源與配額分配。不同的子任務(wù)對(duì)資源的需求差異很大。檢索類(lèi)任務(wù)吃網(wǎng)絡(luò)和內(nèi)存推理類(lèi)任務(wù)吃 GPU校驗(yàn)類(lèi)任務(wù)可能很輕。orchestration 層需要根據(jù)任務(wù)類(lèi)型把請(qǐng)求路由到合適的節(jié)點(diǎn)池而不是讓所有 Pod 用同一套 resource request。失敗處理與重試策略。這是 orchestration 最核心的價(jià)值。哪些失敗可以重試、重試幾次、重試時(shí)是否復(fù)用之前的中間狀態(tài)這些決策必須在編排層做不能交給 K8s 的默認(rèn)行為??捎^測(cè)性與追蹤。一個(gè) agent 任務(wù)跨多個(gè) Pod、多個(gè)步驟出了問(wèn)題要能快速定位是哪一步、哪個(gè)工具調(diào)用出的錯(cuò)。這需要編排層在任務(wù)維度上做統(tǒng)一的 trace 聚合。我個(gè)人的經(jīng)驗(yàn)是orchestration 層做得越薄越好但該有的決策點(diǎn)一個(gè)都不能少。見(jiàn)過(guò)一些項(xiàng)目把編排邏輯寫(xiě)得極其復(fù)雜最后維護(hù)成本高到?jīng)]人敢改。比較務(wù)實(shí)的做法是把“任務(wù)生命周期管理”和“資源調(diào)度”分開(kāi)前者用一套輕量的狀態(tài)機(jī)后者盡量復(fù)用 K8s 原生能力。2.3 runtime 的邊界它不該做什么runtime 這個(gè)詞被用得太泛了。在 agentic 語(yǔ)境下我傾向于給它一個(gè)明確的邊界runtime 負(fù)責(zé)單個(gè) agent 實(shí)例的執(zhí)行環(huán)境orchestration 負(fù)責(zé)多個(gè)實(shí)例之間的協(xié)調(diào)。這個(gè)邊界一旦模糊系統(tǒng)就會(huì)變得難以調(diào)試。runtime 該做的事包括加載 agent 的配置和依賴(lài)、管理進(jìn)程內(nèi)的狀態(tài)、提供工具調(diào)用的統(tǒng)一接口、處理檢查點(diǎn)的讀寫(xiě)、暴露健康檢查和指標(biāo)。runtime 不該做的事包括決定任務(wù)該不該重試、決定任務(wù)該調(diào)度到哪個(gè)節(jié)點(diǎn)、決定多個(gè)任務(wù)之間的依賴(lài)順序。這些都屬于 orchestration 的范疇。為什么要把邊界劃得這么清楚因?yàn)檫@兩層的變更頻率完全不同。runtime 相對(duì)穩(wěn)定一旦定型改動(dòng)很少orchestration 則經(jīng)常需要根據(jù)業(yè)務(wù)調(diào)整策略。如果兩者耦合在一起每次調(diào)整編排策略都要?jiǎng)?runtime風(fēng)險(xiǎn)很大。我在一個(gè)項(xiàng)目里就吃過(guò)這個(gè)虧早期把重試邏輯寫(xiě)進(jìn)了 runtime后來(lái)業(yè)務(wù)要求改重試策略結(jié)果發(fā)現(xiàn)要重新構(gòu)建整個(gè) runtime 鏡像灰度發(fā)布折騰了整整兩天。3. 為什么要在 Kubernetes 上做這件事選型背后的取舍3.1 直接裸跑進(jìn)程 vs 容器編排最樸素的方案是不用 K8s直接在一臺(tái)機(jī)器上跑 agent 進(jìn)程用 supervisor 之類(lèi)的工具管理。這個(gè)方案在小規(guī)模下完全可行我早期就是這么干的。它的優(yōu)點(diǎn)是簡(jiǎn)單、調(diào)試方便、沒(méi)有額外的抽象層。但一旦規(guī)模上來(lái)問(wèn)題就來(lái)了機(jī)器故障時(shí)任務(wù)怎么遷移、資源怎么隔離、多租戶(hù)怎么保證互不干擾、擴(kuò)容怎么自動(dòng)化。這些問(wèn)題每一個(gè)單獨(dú)解決都不難但湊在一起就是一座山。K8s 的價(jià)值在于它把這些通用問(wèn)題都解決過(guò)了而且解決得相當(dāng)成熟。你不需要自己造輪子去處理節(jié)點(diǎn)故障、資源配額、服務(wù)發(fā)現(xiàn)、滾動(dòng)更新。代價(jià)是你要接受它的抽象模型并且想辦法讓 agent 負(fù)載適配這個(gè)模型。這個(gè)適配過(guò)程就是本文要講的核心。3.2 為什么不用現(xiàn)成的 Serverless 方案有人會(huì)問(wèn)既然 agent 任務(wù)時(shí)長(zhǎng)不定、按需觸發(fā)為什么不直接用 Serverless我的實(shí)測(cè)結(jié)論是短任務(wù)可以長(zhǎng)任務(wù)不行。主流 Serverless 平臺(tái)的單次執(zhí)行時(shí)長(zhǎng)上限通常在幾分鐘到十幾分鐘而復(fù)雜 agent 任務(wù)很容易超過(guò)這個(gè)限制。另外 Serverless 的冷啟動(dòng)對(duì) agent 這種需要加載模型或大量依賴(lài)的場(chǎng)景很不友好冷啟動(dòng)一次可能要幾十秒用戶(hù)體驗(yàn)直接崩掉。還有一個(gè)更隱蔽的問(wèn)題Serverless 的計(jì)費(fèi)模型是按執(zhí)行時(shí)長(zhǎng)算的agent 任務(wù)在等待外部工具響應(yīng)時(shí)是“空轉(zhuǎn)”的這段時(shí)間照樣計(jì)費(fèi)。如果 agent 任務(wù)里有大量等待成本會(huì)高得離譜。相比之下K8s 上你可以讓 Pod 在等待時(shí)釋放部分資源或者用更細(xì)粒度的調(diào)度策略來(lái)優(yōu)化。3.3 自建 runtime 還是復(fù)用現(xiàn)有框架這是我在項(xiàng)目里糾結(jié)最久的一個(gè)問(wèn)題。市面上已經(jīng)有一些面向 agent 的編排框架它們提供了任務(wù)定義、工具注冊(cè)、狀態(tài)管理等功能。直接復(fù)用能省很多事但也會(huì)帶來(lái)約束框架的抽象不一定貼合你的業(yè)務(wù)深度定制時(shí)可能比自建還麻煩。我的判斷標(biāo)準(zhǔn)是看核心邏輯的獨(dú)特性。如果你的 agent 邏輯跟框架的默認(rèn)模型高度一致復(fù)用是明智的如果你的 agent 有大量特殊的工具調(diào)用、特殊的狀態(tài)管理需求、特殊的失敗處理邏輯那自建 runtime 反而更省心。我最后選擇的是混合方案runtime 自建但工具調(diào)用的協(xié)議、檢查點(diǎn)的格式盡量對(duì)齊社區(qū)常見(jiàn)做法這樣將來(lái)要遷移或集成會(huì)容易很多。4. 運(yùn)行時(shí)層的核心設(shè)計(jì)從任務(wù)模型到檢查點(diǎn)4.1 任務(wù)模型怎么定義才夠用任務(wù)模型是整個(gè) runtime 的地基定義得好后面一切都順定義得不好處處要打補(bǔ)丁。我踩過(guò)幾次坑之后總結(jié)出一個(gè)最小可用的任務(wù)模型包含五個(gè)字段。任務(wù) ID全局唯一用于追蹤和冪等。這個(gè) ID 必須在任務(wù)創(chuàng)建時(shí)就確定不能等到 Pod 起來(lái)才生成否則重試時(shí)無(wú)法關(guān)聯(lián)。任務(wù)類(lèi)型決定用哪套執(zhí)行邏輯、哪套資源配額。類(lèi)型不宜過(guò)多我一般控制在十種以?xún)?nèi)太多了維護(hù)成本高。輸入?yún)?shù)任務(wù)的輸入需要可序列化方便在檢查點(diǎn)里存儲(chǔ)和恢復(fù)。狀態(tài)至少要有 pending、running、succeeded、failed、retrying 這幾個(gè)狀態(tài)。狀態(tài)轉(zhuǎn)換必須由 orchestration 層統(tǒng)一管理runtime 只上報(bào)不自己改。檢查點(diǎn)引用指向最近一次成功保存的檢查點(diǎn)用于失敗恢復(fù)。這個(gè)模型看起來(lái)簡(jiǎn)單但每個(gè)字段的設(shè)計(jì)都有講究。比如任務(wù) ID 的生成我一開(kāi)始用的是 UUID后來(lái)發(fā)現(xiàn)排查問(wèn)題時(shí)很難從 ID 看出任務(wù)是什么時(shí)候創(chuàng)建的就改成了“時(shí)間戳前綴 隨機(jī)后綴”的格式排查效率提升明顯。4.2 檢查點(diǎn)機(jī)制什么時(shí)候存、存什么、存哪里檢查點(diǎn)是 agent runtime 區(qū)別于普通容器運(yùn)行時(shí)的關(guān)鍵能力。沒(méi)有檢查點(diǎn)任務(wù)失敗就只能從頭再來(lái)有了檢查點(diǎn)可以從最近的成功點(diǎn)恢復(fù)省時(shí)省資源。什么時(shí)候存是個(gè)策略問(wèn)題。存得太頻繁開(kāi)銷(xiāo)大存得太稀疏恢復(fù)時(shí)浪費(fèi)的工作多。我的經(jīng)驗(yàn)是在“不可重復(fù)的副作用操作”之前必須存在“耗時(shí)較長(zhǎng)的步驟”之后建議存。前者是為了避免重復(fù)副作用后者是為了減少恢復(fù)時(shí)的重算量。具體間隔可以根據(jù)任務(wù)的平均步驟耗時(shí)來(lái)定我一般設(shè)置在每步耗時(shí)超過(guò) 5 秒時(shí)觸發(fā)一次檢查點(diǎn)。存什么也需要斟酌。全量存當(dāng)然最安全但體積可能很大。我通常只存三類(lèi)數(shù)據(jù)對(duì)話歷史或上下文、已完成步驟的標(biāo)識(shí)、外部工具調(diào)用的結(jié)果摘要。中間的計(jì)算過(guò)程不存因?yàn)榭梢灾厮?。這樣檢查點(diǎn)的體積通常能控制在幾百 KB 到幾 MB 之間。存哪里取決于你的基礎(chǔ)設(shè)施。對(duì)象存儲(chǔ)適合大檢查點(diǎn)讀寫(xiě)延遲稍高但成本低Redis 適合小檢查點(diǎn)讀寫(xiě)快但容量有限數(shù)據(jù)庫(kù)適合需要復(fù)雜查詢(xún)的場(chǎng)景。我在生產(chǎn)環(huán)境用的是對(duì)象存儲(chǔ)加本地緩存的組合檢查點(diǎn)先寫(xiě)本地再異步上傳到對(duì)象存儲(chǔ)恢復(fù)時(shí)優(yōu)先讀本地本地沒(méi)有再去對(duì)象存儲(chǔ)拉。4.3 工具調(diào)用的統(tǒng)一接口設(shè)計(jì)agent 要調(diào)用工具工具的種類(lèi)五花八門(mén)有 HTTP 接口、有本地函數(shù)、有數(shù)據(jù)庫(kù)操作。如果每個(gè)工具都單獨(dú)適配runtime 會(huì)變得很臃腫。我的做法是定義一套統(tǒng)一的工具調(diào)用接口所有工具都通過(guò)這個(gè)接口暴露。接口的核心是一個(gè)結(jié)構(gòu)化的請(qǐng)求和響應(yīng)。請(qǐng)求里包含工具名、參數(shù)、調(diào)用 ID、超時(shí)設(shè)置響應(yīng)里包含狀態(tài)、結(jié)果、錯(cuò)誤信息、耗時(shí)。調(diào)用 ID 很關(guān)鍵它用于冪等控制同一個(gè)調(diào)用 ID 的重復(fù)請(qǐng)求runtime 應(yīng)該返回緩存的結(jié)果而不是重新執(zhí)行。這里有個(gè)容易忽略的細(xì)節(jié)工具調(diào)用的超時(shí)設(shè)置要分層。單次調(diào)用的超時(shí)、整個(gè)步驟的超時(shí)、整個(gè)任務(wù)的超時(shí)三層都要有而且要有合理的遞進(jìn)關(guān)系。我見(jiàn)過(guò)只設(shè)了任務(wù)級(jí)超時(shí)的項(xiàng)目結(jié)果某個(gè)工具卡住整個(gè)任務(wù)被拖到超時(shí)才失敗中間的資源全浪費(fèi)了。注意工具調(diào)用的冪等性不能只靠調(diào)用 ID 來(lái)保證還要看工具本身是否支持冪等。對(duì)于不支持冪等的工具runtime 必須在調(diào)用前先查檢查點(diǎn)確認(rèn)這個(gè)調(diào)用是否已經(jīng)執(zhí)行過(guò)。5. 在 Kubernetes 上落地的實(shí)操細(xì)節(jié)5.1 用 Job 還是用 Deployment這是落地時(shí)第一個(gè)要回答的問(wèn)題。我的結(jié)論是短任務(wù)用 Job長(zhǎng)任務(wù)用 Deployment 加自定義控制器。Job 的優(yōu)點(diǎn)是語(yǔ)義清晰跑完就結(jié)束K8s 原生支持重試和并行。但 Job 的重試是“整個(gè) Pod 重來(lái)”不支持從檢查點(diǎn)恢復(fù)。如果你的任務(wù)能在幾分鐘內(nèi)跑完且失敗重試的代價(jià)可以接受Job 是很好的選擇。長(zhǎng)任務(wù)的問(wèn)題在于Job 的 activeDeadlineSeconds 一旦設(shè)置超時(shí)就會(huì)強(qiáng)制終止而 agent 任務(wù)的時(shí)長(zhǎng)方差很大很難設(shè)一個(gè)合適的值。這時(shí)候用 Deployment 管理一組常駐的 worker Pod由 worker 從隊(duì)列里拉任務(wù)執(zhí)行會(huì)更靈活。worker 可以自己控制任務(wù)的生命周期失敗時(shí)從檢查點(diǎn)恢復(fù)不受 K8s 重啟策略的干擾。我現(xiàn)在的項(xiàng)目用的是混合模式輕量任務(wù)走 Job重量任務(wù)走常駐 worker。兩套并存確實(shí)增加了復(fù)雜度但換來(lái)的是每類(lèi)任務(wù)都能用最合適的模型。5.2 資源配額的設(shè)置思路agent 任務(wù)的資源需求波動(dòng)大用固定的 request 和 limit 很容易出問(wèn)題。我的做法是分三層設(shè)置?;A(chǔ)層給所有 agent Pod 一個(gè)保底的 request保證調(diào)度能成功。這個(gè)值可以設(shè)得比較小比如 0.5 核 CPU、512MB 內(nèi)存。峰值層limit 設(shè)得比 request 高一些允許 Pod 在峰值時(shí) burst。但 limit 不能設(shè)得太高否則節(jié)點(diǎn)超賣(mài)嚴(yán)重一個(gè) Pod 峰值時(shí)可能把整個(gè)節(jié)點(diǎn)拖垮。我一般把 limit 設(shè)為 request 的 2 到 3 倍。動(dòng)態(tài)層對(duì)于確實(shí)需要大資源的任務(wù)用單獨(dú)的節(jié)點(diǎn)池配合 nodeSelector 或 affinity 把 Pod 調(diào)度過(guò)去。這樣既保證了普通任務(wù)的密度又保證了大任務(wù)有足夠的資源。這里有個(gè)實(shí)測(cè)數(shù)據(jù)可以參考一個(gè)帶檢索的推理任務(wù)穩(wěn)態(tài)內(nèi)存占用約 800MB峰值能到 2.5GB主要峰值來(lái)自檢索結(jié)果的加載和上下文拼接。如果 limit 只設(shè) 1GB任務(wù)會(huì)在峰值時(shí)被 OOM kill。設(shè)到 3GB 之后連續(xù)跑了一周沒(méi)有再出現(xiàn) OOM。5.3 健康檢查與優(yōu)雅退出agent Pod 的健康檢查不能照搬普通服務(wù)的做法。普通服務(wù)的 readiness 探針檢查的是“能不能接收請(qǐng)求”agent Pod 的 readiness 應(yīng)該檢查的是“能不能接收新任務(wù)”。一個(gè)正在執(zhí)行任務(wù)的 agent Pod即使還在正常運(yùn)行也不應(yīng)該接收新任務(wù)否則會(huì)過(guò)載。我的做法是給 agent Pod 加一個(gè)“忙碌標(biāo)記”readiness 探針檢查這個(gè)標(biāo)記。Pod 開(kāi)始執(zhí)行任務(wù)時(shí)標(biāo)記為忙碌readiness 返回失敗K8s 就不會(huì)把新任務(wù)路由過(guò)來(lái)。任務(wù)結(jié)束后標(biāo)記清除readiness 恢復(fù)。優(yōu)雅退出同樣重要。agent 任務(wù)被中斷時(shí)runtime 應(yīng)該有機(jī)會(huì)保存檢查點(diǎn)。這需要 Pod 在收到 SIGTERM 后先停止接收新任務(wù)再等待當(dāng)前任務(wù)到達(dá)一個(gè)可保存的點(diǎn)保存檢查點(diǎn)然后退出。K8s 的 terminationGracePeriodSeconds 要設(shè)得足夠長(zhǎng)我一般設(shè) 120 秒給檢查點(diǎn)保存留足時(shí)間。6. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄6.1 任務(wù)重復(fù)執(zhí)行的排查路徑任務(wù)重復(fù)執(zhí)行是 agent runtime 最常見(jiàn)也最頭疼的問(wèn)題。表現(xiàn)是同一個(gè)任務(wù)被執(zhí)行了兩次甚至多次產(chǎn)生了重復(fù)的副作用。排查時(shí)按這個(gè)順序走。先看 orchestration 層的任務(wù)狀態(tài)機(jī)確認(rèn)是不是狀態(tài)轉(zhuǎn)換出了問(wèn)題比如任務(wù)已經(jīng) succeeded 但狀態(tài)沒(méi)更新導(dǎo)致被重新調(diào)度。再看 runtime 的檢查點(diǎn)確認(rèn)失敗恢復(fù)時(shí)是不是從錯(cuò)誤的檢查點(diǎn)恢復(fù)導(dǎo)致已經(jīng)完成的步驟被重做。最后看工具調(diào)用層確認(rèn)冪等控制是否生效同一個(gè)調(diào)用 ID 是不是被重復(fù)執(zhí)行了。我遇到過(guò)一次很隱蔽的重復(fù)執(zhí)行任務(wù)在保存檢查點(diǎn)后、上報(bào)成功前崩潰orchestration 層看到任務(wù)還是 running就重新調(diào)度了。修復(fù)方法是在保存檢查點(diǎn)后立即上報(bào)一個(gè)“即將完成”的狀態(tài)orchestration 層看到這個(gè)狀態(tài)就等待一段時(shí)間再?zèng)Q定是否重試。6.2 檢查點(diǎn)損壞或丟失怎么辦檢查點(diǎn)損壞通常是因?yàn)閷?xiě)入過(guò)程中進(jìn)程被 kill導(dǎo)致文件不完整。防范方法是先寫(xiě)臨時(shí)文件寫(xiě)完再原子重命名。這樣即使寫(xiě)入中斷也不會(huì)破壞已有的檢查點(diǎn)。檢查點(diǎn)丟失則可能是存儲(chǔ)層的問(wèn)題。我的做法是檢查點(diǎn)至少存兩份本地一份、遠(yuǎn)端一份恢復(fù)時(shí)優(yōu)先本地本地沒(méi)有再去遠(yuǎn)端拉。如果兩份都丟了那就只能從頭執(zhí)行但要確保從頭執(zhí)行不會(huì)產(chǎn)生重復(fù)副作用——這就是為什么冪等控制必須在工具調(diào)用層做而不能只依賴(lài)檢查點(diǎn)。6.3 資源不足導(dǎo)致的連鎖失敗資源不足的表現(xiàn)是 Pod 頻繁被 OOM kill 或調(diào)度失敗。排查時(shí)先看 Pod 的 events確認(rèn)是調(diào)度失敗還是運(yùn)行中被 kill。調(diào)度失敗通常是 request 設(shè)得太大節(jié)點(diǎn)放不下運(yùn)行中被 kill 通常是 limit 設(shè)得太小峰值時(shí)超了。我整理了一個(gè)速查表覆蓋幾種典型情況。現(xiàn)象可能原因排查方法處理建議Pod 一直 Pendingrequest 過(guò)大或節(jié)點(diǎn)資源不足看 describe pod 的 events降低 request 或擴(kuò)容節(jié)點(diǎn)池Pod 頻繁重啟limit 過(guò)小或內(nèi)存泄漏看 pod 的 restart count 和 OOM 記錄提高 limit 或排查泄漏任務(wù)超時(shí)失敗超時(shí)設(shè)置不合理或工具卡住看任務(wù)各步驟耗時(shí)分布調(diào)整超時(shí)或加工具級(jí)超時(shí)檢查點(diǎn)寫(xiě)入失敗存儲(chǔ)不可用或權(quán)限問(wèn)題看 runtime 日志檢查存儲(chǔ)連接和權(quán)限配置6.4 幾個(gè)我踩過(guò)的坑第一個(gè)坑是把 agent 的上下文存在了 Pod 的本地磁盤(pán)。Pod 重啟后本地磁盤(pán)清空上下文全丟。后來(lái)改成檢查點(diǎn)存對(duì)象存儲(chǔ)本地只做緩存問(wèn)題解決。第二個(gè)坑是用 K8s 的 liveness 探針檢查 agent 的存活。agent 在執(zhí)行長(zhǎng)任務(wù)時(shí)主線程可能被占用導(dǎo)致探針超時(shí)Pod 被誤殺。后來(lái)把 liveness 探針改成檢查一個(gè)獨(dú)立的健康端點(diǎn)不依賴(lài)主線程問(wèn)題解決。第三個(gè)坑是任務(wù)隊(duì)列沒(méi)有做優(yōu)先級(jí)。所有任務(wù)一視同仁結(jié)果一個(gè)長(zhǎng)任務(wù)堵在前面后面的短任務(wù)全被拖慢。后來(lái)加了優(yōu)先級(jí)隊(duì)列短任務(wù)和高優(yōu)先級(jí)任務(wù)可以插隊(duì)整體吞吐提升明顯。7. 我對(duì)這套方案的一些個(gè)人體會(huì)做 agentic runtime 這件事最大的感受是邊界比功能重要。一開(kāi)始總想著把功能做全結(jié)果 runtime 越來(lái)越臃腫調(diào)試越來(lái)越難。后來(lái)想清楚了 runtime 和 orchestration 的邊界把該分出去的邏輯分出去系統(tǒng)反而穩(wěn)定了。另一個(gè)體會(huì)是檢查點(diǎn)的設(shè)計(jì)要趁早。我第一個(gè)版本沒(méi)做檢查點(diǎn)任務(wù)失敗就從頭來(lái)調(diào)試階段還能忍上了生產(chǎn)就完全不行。后來(lái)補(bǔ)檢查點(diǎn)發(fā)現(xiàn)很多地方要改成本比一開(kāi)始就設(shè)計(jì)高得多。所以如果你現(xiàn)在正在做類(lèi)似的東西哪怕任務(wù)還簡(jiǎn)單也建議把檢查點(diǎn)的接口先留出來(lái)。還有一個(gè)反直覺(jué)的經(jīng)驗(yàn)不要過(guò)度追求資源利用率。我早期總想把節(jié)點(diǎn)資源壓榨到極致結(jié)果 Pod 頻繁因?yàn)橘Y源競(jìng)爭(zhēng)被 kill。后來(lái)把資源留出 30% 的余量雖然成本高了一點(diǎn)但穩(wěn)定性提升了一大截綜合算下來(lái)反而更劃算。這套東西還在持續(xù)演進(jìn)Kubernetes 社區(qū)對(duì) agentic 負(fù)載的支持也在變化。我目前關(guān)注的一個(gè)方向是能不能用更原生的方式表達(dá) agent 任務(wù)的生命周期而不是靠自定義控制器去補(bǔ)。如果這塊有進(jìn)展現(xiàn)在的很多 workaround 可能就不需要了。