Agentic Reliability Engineering

第三章
代理式可觀測性的基礎

本章為最終書稿的第二章。請注意,GitHub 儲存庫將於稍後正式啟用。
若您願意實際參與本草稿的審閱與意見回饋,請與編輯聯繫:jleonard@oreilly.com

大多數可觀測性堆疊,都是為了讓人類判讀而設計的。但在接下來的十年裡,那將是錯誤的閱聽眾。

代理式系統首先失敗的地方,不是決策,而是感知。在一個系統能夠以任何程度的自主性進行推理、預測或行動之前,它必須先能夠可靠地觀察自己與周遭環境——不是像人類今天透過儀表板與警報那樣去觀察系統,而是以一種能讓機器組成情境、評估條件、有信心地做出決策的方式。這正是為什麼可觀測性並不只是 ARE 的一項輔助能力,而是它最根本的依賴;把順序搞錯,是早期代理式專案陷入停滯最常見的單一原因。

前一章主張,人類已經無法再站在迴圈中心,如今站在那裡的是代理。代理能看見什麼、能理解自己所看到的東西到什麼程度,成了整套可靠性系統中最重要的一項特質。本書其餘的一切都仰賴於它:韌性、事故應變、交付、治理、推理模式——如果代理對系統的感知不可靠、模糊不清,或是為了另一種閱聽眾而建構的,這些都無法運作。這正是為什麼可觀測性被排在本書的第一位,而不是像大多數教科書那樣放在第三或第四位。代理的感知,是其他一切的先決條件。這一層搞錯了,上面的每一層都會以看似不相干的理由失敗。

在傳統的 SRE 與維運模型裡,可觀測性主要是為了支援人類而存在。指標、日誌、追蹤的設計目的,是在事故期間回答問題,支援事後分析,或事後驗證假設。這套模型運作得還算不錯,儘管並不完美,因為人類很擅長補足缺口——工程師會推斷意圖、容忍模糊、用經驗與直覺填補缺漏的情境。機器做不到這些。代理只能對它能看見的東西進行推理,也只有在它所看見的東西準確、具備情境、且與決策相關時,它才能負責任地行動。

這個區別,在團隊試圖在既有可觀測性基礎之上導入自主性的那一刻,就會變得清晰可見。在許多環境裡,可觀測性堆疊表面上看起來很健康:儀表板很乾淨,警報調校得宜,關鍵服務顯示穩定的平均值。從人類的角度看,這個系統感覺被充分理解;從機器的角度看,往往並非如此。延遲指標可能被大量平均化,掩蓋了對決策時機至關重要的尾端行為;錯誤率可能跨越商業影響截然不同的端點被彙總在一起;遙測資料可能缺乏擁有權、關鍵性或依賴關係的中繼資料,因為人類早已知道這些關係。對工程師而言,這些缺口是可以應付的;對代理而言,這些缺口恰恰在最需要精準的那一刻,製造出模糊性。

早期的代理式專案,往往就在這裡悄悄失敗——不是因為代理設計得不好,而是因為感知層從一開始就不是為了支援推理系統而打造的。可觀測性的基礎,被優化成服務於「注意力」,而不是「理解」。這正是本章要填補的落差。讀完本章之後,讀者應該對「為代理打造的遙測資料」長什麼樣子、它與「為儀表板打造的遙測資料」有何不同,以及一支團隊可以採取哪些具體步驟,在不需要從零重寫整套堆疊的情況下,從前者演進到後者,有一套可操作的定義。

2.1 超越可見性的可觀測性

可見(visibility)不等於理解,理解也不等於決策就緒(decision readiness)。

大多數團隊相信自己擁有良好的可觀測性,因為他們能「看見」自己的系統:他們有儀表板、警報、服務地圖、事故時間軸,出事時也答得出問題。這個信念在人類主導的模型裡是合理的,但一旦我們要求系統自己採取行動,它就變得脆弱。從「可觀測性即呈現」轉向「可觀測性即輸入」,正是本章的核心轉變。這個差異之所以重要,是因為「可見性」的優化目標是「注意力」,而「決策就緒」的優化目標是「後果」——這兩個目標並不相同,它們所要求的取捨,往往正面衝突。

想像一個常見的生產環境設定:一個服務對外暴露請求延遲、錯誤率與吞吐量,儀表板顯示滾動平均值,超過門檻就觸發警報。事故發生時,工程師深入查看、交叉比對日誌與追蹤,找出根本原因。這套系統之所以能運作,是因為人類補上了缺口。對代理而言,同樣的設定卻是不完整的:平均延遲掩蓋了決定使用者體驗的尾端行為;彙總的錯誤率把關鍵路徑與非關鍵路徑混在一起;警報門檻編碼的是人類的容忍度,而不是營運風險。這些都不是錯誤,而是針對人類認知所做的優化。現在,導入一個被賦予「提早行動以保護 SLO」任務的代理:它看見延遲上升,卻無法判斷這個影響是侷限在一個低優先端點,還是正在讓某個關鍵旅程惡化;它無法評估某個依賴是原因還是症狀;它無法對爆炸半徑進行推理,因為擁有權與關聯關係並未被編碼進遙測資料裡。結果是可以預見的:代理要嘛過度保守、什麼有用的事都不做,要嘛過度激進、製造出不穩定。無論哪一種,失效模式最終都可以追溯回「看得見、卻無法據以行動」的可觀測性。

這個轉變需要一套不同的量測單位。我們把它稱為「決策級遙測」(decision-grade telemetry),這個詞會貫穿本書其餘篇幅。決策級遙測,是為了讓代理據以行動而打造的遙測資料,而不是為了讓人類盯著看而打造的。這不是小差異:儀表板呈現的是「有趣的東西」;決策級遙測呈現的是「可以做決定的東西」。儀表板容許模糊,因為人類可以自行釐清;決策級遙測不能容許模糊,因為沒有人會在代理行動之前先幫它釐清。儀表板是靠「解讀」被消費的;決策級遙測是靠「程式碼」被消費的。

