跳轉到

災難管理

災難管理和災難處理的差異在於: 處理災難代表減輕衝擊或(且)把服務恢復到以前的狀態,通常代表的是事發時的急救方案; 而災難管理代表以有效的方式協調團隊的工作,並確保溝通(包含對外)管道的暢通。

很多事情都會有意外事件發生,不管是 森林野火火車翻覆、 食品衛生新聞等等。 在面對緊急事件時,最好的處理辦法就是事前練習並規劃好有系統的指南。 即使發生了沒遇過的狀況,透過 提前的分工工作的結構化, 將會讓處理人員有個依託並較為冷靜面對。

分工

分工基本上分為兩大部分:溝通處理。 透過主要指揮官(Incident Commander, IC)、 溝通領導(Communications Lead, CL)和處理領導(Operations Lead, Ops Lead, OL), 把相關工作劃分好。

IC 的工作保括:

  • 確保命令的單一性,一個人不會聽命於 A 又同時聽命於 B;
  • 分配好各個角色(包含 CL 和 OL)且角色功能定義明確,並預設所有角色還未指派;
  • 所做的所有事情都做好記錄,包括除錯、介入行為等等(幫助未來思考可優化的項目);
  • 儘早並定期的宣告事件狀況,並確保溝通管道的暢通。

如果事件夠大,IC 可能會把溝通的任務指派給一個 CL,除此之外,他可能還會有這些職責:

  • 統一的對外窗口,回答所有關切性問題;
  • 定期整理並匯報事件給高層、全公司等等。

OL 則是專注在:

  • 問題的查找;
  • 急救方案的判定;
  • 協助找出根治的方法。

儘早宣告事件

Google Home(以下簡稱 GH)是一個智能管家,當你需要請他處理事情時,例如打開客廳電燈, 需要先觸發關鍵字(hotword),例如 OK Google。 每個人在說這個關鍵字時,可能的語調和口氣都不一樣,故而使用者需要提供訓練素材來幫助辨識。 這個素材會被存放在雲端,但是訓練結果的參數會放在客戶的裝置上, 故而 GH 會需要定期去和 server 取得訓練結果。

在一次版本更新(v1.88)中,裡面有一個錯誤會導致 GH 去跟 server 要的次數變成一般的 50 倍。 在 5 月 22 號禮拜一中, 一個待命小組成為 Jasper 觀察到每秒存取量(queries per second, QPS)變高了, 故而暫停新版本的推廣,讓他停在 25% 的推廣比例(也就是線上有四分之一的用戶使用新版本)。 Jasper 開票追蹤這個異常狀態,並備註短時間的存取量來到一般情況的 50 倍。

開發者 Melinda 把這個問題連結到之前的某一張舊的票, 這個舊的票是在說明 GH 每次刷新登入狀態都會不被預期的去存取一次關鍵字的訓練結果, 而這個錯誤預期會在這次版本更新(v1.88)中被解決, 所以最終認為這個異常流量應該會是暫時性的問題,所以把流量限制拉大,並提高推廣程度。

當服務在 5 月 31 號禮拜三被推廣到 50% 時,他們發現這問題的根因並不是預期的舊錯誤, 因為提高推廣程度應該讓這個流量下降,但是卻上升了。 同一天,客服開始收到一些回報,說明 GH 在啟動時收到錯誤訊息, 而這個錯誤訊息經過開發團隊確認正是觸發限流導致的錯誤狀態。 他們再一次的把流量限制拉大,紓緩了錯誤發生,並進一步追蹤這個錯誤原因。 雖然這個錯誤是後端開發看到的錯誤,但是因為這是前端開發製造的錯誤, 所以在追蹤初期朝著的方向完全是錯誤的,也加上這個錯誤並沒有被提高等級到事件(incident), 所以前後端開發者的溝通仍有限。

開發著開始嘗試在新版本中加上一些日誌,來幫助排查問題,但是因為流量限制開大的關係, 沒有任何使用者回報限流的錯誤狀態,故而決定進一步提高推廣度。 當他們在 6 月 3 號禮拜六把推廣度提高到 100% 時,客服開始收到部分的錯誤回報, 儘管收到錯誤回報,因為在週末,開發團隊仍沒有把這錯誤提升到事件, 並依照正常處理錯誤的流程,在開票追蹤系統討論和處理。

