這篇文章已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用 Kubernetes 事件將控制平面錯誤回報給應用程式

Box,我們管理數個大規模 Kubernetes 叢集,這些叢集作為內部平台即服務 (PaaS),為數百個已部署的微服務提供服務。這些微服務中的大多數是為超過 80,000 名客戶提供 box.com 服務的應用程式。PaaS 團隊還部署了多個與平台基礎架構相關的服務,作為控制平面

Box 控制平面的一個用例是公鑰基礎建設PKI)處理。在我們的基礎架構中,需要新 SSL 憑證的應用程式也需要觸發控制平面中的一些處理。由於安全原因,我們的大多數應用程式都不允許產生新的 SSL 憑證。控制平面具有不同的安全邊界和網路存取權限,因此被允許產生憑證。

| | | 圖 1:PKI 流程的方塊圖 |

如果應用程式需要新憑證,則應用程式擁有者會明確地將 自訂資源定義 (CRD) 新增至應用程式的 Kubernetes 設定 [1]。此 CRD 指定 SSL 憑證的參數:名稱、通用名稱和其他。控制平面中的微服務監看 CRD,並觸發 SSL 憑證產生的某些處理 [2]。憑證準備就緒後,相同的控制平面服務會將其傳送至 Kubernetes Secret [3] 中的 API 伺服器。之後,應用程式容器使用 Kubernetes Secret VolumeMounts [4] 存取其憑證。您可以在 GitHub 上的範例應用程式中看到此系統的運作示範。

本文的其餘部分涵蓋控制平面中此「觸發式」處理中的錯誤情境。特別是,我們特別關注使用者輸入錯誤。由於 SSL 憑證參數來自 CRD 格式的應用程式設定檔,因此如果該 CRD 規格中存在錯誤,應該會發生什麼情況?即使是錯字也會導致 SSL 憑證建立失敗。錯誤資訊在控制平面中可用,即使根本原因很可能在應用程式設定檔內部。應用程式擁有者無法存取控制平面的狀態或日誌。

向應用程式擁有者提供正確的診斷,以便她可以修復錯誤,這在大規模情況下變成嚴重的生產力問題。Box 快速遷移到微服務導致每週都有數個新的部署。許多不知道基礎架構每個細節的首次使用者,需要成功部署他們的服務並輕鬆排除問題。作為基礎架構的擁有者,我們不希望成為瓶頸,同時從控制平面日誌讀取錯誤並將其傳遞給應用程式擁有者。如果擁有者的組態中的某些內容在其他地方引起錯誤,則擁有者需要完全授權的診斷。此錯誤資料必須自動流動,而無需任何人工介入。

經過相當多的思考和實驗,我們發現 Kubernetes 事件 非常適合自動傳達這些種類的錯誤。如果錯誤資訊放置在 Pod 的事件流中,它會顯示在 kubectl describe 輸出中。即使是初學者使用者也可以執行 kubectl describe pod 並獲得錯誤診斷。

我們實驗了控制平面服務的狀態網頁,作為 Kubernetes 事件的替代方案。我們確定狀態網頁可以在每次處理 SSL 憑證後更新,並且應用程式擁有者可以探測狀態網頁並從那裡獲得診斷。在最初實驗狀態網頁之後,我們已經看到這不如 Kubernetes 事件解決方案有效。狀態網頁成為應用程式擁有者要學習的新介面、要記住的新網址,以及在故障排除工作期間要切換到不同工具的另一個上下文。另一方面,Kubernetes 事件乾淨地顯示在 kubectl describe 輸出中,這很容易被開發人員識別。

以下是一個簡化的範例,展示我們如何使用 Kubernetes 事件進行跨不同服務的錯誤報告。我們已經開源了一個 範例 golang 應用程式,代表先前提及的控制平面服務。它監看 CRD 的變更並執行輸入參數檢查。如果發現錯誤,則會產生 Kubernetes 事件,並且相關 Pod 的事件流會更新。

範例應用程式執行此 程式碼 來設定 Kubernetes 事件產生

// eventRecorder returns an EventRecorder type that can be  
// used to post Events to different object's lifecycles.  
func eventRecorder(  
   kubeClient \*kubernetes.Clientset) (record.EventRecorder, error) {  
   eventBroadcaster := record.NewBroadcaster()  
   eventBroadcaster.StartLogging(glog.Infof)  
   eventBroadcaster.StartRecordingToSink(  
      &typedcorev1.EventSinkImpl{  
         Interface: kubeClient.CoreV1().Events("")})  
   recorder := eventBroadcaster.NewRecorder(  
      scheme.Scheme,  
      v1.EventSource{Component: "controlplane"})  
   return recorder, nil  
}

在一次性設定之後,以下 程式碼 產生與 Pod 相關的事件

ref, err := reference.GetReference(scheme.Scheme, &pod)  
if err != nil {  
   glog.Fatalf("Could not get reference for pod %v: %v\n",  
      pod.Name, err)  
}  
recorder.Event(ref, v1.EventTypeWarning, "pki ServiceName error",  
   fmt.Sprintf("ServiceName: %s in pki: %s is not found in"+  
      " allowedNames: %s", pki.Spec.ServiceName, pki.Name,  
      allowedNames))

可以透過執行範例應用程式來理解更多實作細節。

如先前所述,以下是應用程式擁有者的相關 kubectl describe 輸出。

Events:  
  FirstSeen   LastSeen   Count   From         SubObjectPath   Type      Reason         Message  
  ---------   --------   -----   ----         -------------   --------   ------     
  ....  
  1d      1m      24   controlplane            Warning      pki ServiceName error   ServiceName: appp1 in pki: app1-pki is not found in allowedNames: [app1 app2]  
  ....  

我們已經示範了 Kubernetes 事件的一個實際用例。在組態錯誤的情況下,對程式設計人員的自動化回饋顯著改進了我們的故障排除工作。未來,我們計劃在類似的用例下在各種其他應用程式中使用 Kubernetes 事件。最近建立的 sample-controller 範例也在類似的情境中使用了 Kubernetes 事件。很高興看到有更多範例應用程式來引導社群。我們很高興繼續探索事件和 Kubernetes API 的其餘部分的其他用例,以使我們的工程師更容易進行開發。

如果您有想要分享的 Kubernetes 經驗,請提交您的故事。如果您在您的組織中使用 Kubernetes 並且想要更直接地表達您的經驗,請考慮加入 CNCF 終端使用者社群,Box 和數十家志同道合的公司也是其中的一部分。

特別感謝 Greg Lyons 和 Mohit Soni 的貢獻。