四項特質把決策級遙測與其他遙測資料區分開來。它是「可查詢的」(queryable):代理能在決策當下以程式化方式取得它,不需要解析散文或對 UI 做畫面爬取。它是「正規化的」(normalised):同一個概念在不同服務、環境、團隊之間有相同的表示方式,讓推理可以跨越它們組合起來。它是「語意標註的」(semantically annotated):訊號本身代表什麼,會與訊號一起被編碼下來,而不是隱含在當初設定儀表板的那個人腦中。它也是「決策相關的」(decision-relevant):決策級集合裡的每一個訊號,都是因為某個具體的決策需要它才存在,而沒有任何決策會用到的訊號,會被認定為「成本」,而不是「涵蓋率」。同時滿足這四項特質的遙測資料,才算得上決策級;只滿足第一項的,只是一種更快讓人困惑的方式。

圖 3-1 可見性 vs. 決策就緒的可觀測性。同樣的資料,不同的形狀。
附註 決策級遙測的判準:如果代理必須解析一個字串才能對一個訊號採取行動,這個訊號就不是決策級的。結構化欄位、有型別的值、明確的語意,在這裡不是風格上的偏好,而是任何後續推理系統的先決條件。

以人類為中心的可觀測性問的是「我現在該看什麼?」代理式的可觀測性問的是「現在可以安全地做出什麼決策?」這個區別重塑了下游的一切。一個為服務呈現前五大指標的儀表板,對於一位在掃描異常的人類來說很有效;但代理需要理解哪些指標在什麼條件下、以什麼信心程度,對哪些決策才重要——這需要的是語意,而不是呈現。一個延遲指標,在圖表上可能完美易讀,但對機器而言卻可能完全無法解讀,除非它帶有這些情境:它代表哪些請求、涉及哪些依賴、影響哪些使用者或旅程、這個狀況正在以多快的速度演變。沒有這些情境,這個訊號就無法被安全地用於自主行動。

值得具體走一遍「決策就緒」在一個從一開始就為此而設計的系統裡長什麼樣子。想像一個支援多種、關鍵性各不相同的使用者旅程的大型服務導向平台。它的可觀測性,不是優先圍繞著儀表板組織起來的,而是圍繞著系統在壓力下可能需要做出的決策組織起來的。延遲不是以單一的服務級平均值呈現,而是以端點層級的分布來擷取,每個分布都帶有明確的標籤:服務名稱與版本、依賴身分、使用者旅程、商業關鍵性等級。這讓人類與代理都能區分出:一個低優先報表端點上的尖峰,與結帳或身分驗證路徑上早期的惡化,即使整體服務平均值依然穩定。錯誤訊號遵循同樣的原則:與其呈現單一錯誤率,不如依失敗類別、請求意圖、涉及的依賴來分段,這讓系統能偵測到不對稱的失效模式,例如某個依賴只回傳部分錯誤、只影響一部分關鍵請求的情況。人類需要時,仍然可以查看彙總摘要;決策級訊號則依然完整存在、結構化地留在底層。

飽和度(saturation)是值得做對的第三類訊號。預設做法是暴露 CPU 與記憶體使用百分比,這些數字容易視覺化,也是十年來儀表板上的常客。問題在於,這些數字很粗糙,而且落後於真實的營運現況——等到 CPU 到達 90%,佇列早已開始堆積;等到佇列開始堆積,延遲早已超標;等到延遲超標,SLO 早已在燃燒。具備意圖感知的飽和度訊號(佇列深度相對於可接受延遲的比例、請求積壓的成長速率、執行緒池或連線耗盡的趨勢)雖然雜訊較多、視覺上也不那麼討喜,卻更具預測力。它們讓代理能在系統即將違反 SLO 的那一刻就辨識出來,而不是事後才反應。這些指標的插測難度並不比 CPU 高,難的是「記得要去插測」,因為過去十年掛在維運室牆上的儀表板,從來不需要它們。

這個轉變中最陰險的失效模式,是「虛假的信心」。儀表板看起來很健康,警報一片安靜,系統看似穩定。人類信任自己所看到的,而代理則以機器的速度與一致性,把這份信任放大。曾經可以容忍的盲點,變成了自動化的行為;小小的建模誤差,複利累積成系統性的失效。這正是當一支團隊把可觀測性當成一層「回報層」,卻要求一個推理系統在它之上運作時,會發生的事——問題不在推理系統,問題在回報層。虛假的信心特別常見於:指標被大量彙總、服務之間的關係是隱含而非明確的、擁有權是被假定而非被編碼的、訊號是為了回溯而不是為了預判而設計的時候。代理式系統不會質疑這些假設,它們只會把這些假設大規模地付諸實行。

這樣的重新框架,會逼出一些不舒服的對話。團隊會發現,自己過去在可觀測性上的許多投資,優化的目標是「回報」,而不是「推理」。這不代表那些投資是浪費的,但確實意味著它必須演進。本章接下來的篇幅,就是要談「如何演進」:我們會探討什麼讓一個訊號在語意上可被代理使用、如何為推理系統設計遙測資料、如何為代理需要用來推理爆炸半徑的關係建模,以及當這整套堆疊以錯誤的順序被建構時,通常會在哪裡出問題。在這一切之前,第一步是先接受這個重新框架:可觀測性不再是你「呈現」的東西,而是你「據以運算」的東西。一旦團隊接受這一點,接下來的設計決策就會開始說得通;在那之前,其他任何對代理式可靠性的投資,都建立在一個承載不住它的基礎之上。

2.2 訊號、語意與決策相關性

一旦可觀測性超越了「可見性」,下一個問題就不再是「我們能看見什麼」,而是「系統能理解什麼」。

這正是許多可觀測性計畫停滯的地方——因為蒐集訊號很容易,讓那些訊號對決策有意義卻不容易。對人類而言,解讀是下意識發生的:一位經驗豐富的工程師看到延遲飆升,會本能地過濾雜訊、回想過去的事故、與近期變更做關聯。對代理而言,這些直覺完全不存在,除非我們明確地把它編碼進去。因此,訊號必須攜帶語意。沒有語意,資料只是量測;有了語意,它才成為證據。這個區別不是學術上的講究,而是「會反應的自動化」與「會推理的自主性」之間的差別。

能夠填補這道落差的特質,我們稱之為「決策相關性」(decision relevance)。當一個訊號與代理可取得的其他訊號放在一起時,會改變代理將選擇的行動,這個訊號就具備決策相關性;一個不會改變選擇的訊號,無論它有多精準或產製成本有多高,都不具備決策相關性。今天大部分的遙測資料都不具備決策相關性——它們只是「量測」,之所以被擷取,只是因為擷取起來很容易,之所以被保留,只是因為沒有人曾練就「主張刪除它」的肌肉。決策相關的遙測資料是被「精選」出來的,而不是「窮舉」出來的。這門紀律,就是要知道哪個訊號會真正影響代理的決策,然後只留下那些。

