我入行的時候,已經不用從裸機開始裝作業系統了,但也還沒到今天這種,程式碼推上去就有人幫你把機器變出來的年代。我剛好卡在中間,把虛擬機、雲端、container、serverless 一路走過一遍。回頭看,每一次基礎設施往上長一層抽象,我手上能碰到的東西就少一層,而監控在意的東西,也就跟著往上漂移了一層。
一個痛苦的系統維運苦活
那個時候公司內部要上一套服務,流程是這樣的:先買實體機器回來,裝上 vSphere 把硬體虛擬化,切出虛擬機,再一台一台裝作業系統、裝應用程式。網路面也是自己來,要負載平衡就架 HAProxy 這類 load balancer,把流量分到後面幾台機器上。至於自動化部署,那是另一條故事線,以後再說 — 但這兩件事其實蠻相關的,沒有可信的監控,自動化部署跟閉著眼睛推上線沒什麼兩樣。
這種環境下,監控要顧的東西完全是你自己決定的,因為除了你,沒有別人會顧。
層層堆疊的監控
最先看的,理所當然是作業系統層級的基礎資源:CPU usage、memory free space、disk free space、disk IOPS、network byte 跟 packet count。這一層回答的問題很單純 — 這台機器還有沒有力氣做事。
但機器有力氣,不代表服務還在。所以再往上一層,是看應用程式本身:process 數量是不是正常,有沒有莫名其妙少了一個或多了一堆,個別 process 的 memory 跟 cpu usage 有沒有異常,服務該開的 port 到底有沒有開起來。這一層回答的是 — 東西還在不在。
東西在,也不代表它還能做事。所以再往上是 health check probe,打一個請求進去,看它有沒有正常回應。這一層回答的是 — 它還理不理人。
而最有價值的,其實是再上面那一層:從應用程式本身的 telemetry 裡,找出那些超過某個值就代表要出事的指標,把它挑出來監控。這一層的意義跟前面幾層完全不同 — 前面幾層都是問題已經發生了才會亮燈,而這一層是在實際爆炸之前,先給你一個警告。
最後是整體對外的 end to end 接口,寫一支 script 去模擬使用者真正會做的事,然後看拿回來的結果對不對。前面每一層都綠燈,這一層還是有可能是紅的,而使用者感受到的,永遠是這一層。
難以拿捏的閾值
這些監控真正麻煩的地方,不在於怎麼收數字,而在於閾值要訂在哪裡。
同樣一條 CPU usage 超過 90% 的規則,套在核心數多的機器跟核心數少的機器上,代表的緊急程度完全不同;disk IOPS 也一樣,硬體本身的能力就差很多。所以硬體的規格參數本身,就必須被當成監控的輸入參數,而不是整個機房套用同一組數字。
另一個做法是改看平均值,但這裡有個很容易踩到的陷阱。CPU usage 如果是 by core 取均值,那看起來很合理 — 但假設一台八核心的機器上,實際上只有兩個 process 在跑,這兩個 process 把自己那兩顆核心吃到滿載,剩下六顆閒著,取均值算出來還是很低,儀表板上一片正常。可是對這個服務來說,它已經滿了。這種情況改看 CPU load 會準一些,可是 load 本身也有它的誤解空間,還是得對照 process 數量跟核心數一起看,不能只信一個數字。
再來是把閾值切成 warning 跟 critical 兩級,讓警報本身就帶著緊急程度,而不是所有事情都用同一種急迫感把人挖起來。還有一件很實際的事:瞬間的 spike 非常容易造成誤報,所以最好挑有 flapping detection 的機制,連續幾次都超標才真的發通知 — 半夜為了一根毛刺被叫醒個幾次,就會知道這個功能有多重要。
兩分鐘開機的時代
進到雲端虛擬機的時代,最直接的變化是開機時間。系統起來只要兩分鐘,代表整套 scale out 可以在兩三分鐘內完成。
這件事把警報的性質整個改掉了。以前 CPU 衝高的 alert 是用來叫醒 on call 的人,人醒來之後去開機器;現在這個 alert 直接變成 scale out 的觸發事件,機器自己長出來,人不用醒。維運人員的 on call 時間就是這樣被砍掉一大塊的。
相對地,release 的重點也跟著換了位置。那時候一次發版最重要的事情,是把驗證好的虛擬機轉成 image,讓 auto scaling 系統可以直接拿這份 image 去部署新機器;然後新機器起來之後,還要能自動被加進監控系統裡。這一步如果漏掉,機器的生滅速度就會超過監控的覆蓋速度,而那正是我在〈Unknown Known:當監控的建構速度跟不上資源創建〉裡寫的那筆帳單的來源。
container 的現世
不過維護虛擬機依舊是花人力的地方。機器本身可以自動長出來了,但機器上要跑什麼、裝了哪些版本的套件,這件事沒有被解決 — 應用程式的套件相依,常常就是升級之後系統整個秀逗的原因,而且往往是在最不想處理的時間點秀逗。
然後 container 就出現了。它把應用程式跟它所有的相依套件全部打包在同一個 image 裡,啟動一個 image 的速度可以壓到數秒,維護成本跟套件相依的問題一次省掉一大半,auto scaling 也從此變成主流做法 — 因為擴一台機器要兩分鐘跟擴一個實例要兩秒,是兩種完全不同的營運模式。
監控單位的轉移
監控也就跟著轉向以 container 為單位,而不再是虛擬機,因為同一台虛擬機裡面就可以跑好幾個 container,機器已經不是最小的意義單位了。
container 管理本身也衍生出一門專門的系統,像 Kubernetes、ECS、Cloud Run,把 auto scaling 跟 CPU、memory 的資源管理做成一層抽象,然後把虛擬機層級的 CPU、memory、disk scaling,跟 container 層級的 scaling 分離開來:監控總體資源來決定虛擬機要不要擴,而 container 層級則有自己的 HPA(horizontal pod autoscaling),依照個別 container、也就是個別應用的實際負載去擴展。同一套叢集裡,兩層各擴各的。
也正因為 container 管理被抽象化了,可以直接透過 API 拿到 container 的實例清單,才讓這一層的監控變得可行。Prometheus 就是活在這一層的東西:透過 metric server 取得 container 的資源 metrics,並且定義出一套統一的 telemetry 輸出格式,讓收集這件事變得標準化。收回來的數值可以用 query 搭配 function 去運算,再從運算結果訂出 warning 跟 critical 的閾值,接到通知上,或者直接接到 scale 的條件上。
SLI、SLO、SLA 的誕生
再往下走,連 instance 層級的管理都嫌麻煩了。於是雲端開發商把這套已經成熟的技術再包一層,變成 managed service,像 ECS 跟 EKS 的 Fargate、Cloud Run,讓開發者盡量往 application layer 靠,專注在應用本身。
這也正是 SRE 定義 SLI、SLO、SLA 這套方針的來源。當底下的機器都不歸你管,你能定義、也該定義的,就只剩服務本身的健康程度:先訂出 SLI(service level indicator),像是 API latency、API response status code;再訂出想要保持的目標 SLO(service level objective),像是平均回應時間要在 500ms 以內;最後才是把這樣的服務品質寫進合約裡,變成 SLA(service level agreement),依照日、月、年的平均 downtime 換算出要對外承諾的百分比 — 像 99.99%,一年系統 downtime 要低於 53 分鐘,超過就會有補償條款。
監控的角色到這裡也變了。它不再是幫你盯著機器有沒有事,而是持續證明這份對外的承諾有沒有守住。
Lambda 與使用者體感
到了更後期,連 container 層級的維護都嫌麻煩,於是無狀態的服務可以直接用 Lambda function 來運作,AWS 自己針對流量去擴容,開發者連機器的概念都拿不到了。這時候剩下值得關心的,就只有使用者實際的使用體驗。SLI 跟 SLO 依舊是衡量的標準作法,只是這些標準會盡量地貼近使用者的體感,像 Apdex 這類把回應時間換算成滿意度的指標。
監控追的是掌控權
從 vSphere 上那台要自己裝作業系統的虛擬機,到今天推上去就會自己擴容的 Lambda,每個時代都把上一個時代最麻煩的那件事抽象掉:機器的生滅交給 auto scaling,套件相依交給 container image,資源調度交給 orchestration,節點維護交給 managed service。
而監控的重心,就一路跟著開發者「還碰得到什麼」往上飄。以前要盯 disk IOPS,是因為那顆硬碟就在你面前,而且只有你會去看它;現在不盯了,不是因為它不重要,是因為它已經不歸你管了。等到什麼都不歸你管的那一天,monitor 剩下的也就只有一件事:使用者今天用得好不好。