到了隔天週日,由於錯誤回報持續上升,開發團隊終於把這個錯誤提升到最高層級, 由於錯誤率上升,待命小組請求 SRE 團隊把節點數拉大(提升負載能力)並將流量導引到新的節點, 但因為是限流錯誤,所以即使拉高負載,新的節點仍會拒絕處理高併發的單一使用者請求, 故而把問題更聚焦在限流的原因上面。 最終專案管理員再一次請求提高限流後,問題被舒緩,同時開發者也在集中溝通的情況下暸解彼此狀況, 並進一步排查出根因。

Google Home 事件的事後分析

因為開發者在週末仍致力於處理這個問題,所以即使在週末,這問題仍被解決了, 但這並不是一個好的處理問題方式,因為最終我們都希望公司成為一個能替員工平衡工作和生活的地方。

第一個,不要在週末把推廣程度拉高。 再來,處理問題第一步通常都是避免錯誤繼續擴散, 當錯誤狀況被紓緩時,應該要專注於排查問題的根因,而不是繼續推廣新版本。 最後,當問題排查不清時,應該將錯誤提升到事件,並召集相關人士一起來處理,這樣可以避免:

  • 溝通壁壘,缺陷追蹤系統(e.g. JIRA)並不是良好的溝通橋樑;
  • 不同角度的看待事情,會加速錯誤的排查;
  • 讓不同部門的同仁面對線上錯誤有進一步的心理準備。

集中化的討論是解決事件的核心,例如召集一群人坐在一個會議室或者一場線上會議,並即時的討論和面對。

先可用再找根因

Kubernetes 是一個容器管理服務,Google 有在雲端上提供託管服務,稱其為 Google Kubernetes Engine, GKE。 GKE 在啟動之初會先去獲得服務需要的鏡像檔,例如管理容器需要的排程器、負載平衡器等等。 但因為 Kubernetes 是一個開源服務,所以有些新的依賴會在版本更新或特定情況下被新增進來。

早上 6:41 時,Zara 開始注意到 Europe-West 區域啟動 GKE 服務的錯誤率過高的告警, 並在 7:06 開始宣告事件,身為宣告事件者,Zara 就變成了預設的事件指揮官。 Zara 把相關情況告訴給 Rohit 並指派其負責對外(GKE 的使用者)溝通的窗口, Rohit 則在 7:24 開始告知當地區域的客戶目前無法啟動服務,但是現有的服務仍然可以運行。

在 8:20 前,團隊發現這樣一個 log:

error: failed to run Kubelet: cannot create certificate signing request: Post
https://192.0.2.53/apis/certificates.k8s.io/v1beta1/certificatesigningrequests

團隊開始嘗試驗證憑證的相關操作,各自獨立時都能運作,但是當把服務整合在一起時,就是會錯。 Zara 於是把處理進度整理起來,並在 8:22 開始向相關溝通系統通報,請求大家的協助。 到了 8:45 資深 SRE Il-Seong 開始進班,他首先確認了當日更新和告警之間的關係, 在確認了兩者之間無關係之後,開始把相關資訊整合進可以共編的文件中,並建議 Zara 讓基礎設施、 雲端網路和計算資源的相關團隊都加進事件處理團隊中,並逐一排除。

隨著事件發生得越來越久,進入支援的團隊越來越多,溝通開始變成一個挑戰。 這個事件的指揮官也在 10:00 時從 Zara 轉移到資深的 Il-Seong, 於是 Il-Seong 開始把事件的經過和問題都整理和結構化。 Zara 則在此時成為處理領導(OL),溝通領導(CL)則指派給 Herais, 並在當下寄送「全體動員」的信箱給所有 GKE 相關的工程。