當你拿一個熟悉的指標、換到代理那一側去看時,這個對比會變得格外鮮明。以一個從結帳服務擷取的單一延遲訊號為例。若以一分鐘視窗內的服務級平均值擷取、不帶任何標籤,它長這樣:

checkout.latency.avg = 142ms

這對儀表板來說是個不錯的數字。看著它的人類知道「checkout」是什麼、大致知道健康的數字長什麼樣、從情境中知道螢幕上顯示的是一分鐘平均值,若有異狀還能拉出端點層級的分布。但看著它的代理,這些什麼都沒有:平均值是因為最慢的 1% 請求翻倍而移動,還是因為中位數整體上升?哪些端點造成了影響?哪些使用者旅程正在惡化?這個尖峰出現在已驗證身分的路徑,還是匿名路徑?是不是某個已知的依賴正在上游惡化?這個訊號回答不了以上任何一個問題,所以代理也無法就這些問題進行推理。這個訊號不具決策相關性,它只是一個量測值。

現在,同樣的底層資料,以語意化方式擷取:

service: "checkout"
endpoint: "/api/v2/cart/submit"
journey: "purchase"
criticality: "tier-1"
latency_p50_ms: 38
latency_p99_ms: 412
latency_p999_ms: 1180
request_count: 8204
window_start: "2025-11-14T03:14:00Z"
window_seconds: 60
upstream_dependencies: ["payments", "inventory"]
upstream_dependency_health: { "payments": "degraded", "inventory": "ok" }
recent_change_id: "deploy-3942"
recent_change_age_seconds: 184
範例 同樣的底層延遲資料,以語意化方式擷取:以 p50/p99/p999 取代單一平均值;帶有端點、旅程、關鍵性標籤;上游依賴的健康狀態;代理現在可以問:p99 是不是在一個第一級端點上惡化?上游依賴是不是處於已知的降級狀態?這次變化是否與近期的一次變更相關聯?這些問題,代理都可以據以行動;而第一個版本,一個都答不出來。

這裡的資料量並不比原本大多少,改變的是「發送時」所套用的紀律。一旦一個訊號攜帶了語意,代理就不再需要猜測:它可以把行動的範圍限縮到正確的端點;可以在上游依賴更可能是原因時延後行動;可以把這次延遲的變化,與三分鐘前上線的一次部署做關聯。這些每一項都是「決策」,而不是「量測」,而決策正是代理式系統被建構出來要產出的東西。這正是決策級遙測的全部工作,在單一訊號上被具體呈現出來。

還有三項語意特質值得在這裡明確點出,因為它們正是大多數團隊投資不足的地方。第一,語意必須在不同發送端之間保持一致。同一個延遲訊號,在結帳服務上代表一個意思,在搜尋服務上代表另一個意思,就會產生互相矛盾的推理。Schema 就是契約,而這個契約必須在發送當下就以程式碼強制執行,而不是在除錯時寫在一份 wiki 上。第二,語意必須有版本控管。Schema 會演進,若代理是依照某一版 schema 訓練或設定的,卻讀到依照另一版 schema 發送的訊號,就會誤讀。遙測資料需要和生產程式碼一樣的發布紀律;把它當成「設定」的團隊,通常會用慘痛的方式發現這一點。第三,決策相關性是有條件的,不是絕對的。不是每個訊號對每個決策都重要——部署期間錯誤率上升,可能代表一次失敗的發布;同樣的錯誤率上升,若發生在一次受控的混沌實驗期間,卻可能是預期中的事。無法區分這兩種情境的代理,要嘛反應過度,要嘛反應不足。因此,決策相關性取決於條件,而這些條件必須和量測值本身一樣,被謹慎地編碼進訊號串流裡:部署狀態、實驗狀態、擁有權邊界、政策限制,這些都屬於遙測資料的一部分,因為它們都會改變某個特定訊號所代表的意義。

信心與不確定性,在這裡值得特別談一談,因為這正是代理式可觀測性與傳統可觀測性差異最大的地方。人類維運者對不確定性感到自在,我們經常會說「這看起來有點可疑」或「我還不確定這是不是根本原因」。傳統的監控工具並不會為這種細微差別建模,它們呈現的是二元狀態:健康或不健康、觸發警報或沒有。代理式系統無法只依賴二元解讀,它們必須對信心程度進行推理:某個訊號可能以低確定性暗示一個問題;另一個訊號可能強烈指出影響,但只針對一部分使用者。決策邏輯必須把這個漸層考慮進去。這正是可觀測性資料與機率、推論相交的地方。訊號的設計應該支援信心程度的估算——這意味著歷史基準線、變異量測、關聯強度,或是與數值一起發送的明確不確定性區間。當信心程度沒有被建模時,代理往往會落入兩種失效模式之一:要嘛行動過於激進,觸發不必要的介入;要嘛變得過度保守,凡事都要求人類核准。這兩種結果都會削弱自主性,也都無法靠更好的模型來修正——修正的地方,在訊號本身。

在離開這一節之前,還有最後一項風險值得標記出來。當語意缺失或不一致時,失效模式並不會立刻顯現:代理依照手上的訊號行動,這個訊號看起來合理,這個行動看起來也合理,系統繼續運轉。實際上,這個系統已經變成「自信地犯錯」,而這份錯誤的代價會靜靜地累積,直到某件事以再也無法解釋的方式壞掉。下一章談韌性時,會把這個問題當成頭等大事來處理。現階段的警語是:決策相關性不是可有可無的選項。一個沒有語意根基的推理系統,只是一台快速的猜測機器——有時候猜對,有時候猜錯,但無論哪一種,都不是本書想談的東西。

2.3 為推理系統設計遙測資料

知道遙測資料必須是決策級的是一回事,從一開始就把它設計成那樣,是另一回事。

大多數團隊是從既有的可觀測性堆疊出發,試圖把決策相關性回頭補進去。這沒有問題,也是大多數團隊實際會走的路。但當你把設計規則講得像是從一張白紙開始時,它們會最清楚。四條規則涵蓋了大部分的重點,每一條都對應第 2.1 節裡決策級遙測的四項特質之一。

