Kubernetes 上執行有狀態應用的最佳實踐

作者 | Gilad David Maayan

譯者 | 張衛濱

策劃 | 丁曉昀

在容器化的早期階段,它們被設定為一種執行無狀態應用的機制。

在過去的幾年間,社群意識到在容器中執行有狀態工作負載的價值,而且像 Kubernetes 這樣的編排器引入了必要的特性。

Kubernetes 提供了持久化卷(Persistent Volume,PV)架構以及像 StatefulSet 和 DaemonSet 這樣的控制器,它們能夠讓我們建立有狀態工作負載的 Pod,即便是在 Kubernetes 擴充套件和供應資源的時候,這些工作負載也能保持執行,並且能夠確保現有的客戶端連線不會中斷。

這種方式雖然遠遠談不當簡單直接,但是能夠行之有效,任何採用 Kubernetes 作為執行時基礎設施的人都必須熟悉它。

在本文中,我將會闡述在 Kubernetes 中執行有狀態應用的重要性,給出執行有狀態應用的三個可選方案,並詳細描述它們的執行機制。

1

什麼是有狀態應用?

有狀態應用允許使用者重複返回該應用並恢復之前的操作,比如電子郵件或者網上銀行應用。有狀態的應用會記錄之前事務的上下文,這些上下文可能會對當前或未來事務產生影響。所以,有狀態的應用必須確保每個使用者始終訪問同一個應用程式例項,或者有某種在例項之間同步資料的機制。

有狀態程序的優點是,應用程式可以儲存每個事務的歷史和上下文,跟蹤最近的活動、配置偏好和視窗位置等元素,並允許使用者恢復事務。有狀態的事務的表現就像始終和同一臺伺服器進行對話一樣。

如今,大多數的應用都是有狀態的。容器和微服務等技術的進步推動了基於雲的應用開發,然而由於它們的動態性,使得有狀態程序的管理更具挑戰性。

2

容器化有狀態應用的使用場景

在容器上執行有狀態應用的需求正變得越來越大。容器化的應用可以簡化複雜環境中的部署和運維,如邊緣雲計算和混合雲環境。狀態性對於持續整合和持續交付(CI/CD)也很重要,因為 CI/CD 流水線必須保持狀態,以確保從開發到生產環境部署過程的連貫性。

容器化有狀態應用的常見使用場景包括:

機器學習運維(MLOps)

:在 MLOps 環境中,容器需要是有狀態的,這樣做有多個目的,包括共享推理和訓練的結果以及訓練 job 的檢查點。

AI 和資料分析處理

:資料處理和機器學習框架,如 Apache Spark、Hadoop、Kubeflow、Tensorflow 和 PyTorch,對容器化的支援在不斷增強。這些平臺必須反覆處理大量的資料,需要有保持狀態的機制。

訊息系統和資料庫

:你可能更喜歡使用本地快閃記憶體來獲取低延遲性,但是這會使得容器很難在不同的 worker 節點間進行移動,因為資料會持久化到節點上。高效能共享儲存對各種應用都很重要,比如單例項資料庫(如 MySQL)、記憶體資料庫(如 Redis)、NoSQL 資料庫(如 MongoDB)、業務關鍵型的應用(如 SAP 或 Oracle)以及訊息應用(如 Kafka)。

3

在 Kubernetes 中實現有狀態部署的三個可選方案

在 Kubernetes 叢集中執行有狀態的工作負載主要有三個可選方案,即在叢集之外執行、作為叢集旁的雲服務或者在 Kubernetes 叢集中執行。

在 Kubernetes 之外執行有狀態的應用

一種常見的方式就是在 VM 或裸機中執行有狀態的應用,並讓 Kubernetes 中的資源與之進行通訊。從叢集中 pod 的角度來看,有狀態應用會作為一個外部的整合。

這種方式的

好處

在於,它允許我們按照原樣執行現有的有狀態應用,無需重構或重新架構。如果應用能夠根據 Kubernetes 叢集中工作負載的需要進行擴充套件,那麼我們就不需要 Kubernetes 複雜的自動擴充套件和資源供應機制。

這種方式的

缺點

在於,在叢集外維護非 Kubernetes 的資源,這就需要我們有某種方式來監控程序、執行配置管理,併為應用執行負載均衡和服務發現。我們在 Kubernetes 之外搭建了一個並行的軟體工作流,所以基本是在進行重複的工作。

以雲服務的形式執行有狀態的工作負載