事件到此時有了一些清晰的徵狀:

  • GKE 叢集在建立時,其他節點嘗試註冊近主控台時會失敗;
  • 錯誤訊息告知憑證簽發的機制有錯;
  • 所有在歐洲地區的叢集都會建立失敗,其他則地區則無;
  • 沒有其他 GCP 服務有任何網路和限流的問題。

在收到全體動員的通知後,有一個 GKE 資安團隊同仁 Puanani 加入排查, 並注意到憑證簽發者沒辦法啟動起來。 每次從外部容器註冊商(DockerHub)拉取的鏡像檔都是被破壞的, 並有其他同仁證實這狀況只在歐洲發生。 這時,時間是 9:56,事件已經發生了三個小時。

這時指揮官 Il-Seong 開始分派兩個並行處理方式給不同工程,一個是修復 DockerHub 的鏡像檔, 一個是重新設定叢集讓他從自己管理的容器註冊商(GCR)拉取鏡像檔。

第一個選項的困難在於這是外部服務提供商,要即時的改動是有天生的困難的。 第二個選項代表著需要重新編譯執行檔,這個過程必須耗費一個小時。 到了 10:59,當團隊完成了 90% 的編譯時,他們發現其他設定沒有改到 GCR,於是重新編譯。 這時,有個工程 Tugay 想到一個絕妙的方法:我們去 hook 從 DockerHub 拉取鏡像檔的請求, 並在回應中放上正確的鏡像檔,而 GCR 本來就有設計好這個功能了!

在和 GCR 工程確認好之後,他們在每次拉取時加上一個選項: --registry-mirror=https://mirror.gcr.io, 這時,11:29,他們注意到,拉取鏡像檔的過程本來就有這個選項了! 換句話說,有問題的鏡像檔在 GCR! 到了 11:59 他們修復了 GCR 上錯誤的鏡像檔,並且在 12:11 回報所有建立叢集的狀況都已不在了。 此時,這個存在 6 個小時又 40 分鐘的事件終於被回報為處理完畢,後續在事件報告中, 6 個團隊、41 個獨立人員出現在這個 26000 報告中,並且在報告中提到 28 個事件行為點。

GKE 無法建制新叢集的事後分析

GKE on-call 成員 Zara 快速發現問題並且指派負責顧客溝通的角色,讓大家能快速意識到問題的嚴重性。

GKE 這個服務本身擁有過高的複雜性和依賴性,並且輸出的日誌並不足以快速找到問題癥結點, 並且花了很長的時間在不是問題的 DockerHub 上面。 事件發生之初,指揮官並未及早撰寫結構化的事件回報文件,這個文件可以讓後續協助的人快速進入狀況, 並且能夠讓很多討論出來的解法,遺忘在激烈討論之中。 Zara 在指揮事件處理時, 應該更好的統整多個工程師,不要讓其各自忙各自的,造成過多的重功和溝通壁壘。

最後,處理事件有個大原則:先想辦法讓其可以運作,再接著找根因。 當 GKE 發生狀況時,工程可以先退版到上一次的版本, 或者當狀況是發生在局部地區時,可以調整負載平衡器到其他區域,讓使用者仍能正常建立叢集。 是的,當發生任何異動時,都有可能讓狀況更糟,但是這些處理方法都可以快速地被執行, 而不是六個小時後才能生效。

回到事件發生時,早上 7:00 發現使用者被影響、10:00 發現壞掉的鏡像檔、 11:00 開始編譯調整設定後的執行檔、12:00 修復 GCR 損壞的鏡像檔。 我們其實可以在 10:00 發現症狀時,就快速把所有鏡像檔都復原到可以正常運作時的版本, 而不是推一個新的版本。

以這個情況為例,其實可以在事件發生前,就準備好相關急救設施,例如快速復原版本、流量導引等等。 建立急救設施最好的時機就是在事件發生前, 而急救設施的需求通常可以在各個事後析誤的文件中找到一些蛛絲馬跡。 再強調一次,在處理事件時,應該優先考慮急救措施:先想辦法讓其可以運作,再接著找根因。 換句話說,事件處理的流程通常是:

  • 發現並著手處理事件;
  • 進行急救處理;
  • 根因的查找;
  • 解決根因並撰寫事後析誤文件。