第一條規則是「預設機器可解析」。每個訊號都以帶有型別欄位的結構化紀錄發送,而不是一個需要被解析的字串。日誌遵循 schema;指標以結構化的鍵值對攜帶自己的標籤;追蹤的語意標籤附掛在 span 上,而不是埋在一個訊息欄位裡。代理永遠不應該需要套用正規表達式才能對一個訊號採取行動——如果它必須這麼做,這個訊號就不是決策級的,而使用它的成本,會隨著你連接的每一個新代理而遞增。這裡的紀律是機械性的,而不是架構性的:在插測時多花一小時,在整合時就能省下數週。實務上,這通常意味著採用像 OpenTelemetry 這樣的統一遙測模型——不是因為它時髦,而是因為它強制訊號、追蹤、中繼資料以一致的方式表達。指標、追蹤、日誌會變成同一組底層事件的不同視角,而不是在讀取時才拼接在一起的獨立串流。正是這種一致性,讓代理能夠跨遙測類型組合訊號,而不需要每個代理各自重新實作一次串接邏輯。

第二條規則是「預設帶有情境」。每個訊號都在同一筆紀錄裡,攜帶解讀它所需的情境。一筆延遲讀數,攜帶它的端點、旅程、關鍵性、依賴情境;一筆錯誤紀錄,攜帶它的失敗類別、請求意圖、依賴身分;一個飽和度訊號,攜帶它所代表的佇列或池,以及這個佇列深度是依據哪個 SLO 被判定的。情境的具體形狀因領域而異,但原則是通用的:如果解讀一個訊號需要在讀取時把它與另一個獨立系統做關聯,這個關聯就會變成代理每一次決策都要繳的延遲稅,更糟的是,一旦那個獨立系統本身出狀況,它就會變成失效的來源。要把情境推進訊號本身,而不是推給消費端:用直方圖取代平均值,在發送時就編碼擁有權與關鍵性標籤,讓彙總是刻意套用的,而不是預設就發生的。這些都不需要新的工具,需要的是團隊以「機器將要據以行動」的方式來建模自己的資料——因為愈來愈多情況正是如此。

第三條規則是「時間一致」。來自不同來源的訊號,時間對齊的方式必須足以支援因果推理。時鐘要以 NTP 同步;時間戳要在來源端就發出,而不是在攝取時才被指派;時間視窗要明確——訊號發送端要自己宣告觀測視窗,而不是依賴消費端去推斷。這聽起來很無聊,確實也很無聊,直到你看見一個代理把某次延遲飆升歸咎給錯誤的部署,只因為造成這次延遲的部署事件,在延遲訊號抵達可觀測性管線的四十秒之後才姍姍來遲。時間一致性,正是把一串量測值變成代理能讀懂的故事的關鍵。在這套模型裡,分散式追蹤系統會變得特別強大,因為經過依賴關係與意圖中繼資料豐富化的追蹤,能給代理單靠鬆散的指標無法提供的因果情境。代理問的問題,不再是「這個服務是不是變慢了」,而是「哪個依賴正在惡化、影響哪個旅程、造成什麼衝擊」——這是一個不同的問題,只有在時間是誠實的情況下才問得出來。

第四條規則是「保留血緣」(lineage-preserving)。每個訊號都能被追溯回它的源頭:是哪個服務發出的、是依照哪個版本的 schema 產出的、走過哪條基礎架構管線。當代理的推理仰賴某個訊號時,代理應該要能檢視這個訊號的來歷。這一點比聽起來還重要,因為代理式失效裡最糟的一種,就是代理根據事後證明是錯誤的資料,做出了「正確」的反應。血緣,正是讓這種失效變得可除錯的關鍵;沒有它,代理的推理是一個黑盒子,它所推理的資料也是。血緣在稽核時同樣重要:當監管單位、高階主管,或平行團隊問「代理為什麼採取那個行動」時,團隊的答案不能是「訊號看起來是這樣,而我們信任這條管線」,答案必須能從紀錄中重建出來,包括 schema 版本、來源、資料所走過的路徑,都必須是可查詢的。把血緣當成遙測資料本身的一項特性來對待,而不是可觀測性廠商未來某個版本或許會支援的東西。

這四條規則加在一起,會產出可查詢、正規化、語意標註、決策相關的遙測資料。它們也會產出一項本章目前還沒點名、但需要點名的特質:遙測資料本身的可靠性,變成了我們要量測的對象——涵蓋率、新鮮度、語意完整性、schema 一致性。這些不是「有的話更好」的東西,而是代理需要用來判斷「其他訊號是否可信」的第一等訊號。我們把「關於遙測資料品質的遙測資料」稱為「代理式 SLI」(agentic SLI),本書會反覆回到這個概念。一支從未量測過自家可觀測性堆疊品質的團隊,等於是在要求自己的代理,依據一組可靠性無法被刻畫出來的訊號行動——這是 SRE 團隊絕不會接受用在一項服務上的立場,也沒有理由接受用在如今驅動著那項服務的代理身上。

小提示 一句話的代理式 SLI 測試:我能不能用查詢這個訊號本身的方式,去查詢它的新鮮度、完整性與來歷?如果可以,這個訊號就是可操作的;如果不行,這個訊號就只是裝飾。

這些規則所描述的轉變,說到底,是一種「閱聽眾」的轉變。為儀表板打造的遙測資料,是為了被「閱讀」而建構的;為推理系統打造的遙測資料,是為了被「據以運算」而建構的。至今為止,大多數可觀測性投資都屬於前者;接下來十年的可觀測性工作,屬於後者。做對這件事的團隊會發現,只要套用不同的紀律,自己既有的堆疊大多都能滿足需求;做不對的團隊,則會發現自己一而再、再而三地重建同一套堆疊,每一次都怪罪工具。真正的限制,很少是平台本身,而是 schema、慣例,以及「願不願意把可觀測性層當成一個生產系統來對待」。把這三件事做對的團隊,不需要遷移自己的遙測堆疊;把它們做錯的團隊,則會遷移三次,另一端依然沒有決策級的遙測資料。

2.4 關係、依賴圖與擁有權情報