第二種同樣常見的方法是將有狀態的應用作為託管雲服務來執行。例如,如果你需要在一個容器化的應用中執行一個 SQL 資料庫,並且應用在 AWS 上執行,那麼你可以使用 Amazon 的 Relational Database Service(RDS)。託管資料庫往往是可以進行彈性擴充套件的,所以隨著 Kubernetes 資源的擴充套件,有狀態的服務也可以適應不斷增加的需求。

這種方式的

好處

在於,它的搭建過程非常容易,有狀態工作負載的持續維護應該會非常簡單,而且你使用的是一個與 Kubernetes 相容的雲原生資源。

這種方式的

缺點

在於,託管雲服務是有成本的,它的定製能力通常會比較有限,並且不一定能提供你所需要的效能或延遲屬性。同時,採取這種方式,會讓你鎖定到特定雲供應商上。

在 Kubernetes 中執行有狀態的工作負載

這種方式最難實現,但是從長遠來看,它會帶給我們最大的靈活性和運維效率。我們可以使用 Kubernetes 提供的兩個原生控制器來執行有狀態的應用,即 StatefulSet 和 DaemonSet。

StatefulSet 控制器

StatefulSet 是一個 Kubernetes 的控制器,它管理具有唯一身份標識的多個 pod,並且它們是不能互相交換的(這與常規的 Kubernetes Deployment 有所差異,在 Deployment 中,pod 是無狀態的,可以根據需要經常銷燬和重建)。

在 StatefulSet 中,每個 pod 都有一個持久化的、唯一的 ID。每個 pod 可以有自己的持久化儲存卷。如果 Kubernetes 需要擴充套件和伸縮的話,它會保持與外部使用者或者叢集中其他應用的現有連線。

DaemonSet 控制器

DaemonSet 是一個 pod,Kubernetes 能夠確保它會在叢集的所有節點,或者透過選擇器定義的特定節點子集上執行。每當符合條件的節點被新增到叢集中,這個 pod 都會在它上面啟動。

對於需要以後臺程序的形式執行的有狀態應用來說,DaemonSet 非常有用,比如監控或日誌聚合應用。一般來講,DaemonSets 的靈活性較差,但是比 StatefulSet 更易於管理,資源的使用也更加可預測。

4

Kubernetes 中的持久化儲存

卷(volume)是一個 Kubernetes 實體,它提供了持久化的儲存。Pod 中所有的容器可以共享卷。我們可以藉助持久化卷,讓執行在同一個 pod 中的多個服務使用同一個掛載的檔案系統。

非持久化儲存卷

在 Kubernetes 中,要授予容器對持久化儲存的訪問權,我們需要宣告所需的卷以及所需的位置,以便於在容器的檔案系統中掛載該卷。

Kubernetes 中的常規儲存卷會有一個確定的生命週期:每個卷都與 pod 的生命週期繫結。當 pod 處於活躍狀態的時候,卷會保持在 pod 內,如果重啟 pod 的話,卷會被重置。這個模型不適合有狀態的工作負載,這也是 Kubernetes 引入持久化卷(Persistent Volumes)概念的原因。

PersistentVolumes (PV)

Kubernetes PersistentVolumes(PV)是存在於叢集級別的儲存物件。將 PV 繫結到叢集上會擴充套件它們的生命週期,不再侷限於 pod 的生命週期。因為 PV 位於叢集級別,所以 pod 可以共享資料。我們可以擴充套件持久化卷的大小和規模,但是不能減少它的大小。

我們有兩種方式來提供 PV:

靜態方式(Statically)

:能夠讓我們預先分配儲存資源。這樣會假定叢集可用的物理儲存資源是靜態的。

動態方式(Dynamically)

:能夠讓我們擴充套件可用的儲存空間,以滿足不斷增長的需求。我們可以透過使用 Kubernetes API 伺服器啟用 DefaultStorageClass admission 控制器來使用該方案。

PersistentVolumeClaim(PVC)

PVC 能夠讓 Kubernetes 使用者請求儲存。它的執行方式與 pod 類似,只不過 pod 消費節點資源,而 PVC 消費 PV 資源。除此之外,與 pod 能夠請求特定級別的資源一樣,PVC 也可以請求特定的訪問模式和大小。

PV 和 PVC 的主要差異在於:

Kubernetes 上執行有狀態應用的最佳實踐

5

StatefulSets 和 DaemonSets

StatefulSets

StatefulSet 是一個工作負載 API 物件,旨在管理有狀態的應用。它能夠管理 pod 集合的擴充套件和部署,並且能夠保證這些 pod 的唯一性和順序。