輕重緩急的判定

電網事件通常代表突然斷電,例如電擊、生物侵害等等,為了預防服務的失能,通常會加上一些保險, 不斷電系統(uninterruptible power supply, UPS)就是為此而生的。 在斷電之後,自主式發電機運作之前,會有個空窗期,不斷電系統會在這段時間支援足夠的電力。 Google 在比利時的資料中心就有這樣的系統。

2015 年時,該資料中心因為電擊導致停電,不斷電系統這時開始接手電力的輸出, 但是接下來的兩分鐘內,又來了三次電擊(含第一次總共四次)。 即使如此,運算服務(Google Compute Engine, GCE)的不斷電系統卻仍正常運作, 但第三和第四次的電擊卻導致磁碟(disk)的不斷電系統的失能,並讓磁碟停止運作。 磁碟的停止運作,開始讓 GCE 上的虛擬機(virtual machine, VM)出現讀寫的錯誤。

GCE 的 SRE 工程把資訊傳達給負責磁碟的 SRE 工程,並讓負責磁碟的 SRE 成為本次事件的指揮官, 因為其擁有針對磁碟和主機之間的狀況最清晰的概觀,並總結了事件狀況:

  • 每個有和失能磁碟掛勾的運算主機都需要重啟;
  • 在等待重啟前,讀寫的錯誤會一直發生;
  • 如果該運算主機裡面有客戶正在使用的 VM,就需要先將其「搬遷」之其他主機。

換句話說,處理人員就有幾項工作要處理:把動力輸出從不斷電系統和自主發電機切回電網提供的電力; 重啟所有目前沒有任何 VM 的主機;把 VM 搬進那些已經重啟過的主機。 前面兩項工作已經演練、實際發生過很多次,也有完整的文件參考,但是第三項卻是首次發生的。 為此,指揮官指派專門一個團隊去討論、實作這個工項,並且即時監控其運作狀態。 並且在事發當下,指派另一個小組建構相關工具,讓團隊可以快速而準確得執行搬遷作業。

電擊導致磁碟失能的事後分析

指揮官很有經驗的把較多的資源放在事發當下最棘手的問題,其餘的常見問題則指派相關團隊, 並讓其定時定點回報後,就關注在搬遷 VM 的工作上。 也因為搬遷 VM 是需要 GCE(知道目前各個 VM 的狀況)和磁碟團隊(知道目前主機狀況)之間的溝通, 所以指派的團隊需要確保能在這兩者之中進行有效溝通的。

溝通

PagerDuty 是一個提供事件處理平台的 SaaS 服務商, 它也在其官網提供很多事件處理的素材。

PagerDuty 早期只有一個指揮官並負責全公司的服務, 隨著公司成長到擁有數十個團隊時,他們的事件處理的機制也跟著調整。 事實上,每隔數月,他們就會審視自己的事件處理機制,確保這個流程是有明確目的性且跟隨著商務邏輯的。 例如使用者無法登入和無法註冊的處理方式。

一般來說,小型事件是一個待命工程就能處理的,但是當出現重大事件時,往往需要多人處理, 我們不該讓一個待命的工程獨自面對高壓的情況,緊急情況的溝通在這之中扮演重要的角色。 有幾個方法可以幫助團隊的溝通:

  • 練習緊急事件處理, 每週五 PagerDuty 會在線上環境(但可控的範圍內)實現一些錯誤狀況,讓工程練習事件的處理。 在這過程中,會輪替指揮官(IC),這些過程都可以幫助新人進入狀況, 同時也能加速待命人員的換血(定期輪替待命人員可以舒緩工作壓力)。
  • 部分練習限制處理時間, 練習沒辦法完全模擬真實狀況,但是透過時限來增加實際狀況的緊張感。
  • 從歷史中學習,當然,這個前提是以前的事件都有被好好紀錄(包括訊息和通話)。 在練習的過程中有幾個方向:如何加速緊急處置方案的實現、如何避免災難。