決策級訊號是必要的,但並不足夠。推理系統不會孤立地對單一訊號採取行動,它是根據訊號彼此之間的關係來行動的,而這些關係,正是意義的主要來源。

在人類主導的維運裡,依賴關係的知識存在於人腦中:資深工程師知道哪些服務脆弱、哪些依賴是關鍵的、哪些失效會連鎖擴散。這份知識很少以機器能消費的形式存在。只要人類還是主要的決策者,這道落差就還能存活;一旦代理加入迴圈,這道落差就變成了一道硬邊界。代理無法單靠遙測資料對爆炸半徑進行推理,它需要一個結構,說明「什麼依賴什麼、方向為何、扮演什麼角色、由誰擁有」。這個結構,就是拓撲圖(topology graph)。

拓撲圖,是代理在推理時會查詢的系統模型。服務是節點,依賴關係是有型別的邊。每一條邊都攜帶方向(哪個元件依賴哪個)、角色(同步呼叫、非同步事件、快取查詢、選用性的豐富化)、關鍵性(這條邊的失效會阻斷一個使用者旅程、讓它降級,還是只會記一筆警告日誌)。擁有權則以中繼資料的形式附掛在節點上,編碼著誰負責當責、適用哪一條升級路徑。變更事件則以事實串流的形式流過這張圖,讓代理能與目前的狀態做關聯。拓撲圖之於代理式推理,就相當於服務地圖之於人類的疑難排解,只不過它具備機器需要用來「行動」而非只是「查看」的精確度。

本章接下來會反覆回到這張圖,並以「拓撲代理」(Topology Agent)稱呼它。拓撲代理擁有這張圖,並讓它保持最新,同時對外暴露一個查詢介面,供其他代理在需要對某個服務的鄰域進行推理時呼叫。當一個事故代理需要知道結帳服務上的延遲飆升,是否可能是上游付款服務惡化所致時,它會查詢拓撲代理;當一個變更代理需要知道,哪些服務與它即將部署的服務命運與共時,它會查詢拓撲代理;當一個資源配置代理需要知道,擴充某個服務是否會影響下游依賴時,它同樣會查詢拓撲代理。拓撲代理,是這個系統對自身結構的記憶,本書其餘每一個代理都仰賴著它。

附註 拓撲代理,是貫穿本書的代理陣容中,第一個被正式命名的成員,會在第五、八、十四、十五章再次出現。現階段,先把它當成拓撲圖的具名擁有者。當一個代理對關係進行推理時,它推理的對象,就是拓撲代理所維護的模型。

有一個實用的方式,可以把這件事具體化:看看事故期間,代理對這張圖跑的是什麼樣的查詢。假設一個名為 inventory-api 的服務觸發了延遲警報,事故代理不需要孤立地看 inventory-api,它需要知道 inventory-api 連接到什麼、這些連接長什麼樣子。一次對拓撲代理的查詢,可能會回傳這樣的內容:

service: "inventory-api"
owners: ["team-platform"]
tier: "tier-1"
upstream_dependencies:
  - service: "warehouse-db",  role: "blocking", criticality: "critical"
  - service: "cache-cluster", role: "fallback", criticality: "degraded-ok"
  - service: "feature-flags", role: "optional",  criticality: "tolerable"
downstream_consumers:
  - service: "checkout-api",  journey: "purchase", impact: "blocks"
  - service: "search-api",    journey: "browse",   impact: "degrades"
  - service: "reporting-job", journey: "internal", impact: "delays"
recent_changes:
  - change_id: "deploy-3942", age_seconds: 184, scope: "inventory-api"
  - change_id: "config-1027", age_seconds: 612, scope: "warehouse-db"

這讓代理能夠推理出:一個會阻斷第一級使用者旅程的惡化,與一個只會延遲一份內部報表工作的惡化,兩者之間的差別。前者值得積極出手,後者則值得延後處理。代理不需要被明確告知這一點,它可以從這張圖與關鍵性標籤,自己推理出正確的行動。單靠訊號本身,是無法支撐這種推理的——正是這張圖,讓這種推理成為可能。

圖 3-2 一張帶有擁有權疊層的有型別拓撲圖。這正是拓撲代理所維護的結構。

這張圖本身可以用兩種方式建構,而大多數成熟的團隊兩者兼用。一部分是從遙測資料推斷出來的:分散式追蹤揭露呼叫路徑,事件揭露協調關係,設定狀態揭露預期中的拓撲結構。推斷的方式擴展性很好,但當流量爆量、重試很激進,或備援機制在壓力下被觸發時,會產生雜訊很多的邊。另一條路徑,是明確地宣告依賴關係:擁有某個服務的團隊,把它依賴什麼、以什麼角色、什麼關鍵性,作為該服務設定的一部分宣告出來。宣告出來的依賴關係,會與觀測到的追蹤資料做驗證,讓這張圖保持誠實。兩者結合,會產出一張既完整又準確的圖;單靠任何一種方式,都容易產生漂移。

在團隊承諾採用其中一種方式之前,值得先理解兩者的取捨。推斷出來的圖,起步成本低,也不需要與擁有服務的團隊協調;它的失效模式,是把暫時性的邊(重試、備援、罕見路徑的查詢)與正規的邊賦予同樣的權重,一張把每一次觀測到的呼叫都升格為「真正」依賴的圖,會產出把雜訊當成結構來對待的推理。修正方式是邊的加權與穩定性過濾,這確實有效,但前提是這張圖已經上線夠久,足以學會「穩定」長什麼樣子。宣告出來的圖則正好相反:它要求每一個擁有服務的團隊事先付出紀律,但產出的圖從第一天起就是權威的。它的失效模式,是宣告出來的依賴關係與現實漂移——一個團隊在程式碼裡加了一個新依賴,卻忘了更新宣告,這張圖相信世界長成一個樣子,而系統實際上卻是另一個樣子。與觀測到的追蹤資料做驗證,可以補上這個迴圈,但前提是這個驗證被當成一個生產訊號來對待,而不是「有的話更好」。大多數建構出一張能用的圖的團隊,都是用推斷來啟動、用宣告來錨定,並在兩者之間持續進行調和。

