本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
使用 Kubernetes 事件將控制平面的錯誤報告給應用程式
在 Box,我們管理著多個大型 Kubernetes 叢集,它們作為內部平臺即服務(PaaS)為數百個已部署的微服務提供支援。這些微服務中的大多數都是為超過8萬客戶提供 box.com 服務的應用程式。PaaS 團隊還部署了幾個與平臺基礎設施相關的服務作為控制平面。
Box 控制平面的一種用例是公共金鑰基礎設施(PKI)處理。在我們的基礎設施中,需要新 SSL 證書的應用程式還需要觸發控制平面中的一些處理。出於安全原因,我們大多數應用程式不允許生成新的 SSL 證書。控制平面具有不同的安全邊界和網路訪問,因此允許生成證書。
| | | 圖1:PKI 流程框圖 |
如果應用程式需要新證書,應用程式所有者會明確地將自定義資源定義 (CRD) 新增到應用程式的 Kubernetes 配置中 [1]。此 CRD 指定了 SSL 證書的引數:名稱、通用名稱等。控制平面中的一個微服務會監視 CRD 並觸發一些 SSL 證書生成處理 [2]。一旦證書準備就緒,同一個控制平面服務就會將其作為 Kubernetes Secret 傳送到 API 伺服器 [3]。之後,應用程式容器使用 Kubernetes Secret VolumeMounts 訪問其證書 [4]。您可以在我們的 GitHub 示例應用程式中看到此係統的工作演示。
本文的其餘部分將介紹控制平面中這種“觸發”處理中的錯誤場景。特別是,我們特別關注使用者輸入錯誤。由於 SSL 證書引數以 CRD 格式來自應用程式的配置檔案,如果 CRD 規範中存在錯誤,會發生什麼?即使是拼寫錯誤也會導致 SSL 證書建立失敗。錯誤資訊在控制平面中可用,即使根本原因很可能在應用程式的配置檔案中。應用程式所有者無法訪問控制平面的狀態或日誌。
為應用程式所有者提供正確的診斷,以便她能夠糾正錯誤,在大規模部署中成為一個嚴重的生產力問題。Box 迅速遷移到微服務,導致每週都有幾個新部署。許多第一次使用的使用者,他們不瞭解基礎設施的每一個細節,需要成功部署他們的服務並輕鬆排除故障。作為基礎設施的所有者,我們不希望在讀取控制平面日誌中的錯誤並將其傳遞給應用程式所有者時成為瓶頸。如果所有者配置中的某些內容導致其他地方出現錯誤,所有者需要一個完全賦能的診斷。此錯誤資料必須在沒有任何人為干預的情況下自動流動。
經過深思熟慮和實驗,我們發現Kubernetes 事件非常適合自動傳達此類錯誤。如果錯誤資訊放在 Pod 的事件流中,它會顯示在 kubectl describe 的輸出中。即使是初級使用者也可以執行 kubectl describe pod 並獲取錯誤診斷。
我們曾嘗試將控制平面服務的狀態網頁作為 Kubernetes 事件的替代方案。我們確定狀態頁可以在處理 SSL 證書後每次更新,並且應用程式所有者可以探測狀態頁並從中獲取診斷。在初步嘗試狀態頁後,我們發現其效果不如 Kubernetes 事件解決方案。狀態頁成為了應用程式所有者需要學習的新介面,需要記住的新網址,以及在故障排除過程中額外一次上下文切換到不同工具。另一方面,Kubernetes 事件清晰地顯示在 kubectl describe 的輸出中,這很容易被開發人員識別。
這是一個簡化的示例,展示了我們如何使用 Kubernetes 事件跨不同服務進行錯誤報告。我們已經開源了一個代表上述控制平面服務的示例 Go 語言應用程式。它監視 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 並希望更直接地表達您的經驗,請考慮加入 Box 和數十家志同道合的公司所屬的 CNCF 終端使用者社群。
特別感謝 Greg Lyons 和 Mohit Soni 的貢獻。