另外需要注意的是,長時間的災難處理是需要人員輪替的, 例如 PagerDuty 2017 年在半夜發生長達十小時失能的事件。 他們每四個小時就輪替一次處理事件的人員,這樣除了讓人員得到充分休息之外, 也可以確保事件被完整紀錄(否則如何做交接?)以及讓接手的人員提出一些新的想法。

框架

建立一個通用框架除了幫助溝通之外,也可以找到哪些部分對公司來說是最重要的,例如:

  • 待命人員指派相關人員和升級事件;
  • 優先實施緊急處置;
  • IC、CL 和 OL 的指派。

框架的價值也在於它提供了 理論上的事件處理方式, Google 發現如果可以把理論和實務狀況和處理方式進行連結,新進人員會更容易進入狀況。 除此之外,實務的操作練習、歷來事件的學習、事後分析處理方式的好壞在學習過程中仍是非常重要的一塊。

決定好溝通管道,不管是 Line、Teams 或線上會議,只要大家熟悉就好。 沒有人希望在事發當下還要決定用什麼作溝通。 除此之外,你也需要把相關服務的處理人員都列好。 還記得先可用再找根因中,Google 人員發送了一個「全體動員」的信件嗎? 這封信的前提就是已經準備好所有服務的聯絡人員。

總而言之,事件處理框架重點就是:明確溝通管道、誰該接收到訊息、事件發生時誰要負責什麼。

總結

練習,練習,還是練習。 試著自動化模擬事件發生,透過 chaos monkey、歷史事件,都是好的方法。 Google 會執行公司層級的事件練習(稱為 Disaster Recovery Testing, or DiRT), 都是再再的強調練習的重要性。

處理事件時接觸到的人越多,代表事件的等級越高,試著建立結構化訊息,幫助大家快速進入狀況。 當然,這些東西一定都是事件發生前就準備好且經過實際使用練習的, 這些練習都會幫助我們建立肌肉記憶,避免在腦袋空白的情況下,讓時間白白流失。

DiRT 所披露的一些問題

在 Google 開始實施 DiRT 在一些小系統上之後,他們嘗試把測試放到更大規模:公司層級。

他們嘗試讓一個區域的辦公室和資料中心整個斷網和停電,模擬大地震的情況。 理想上當然會有個備援接手這些失能的服務,但卻發現一些意想不到的狀況,例如:

  • 開始轉移時發現所有轉移都需要先和來源端(就是被斷網的辦公室)進行溝通;
  • 進行其他操作時,因為驗證系統的依賴中斷,導致失效;
  • 內部業務功能的審批追蹤系統就建置於該辦公司;
  • 當工程師放棄準備去吃晚餐時,咖啡廳被 DoS 攻擊。

換句話說,不只是對外服務,一些用於生產、內部系統, 包括人力資源、財務、資安和各種設施都需要被測試。

除此之外,早期測試,只有一個人在茫茫文件中成功找到緊急時的通訊辦法(打特殊電話), 隨著之後的練習,超過 100 個人能夠找到這個文件,但是卻發現這個通訊方法最多只能容納 40 人。 並且當有人把該電話設定為待機狀態時,會導致電話中的大家被迫享受優美的待機音樂, 故而需要設計出把人踢走的功能。

最後一個有趣例子是,在模擬長期停電時,為了滿足發電機的供電,需要申請購買大量柴油燃料。 這個申請走的是緊急支出流程,但由於找不到這個緊急支出的相關文件,最終該測試的參與者們, 找到一個豪氣萬千的同事願意把自己的信用卡交出來,並刷上六位數的費用。 我們都知道建立服務需要撰寫文檔讓人使用,但是我們要怎麼知道這份文檔堪不堪用? 唯一的方法就是通過測試和練習。

當然,一個公司文化在這之中扮演很重要的角色,願意接受失敗並將其作為學習手段的組織, 對於使團隊發現和解決系統性問題,起到一個很大的作用。 最後提醒一下,一個集中配置的指揮中心能夠了解和監控任何時候正在進行的所有測試, 這使得 DiRT 成為一個更安全的測試環境。 當發生無法預料的情況時, 指揮中心的團隊(主要由各個領域的技術專家組成)會介入恢復測試或修復問題。