擁有權情報,是坐落在這張圖之上、讓它的行動變得安全的那一層。知道一個服務依賴什麼,能告訴你什麼可能會壞;知道誰擁有它,能告訴你出事時該找誰。擁有權之所以重要,有兩個原因:它編碼了「哪個團隊當責」的社會契約,代理在決定是要單方面行動還是升級時,必須尊重這一點;它也編碼了代理在不經協調的情況下,被允許採取哪些行動的邊界。一個代理若在未經諮詢的情況下,隔離了一個由另一個團隊擁有的第三方依賴,這不是在優化,而是在製造一起治理事故。拓撲代理的擁有權中繼資料,正是防止這件事發生的關鍵:代理讀取擁有權,行動政策拿擁有權與代理的權限做比對,這個行動要嘛觸發,要嘛被轉去審查。

一張有型別的依賴圖,加上擁有權中繼資料,兩者結合,正是把孤立的訊號變成代理能夠據以推理的系統的關鍵。沒有它,代理是在對彼此不相連的量測值採取行動,並祈禱自己推斷出來的關聯是真的;有了它,代理就擁有了一位資深待命工程師在應變事故時所帶來的那種結構性認知,只不過被編碼成了機器能在毫秒之間查詢的形式。這是本書第二部分一切內容的結構性前提,也是今天大多數可觀測性堆疊裡,投資最不足的一層。把拓撲當成一份 wiki 頁面來對待的團隊,等於是要求自己的代理盲目運作;把拓撲當成一個可查詢、有擁有者、受版本控管的產物來對待的團隊,才是在做其他一切賴以建立的基礎工作。

2.5 控制、治理與決策邊界

可觀測性與治理,處處相互接觸,卻不是同一件事。決策級遙測告訴代理「正在發生什麼」;第 1.5 節介紹過的自主邊界,告訴代理「它被允許對此做什麼」;而我們將在第 4.6 節詳細介紹的治理平面,是這些權限實際存在的那個工程化介面。這幾層各自都值得用一整章的篇幅來談,我們不會在這一節裡把它們壓縮在一起。這裡真正重要的,是兩者之間的交接:可觀測性堆疊產出的訊號,正是治理層在決策當下拿來對照政策評估的東西。如果訊號不是決策級的,治理層的評估就不可信;如果治理層不存在,決策級訊號就被浪費了。這兩層是一起演進的,而且演進到相同的成熟度。一支在其中一層投資很深、卻忽略另一層的團隊,會發現投資不足的那一層,變成了自己代理能安全做到什麼事的限制因素,無論另一層多麼成熟都無濟於事。等我們進入架構那一章時,會正式地把這個議題接續下去。

2.6 失效模式、限制與反模式

到目前為止,本章談的是代理式可觀測性運作良好時的樣貌。這一節要談的是反面。五種失效模式,會在嘗試這項工作的團隊之間反覆出現,每一種都以不同的方式削弱代理式可靠性。把它們點名出來,是在自己的堆疊裡辨識出它們的第一步。

在逐一點名之前,有一項這些失效模式共有的特質值得先說清楚:它們每一個都是「設計」上的失敗,而不是「工具」上的失敗。更好的儀表板修不好它們,更好的警報路由也修不好它們。這些失效,存在於團隊決定如何為自己的資料建模、決定把情境放在哪裡、決定哪些訊號值得投資這些選擇之中。這讓它們變得頑固,但也讓它們變得可辨識——這意味著一支能在自己堆疊裡點名出這些失效的團隊,可以用自己既有的紀律修好它們,而不需要買任何新東西。

第一種是「靜默腐化」(silent decay)。這些訊號在團隊建構它們的當下是決策級的;但 schema 會隨時間漂移:新服務用舊的慣例發送資料,過去必填的欄位變成選填,一個關鍵性標籤不再有人維護。這些都不會觸發任何警報,因為從量測的角度來看,什麼都沒壞——指標依然在流動,儀表板依然在渲染。然而,代理的推理品質卻正沿著一條沒有人在盯的曲線持續惡化。等到有人注意到時,代理可能已經做出微妙變差的決策長達數週之久。靜默腐化正是第 2.3 節那套代理式 SLI 紀律存在的理由——你無法管理你不去量測的東西,而大多數團隊並不會量測自己遙測資料本身的可靠性。

第二種是「過時的拓撲」(stale topology)。這張圖在建構當下是準確的:新服務上線、依賴被加入、擁有權易主。有些變化會透過 CI 自動更新這張圖,有些則仰賴有人記得去更新一份宣告。這張圖就這樣漂移了,而代理是根據那個過時的版本進行推理,它所選擇的行動,是基於一個早已不存在的系統。這裡的解方不是英雄式的補救,而是把拓撲圖當成一個和程式碼一樣受發布紀律約束的生產產物,並持續地——而不是只在部署時——把宣告出來的依賴關係與觀測到的遙測資料做驗證。

第三種是「模糊的語意」(ambiguous semantics)。一個服務上的延遲訊號,把花在上游呼叫上的時間也算進去;另一個服務上的同名訊號,卻把它排除在外。一個面向使用者的錯誤指標,在某個團隊的服務上把重試算作失敗,在另一個團隊的服務上卻把它算作成功的最終完成。這些指標帶著相同的欄位名稱,底層的語意卻不同。一個跨服務進行推理的代理,會產出彼此矛盾的結論,卻無從察覺這個矛盾其實出在資料裡,而不是出在真實世界裡。這正是為什麼一致的 schema 與發送契約如此重要——沒有強制執行的慣例,只是裝飾。

第四種是「訊號洪流」(signal flood)。團隊做了很激進的插測:每個服務數千個指標、記錄每一條程式碼路徑的日誌、記錄每一次跳躍的追蹤。訊噪比因此崩潰。人類應付這種情況的方式,是忽略大部分內容——這對人類有效,對代理無效。一個被大量訊號淹沒的代理,要嘛變得極度保守(因為信心永遠無法穩定下來,於是什麼都升級),要嘛在彼此矛盾的訊號之間搖擺不定。這裡的紀律是「精選」,而不是「蒐集」:決策級集合裡的每一個訊號,都應該是因為某個具體決策需要它才存在;沒有任何決策會用到的訊號,應該被移出決策集合,有需要時保留供鑑識之用,但完全不呈現給代理。