StatefulSet 可以幫助我們處理提供持久化的儲存卷。請注意,即便 StatefulSet 中的單個 pod 很容易發生故障,有狀態的工作負載也能對故障保持彈性。持久化的 pod 識別符號能夠將現有的卷與 Kubernetes 新供應的新 pod 進行匹配,以取代發生故障的 pod。

StatefulSet 是如下場景的理想選擇:

穩定的、唯一的網路識別符號。

有序、優雅的部署和擴充套件。

穩定的、持久化的儲存。

有序的、自動的滾動更新。

如下是一個來自 Kubernetes 文件的樣例,展示了 StatefulSet 元件。

這個例子使用nginx服務來控制一個網路域。該 StatefulSet 名為 web,它有一個 Spec,表明必須在特定 pod 中啟動nginx容器的三個副本。它還宣告,當使用由 PV Provisioner 提供的 PV 時,由volumeClaimTemplates提供穩定的儲存。

DaemonSets

DaemonSets 負責確保所有或特定節點上會執行 pod 的副本。一旦節點被新增到叢集中,DaemonSet 所宣告的 pod 就會新增到節點中。當節點在叢集中移除時,DaemonSet pod 就會被垃圾回收掉。刪除 DaemonSet 時,會清理掉它所建立的 pod。

如下是 DaemonSets 的常見使用場景:

在每個節點上執行叢集儲存的 daemon

在每個節點上執行日誌收集的 daemon

在每個節點上執行節點監控的 daemon

針對每種 daemon 型別,你可以定義一個 DaemonSet 涵蓋所有的節點。也可以為每種 daemon 型別定義多個 DaemonSets,針對不同型別的硬體使用不同的標記、記憶體和 CPU。

建立 DaemonSet

執行如下的命令在 Kubernetes 叢集中建立 DaemonSet:

定義 DaemonSet 引數

Kubernetes 允許我們使用 YAML 檔案來描述 DaemonSet。下面的daemonset。yaml檔案樣例定義了一個執行fluentd-elasticsearchDocker 映象的 DaemonSet。這個例子也來自官方文件。

6

Kubernetes 中有狀態應用的最佳實踐

到此為止,我介紹了在 Kubernetes 上執行有狀態工作負載的幾種方法。這裡有一些建議,可以更有效地執行有狀態的應用:

有效利用名稱空間

:最好是將每個有狀態的應用分割到自己的名稱空間中,以確保明確的隔離並且更易於進行資源管理。

使用 ConfigMap

:所有的指令碼和自定義配置應該放到 ConfigMap 中,以確保所有的應用配置都會以宣告式的方式來進行處理。

服務路由

:隨著應用程式的增長,考慮服務路由的可管理性,應該傾向於使用 headless 服務而不是負載均衡器。

Secret 管理

:明文 secret 會給生產應用帶來嚴重的安全風險,要確保所有的 secret 都在一個強大的 secret 管理系統中理。

謹慎規劃儲存

:確定應用的持久化儲存需求,確保物理儲存裝置可供叢集使用,並以確保每個應用元件所需儲存資源的方式定義 Storage Classes 和 PVC。

7

結論

在本文中,我闡述了有狀態容器化應用的基礎知識,並介紹瞭如何在 Kubernetes 中管理有狀態工作負載。這包括以下關鍵的構件:

PersistentVolume(PV)

:允許我們定義持久化儲存單元並將其掛載到 Kubernetes 叢集中的 pod 上的構造。

PersistentVolumeClaim(PVC)

:允許 pod 動態請求符合其要求的儲存的機制。

StatefulSet

:控制器,允許建立具有持久化 ID 的 pod,即便 Kubernetes 動態擴充套件叢集中的應用,它也會保持原樣。

DaemonSets

:控制器,允許叢集中的所有節點或特定子集上執行有狀態的工作負載。

熟悉了這些構件後,你就可以直接在 Kubernetes 叢集中建立安全的、可重複執行的有狀態的工作負載了。就像 Kubernetes 中的所有內容一樣,有狀態的機制並不簡單,需要時間來掌握,但當你掌握了這些機制後,它就會變得強大而可靠。稍微練習一下,你就能成為一個有狀態 Kubernetes 的專家。

作者簡介:

Gilad David Maayan 是一位技術作家,曾與 150 多家技術公司合作,包括 SAP、Imperva、三星 NEXT、NetApp 和 Check Point,製作技術和思想領導力相關的內容,為開發者和 IT 領導層闡明技術解決方案。

https://www。infoq。com/articles/kubernetes-stateful-applications/