第五種是「訊號斷崖」(signal cliff),與訊號洪流正好相反,卻同樣危險。團隊做了很激進的裁剪,把代理現在需要用來對新奇狀況進行推理的訊號給砍掉了。在正常負載下,一切看起來都很好;但在一個前所未見的失效模式下,代理沒有任何訊號能區分這次失效與其他任何情況,於是要嘛做出錯誤的反應,要嘛把一切都升級。修正方式,是依照代理「可能需要」做出的決策的整個空間來設計決策級集合,而不只是依照它「今天」會做的決策。為代理尚未遇過的情境保留一小部分訊號,不是浪費,這和一個西洋棋引擎去評估它預期不會遇到的局面,是同樣的投資。

圖 3-3 代理式可觀測性的五種失效模式,每一種都在儀表板上看不見。
警告 對代理式系統而言,最糟的可觀測性失效不是「訊號缺席」,而是「自信地錯誤」的訊號。訊號缺席會讓代理感到不確定,而不確定的代理會升級處理;自信地錯誤的訊號,卻會讓代理果斷地選擇錯誤的行動。前者是可以復原的,後者可能在任何人意識到「問題出在資料上」之前,就先把系統搞垮了。

這五種失效模式,還共享另一項值得明確點出的結構性特質:它們每一個都在儀表板上看不見。靜默腐化不會觸發警報;過時的拓撲不會反映在延遲上;模糊的語意不會自己舉手;訊號洪流與訊號斷崖,在出事之前看起來都像是正常運作。這正是為什麼代理式可觀測性必須自我量測。代理的可靠性,下游於可觀測性層的可靠性;而可觀測性層的可靠性,又下游於「有沒有人決定這值得量測」。第 2.8 節會把這一切轉化為一組任何團隊都能採用的具體代理式 SLI。而起點,是先承認上述五種失效是真實的、常見的,而且無法被那些「當閱聽眾只有人類時管用」的工具偵測到。

2.7 成熟度前提與就緒訊號

正如第 1.3 節所主張的,ARE 是一級一級掙來的。每個等級所需要的可觀測性樣貌都不同,而試圖跳過較低的等級,正會產出第 2.6 節剛剛列舉的那些失效模式。第 4.9 節會完整鋪陳這套成熟度模型;這裡只呈現這套模型在可觀測性面向上的投影,篇幅足以放進本節。

在「L1,反應式」,可觀測性主要以儀表板與警報的形式存在。遙測資料是給人類消費的,schema 在不同服務之間並不一致,擁有權存在於 wiki 裡。這是大多數組織的起點,作為起點,這並沒有什麼不對。在這個等級導入代理,不會產出回報,只會產出意外。

在「L2,已插測」,遙測資料的涵蓋率足以回答大部分的營運問題,其中相當一部分是結構化的。至少對流量最高的服務而言,schema 是存在的;部分擁有權中繼資料被編碼在目錄裡,而不只是被記錄下來;拓撲以某種形式存在,即使部分是推斷出來的。這是代理式實驗開始變得合理的等級,但範圍要嚴格限縮在特定決策上。

在「L3,意圖感知」,代理式 SLI 的紀律已經被採納,遙測資料本身的品質被量測;schema 有版本控管並被強制執行;拓撲圖可查詢且經過驗證;意圖與門檻值並列表達。對最重要的工作流程,團隊已經擁有決策級遙測,即使還沒有涵蓋每一個服務。這是代理開始產生複利效果的等級。

在「L4,有邊界的自主性」,決策級遙測是預設值。新服務在啟動時就依 schema 發送資料;拓撲圖被當成生產產物來對待;擁有權以和遙測資料相同的形式可被查詢;自主邊界被編碼為政策,並依照驅動推理的同一組遙測資料進行評估。代理的行動,從訊號、經過決策、到結果,端到端都是可稽核的。

在「L5,複利」,系統正在對自身的可觀測性進行推理。涵蓋率缺口會被自動偵測;schema 透過審查演進;學習代理(第 15.10 節)會閉合「結果」與「訊號設計」之間的迴圈,讓從未支撐任何決策的訊號被標記為可移除,而在低信心訊號下做出的決策,則會觸發對更好訊號的投資。這是穩定狀態,目前極少組織達到這個層次;這套成熟度模型的存在意義之一,正是描述「如果它們達到了,會是什麼樣子」。

把這些精簡地陳述出來,目的不是為了給團隊打分數,而是讓團隊誠實地定位自己,並知道接下來該投資什麼。每一次等級躍遷,都是具體的可觀測性工作,大多不起眼,大多是 schema、管線、圖,而不是模型與提示詞。一支想從 L2 的可觀測性堆疊得到 L4 回報的團隊,是得不到的。一支想知道自己身在哪個等級的團隊,可以用下一節的代理式 SLI 來找出答案。

2.8 定義並量測「好」的樣貌

如果遙測資料是代理式可靠性運作所在的基質,那麼這個基質的品質,就是生產系統的一項第一等特質,應該被這樣對待。五項具體、每一項都可量測的特質,把實務上的決策級可觀測性,與那些只是自稱擁有這個標籤的遙測資料區分開來。這些特質是可操作的:一支想知道自己是否準備好邁向下一級自主性的團隊,今天就可以量測它們,並誠實地回答。

第一項特質是「訊號完整性」(signal completeness):代理可能需要做出的決策裡,所需訊號齊備且具決策相關性的比例。一個實務上的量測方式:針對代理職責範圍內的每一項具名決策,宣告做出這項決策所需要的訊號,然後量測「所需訊號全數齊備、新鮮、且符合預期 schema」的決策所佔的百分比。對第一級工作流程而言,98% 是合適的目標;對較新或較不關鍵的工作流程,較低的目標也是合理的。不合適的,是完全不量測完整性,然後在決策當下才發現某個必要訊號根本沒被接上。

第二項特質是「語意一致性」(semantic coherence):符合宣告 schema 與一致語意慣例的訊號比例。一個實務上的量測方式:直接從遙測管線本身發出 schema 一致性指標,每一個驗證失敗的訊號都被計入。目標是 99.9% 或更高,剩下的 0.1% 要被擷取下來供調查,而不是被靜默丟棄。一致性低於 99% 的團隊,通常也會發現自己的代理,正對著在不同服務上代表不同意義的訊號,做出自信滿滿的決策——這正是第 2.6 節提到的「模糊語意」失效模式。

第三項特質是「時間保真度」(temporal fidelity):一組訊號配對的時間戳,在依賴它們的決策所需要的容差範圍內彼此一致的比例。一個實務上的量測方式:挑選一組代理會跨越推理的高流量訊號配對(例如一次部署事件與受影響服務的延遲訊號),量測來源時間戳與這些訊號抵達推理層的時間之間的偏移量。這裡的目標值因領域而異;重要的是團隊已經量測並接受了這個偏移量,而不是在一次自信卻錯誤的根因分析中才發現它。

第四項特質是「血緣可得性」(lineage availability):能夠從原始訊號,經過推論與政策評估,一路端到端重建到最終觸發行動的推理過程,佔全部決策的比例。一個實務上的量測方式:對代理採取的每一個行動,都擷取其輸入、政策評估、信心分數與結果,然後抽樣檢驗這些行動的血緣是否真的能被重建。這裡的目標是 100%,因為無法被重建的推理無法被除錯,而無法被除錯的推理,不應該在生產環境裡運行。

第五項特質是「結果歸因」(outcome attribution):代理式行動裡,實際結果(這個行動是降低了衝擊、沒有效果,還是讓情況惡化)能夠與決策當下的預測結果做比對的比例。一個實務上的量測方式:每個行動都宣告一個帶有信心分數的預期結果,系統則量測實現的結果與這個預測之間的差異。跨行動的彙總,就成為代理的校準分數。一個校準良好的代理,具備可量測的「信心對應正確性」映射關係;一個校準不良的代理,則會有一些行動自信滿滿地觸發、卻靜靜地失敗。校準,正是韌性那一章(第三章)與模擬那一章(第十二章)所建立的基礎;沒有結果歸因,這兩章都無法運作它們各自的封閉迴圈。

範例 一個為第一級事故應變設計出來的代理式 SLI:「98% 的事故,在偵測後 30 秒內附上完整的拓撲情境與近期變更情境。」有三件事值得注意:這個 SLI 是可操作的,而不是空泛的期望;它宣告了一個時間窗(30 秒)、一個目標值(98%),以及代理需要的精確輸入(拓撲情境、近期變更情境)。當這個 SLI 燃燒殆盡時,團隊就知道代理正在以降級的輸入運作,此時自主邊界應該收緊,直到這個 SLI 恢復為止。

這五項特質,成為團隊據以運作的代理式 SLI。它們都不需要什麼奇特的工具,需要的是 schema 紀律、在發送時就擷取血緣,以及一份把可觀測性層當成一個「自身可靠性本身就重要」的生產系統來對待的意願。一支採用這五項 SLI 並依此運作的團隊,往往會在一季之內發現,自己原以為擁有的基礎,其實還有一些過去從未被視為基礎的地方需要補強。這個發現,正是重點所在。另一種選擇,是在一次事故的壓力下才發現同樣的缺口——那時,代理正自信滿滿地根據沒有人驗證過的訊號做決策。

當這些 SLI 到位之後,實務上會發生什麼改變,值得點名。隨著團隊精選出決策級的訊號集合,警報量往往會下降,因為警報從「偵測到異常」,轉變為「需要決策,而代理無法獨自做出這個決策」。工程師開始信任自動化評估,即使他們選擇不依此行動,因為這些評估到來時,帶著血緣、信心分數,以及代理推理所仰賴的拓撲與變更情境。事故開始感覺起來是「可以被解釋的」,而不是「一團混亂」。治理的形狀也隨之改變:對話從「這個行動應不應該被允許」,轉變為「這個代理應該在什麼意圖之下運作」。這一切,系統本身並不會顯得多聰明,它感覺起來是平靜且可預測的——這正是良好的可觀測性一直以來、運作良好時的感覺。唯一改變的,是誰在消費它。

還有一個維度值得在這裡點出,因為它正是讓這些 SLI 能夠持久、而不只是一次性稽核的關鍵:可觀測性層的可靠性會隨時間改變,無論是系統本身在成長,還是代理被新增或修改。這些 SLI 不是一個「達成一次就可以宣稱完成」的目標,而是系統的一項持續性特質,用量測正常運行時間相同的方式量測它,用擁有正常運行時間相同的方式擁有它,用審查正常運行時間相同的方式審查它。一支為了某次上線而搭建了 SLI 骨架、之後卻任其失效的團隊,會在條件更難處理的情況下,重新發現這些 SLI 原本要揭露的問題。紀律才是交付物,儀表板只是表面。

2.9 從基礎到行動

為人類設計的可觀測性,與為機器設計的可觀測性,不是同一件事——這正是本章存在的目的,就是要落定這一句話。從決策級遙測、到代理式 SLI、拓撲圖、拓撲代理、五種失效模式,再到成熟度前提,一切都源自這一個轉變。把它內化的團隊,設計出來的東西會與沒有內化的團隊截然不同,而這個差異會隨著後續每一個工作流程不斷複利累積。

核心主軸很簡單:自主性的好壞,取決於它所運作的系統有多清晰。訊號量不等於訊號品質。擁有權、意圖、治理,必須以機器可讀的形式可被觀測,而不是記錄在一份 wiki 上。成熟度會複利累積,而基礎層的紀律,決定了上面每一個等級產出的是回報還是意外。對代理式系統而言,良好的可觀測性感覺起來是平靜的——它降低的不只是反應時間,更是意外本身。把這一點內化的團隊,會停止追逐工具的成熟度,開始設計能與自己一起推理的系統。

本章的下游,是那個把「觀察」轉化為「可靠性」的迴圈:偵測、推理、行動、學習。這個迴圈的第一拍,就運行在本章談的這個可觀測性層之上——訊號,決定了代理感知到什麼;拓撲,決定了代理據以推理的對象;自主邊界(這是治理的工作),決定了代理被允許做什麼;結果歸因,決定了代理學到了什麼。這幾層當可觀測性沒準備好時,會以不同的方式失效;當可觀測性到位時,也會以不同的方式產生複利效果。下一章會正式安裝這個迴圈,為它的各個部分命名,並展示這些部分如何組合在一起,產出一個能透過經驗、而不是靠英雄式救火來自我改善的系統。那一章裡的一切,都無法脫離你在這裡剛剛建立起來的東西而運作。這一章,談的是「贏得從失敗中學習的資格」;下一章,要談的是「如何把那份學習,轉化為自我修復」。