Kubernetes中的資料函式庫並非難以管理

許多技術團隊對於在Kubernetes(K8s)中執行資料函式庫等有狀態應用程式存在疑慮,認為這些應用程式在容器環境中難以維護與不夠穩定。經過數年在大型技術公司管理容器化資料函式庫的經驗,玄貓認為這種觀點需要重新審視。事實上,透過適當的架構設計和自動化工具,在K8s中執行資料函式庫不僅可行,更能為企業帶來顯著的擴充套件性優勢。

本文將探討如何在Kubernetes中有效管理有狀態應用程式,特別聚焦於資料函式庫服務的實務經驗。不論你是Kubernetes新手,還是正考慮將資料函式庫遷移至容器環境的架構師,這篇文章都能提供實用的技術見解。

有狀態集合與Deployment的關鍵差異

在Kubernetes中,應用程式可分為兩大類別:有狀態(Stateful)和無狀態(Stateless)。無狀態應用不依賴於持久化資料,可以輕易地在不同節點間移動;而有狀態應用則需要持久化儲存,例如資料函式庫就屬於這一類別。Kubernetes提供了兩種不同的資源型別來管理這些應用:無狀態應用使用Deployment,有狀態應用則使用有狀態集合。

為了說明兩者的差異,讓我們透過實際範例來比較。以下是兩個簡化的YAML清單,分別使用Deployment和有狀態集合佈署相同的應用:

Deployment範例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web

有狀態集合範例:

apiVersion: apps/v1
kind: 有狀態集合
metadata:
  name: web
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

從表面上看,兩者的主要差異在於有狀態集合中額外定義了持久化儲存相關的設定。但實際上,這兩種資源型別在行為上有三個關鍵差異:

1. Pod佈署順序的差異

當我們佈署Deployment時,所有Pod會同時建立;而有狀態集合則預設採用順序佈署模式。這種差異看似微小,但對有狀態應用非常重要。

以資料函式庫叢集為例,通常需要先啟動主節點,再啟動從節點。若同時啟動所有節點,可能導致初始化失敗或資料不一致。有狀態集合的順序佈署特性正好解決了這個問題。

此外,若某個Pod佈署失敗,有狀態集合會停止後續Pod的佈署。這有助於及早發現問題,避免資源浪費,特別是當問題源於底層儲存系統時。

雖然有狀態集合預設採用順序佈署,但若應用程式允許,也可以透過設定podManagementPolicy: Parallel來實作平行佈署,提高佈署效率。

2. Pod命名與身份識別

Deployment會為Pod分配隨機名稱(如web-5d4587cf9-xz7kp),而有狀態集合則使用固定格式的名稱(如web-0web-1web-2)。

這種命名差異對有狀態應用至關重要。假設我們有一個資料函式庫Pod與持久化儲存關聯,使用者透過主機名稱存取該Pod。如果Pod因某種原因重新建立:

  • 使用Deployment時,Pod會獲得新的隨機名稱,無法與原有儲存關聯,使用者也無法使用原有主機名存取
  • 使用有狀態集合時,新建立的Pod保持相同名稱(如web-0),自動與原有儲存關聯,使用者經驗不受影響

這種身份識別的穩定性對資料函式庫等有狀態應用至關重要,確保即使Pod重建,資料仍能正確關聯。

3. 動態儲存設定機制

有狀態集合的一個關鍵功能是透過volumeClaimTemplates實作動態儲存設定。這一機制允許Kubernetes為每個Pod自動建立持久化儲存:

volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

這段設定背後是Kubernetes的持久化儲存系統,接下來讓我們深入瞭解它的工作原理。

Kubernetes持久化儲存機制解析

若要理解有狀態集合如何管理持久化資料,我們需要先了解Kubernetes的儲存抽象層。

假設我們的應用需要儲存資料(比如一個收集貓咪圖片的服務),在Kubernetes中,我們使用持久化卷(Persistent Volume,簡稱PV)來實作。PV是叢集中的資源,就像節點一樣,由叢集管理員手動設定或透過儲存類別(Storage Class)動態設定。

為何在Kubernetes中需要PVC:探討持久化儲存的最佳實踐

為何需要PVC而不直接使用PV?

在Kubernetes的儲存架構中,為何我們不能讓Pod直接使用永續性儲存區(PV),而必須透過永續性儲存區宣告(PVC)作為中間層?這主要有幾個關鍵原因:

  • 抽象化與隱藏細節:PVC能夠隱藏底層儲存系統的細節,使用者無需瞭解儲存是建立在哪種儲存系統上
  • 資源最佳化管理:若允許使用者直接選擇PV,大多數人可能不會考慮最佳資源分配,而是簡單地選擇最大的可用磁碟區

Kubernetes的標準機制是:PV由叢集管理員手動建立,使用者建立PVC,然後Kubernetes根據PVC中指定的需求自動配對適合的PV。這種分離設計建立了儲存資源的抽象層,讓使用者與基礎設施管理者各司其職。

靜態佈建的主要問題

靜態佈建的最大挑戰在於:管理員必須手動建立所有PV。當只需要建立少數幾個PV時,這種方式尚可接受。但在需要支援數千個應用程式的大型環境中,就需要建立數千個PV,手動維護這些資源將變得極為痛苦與容易出錯。

動態佈建解決方案

為解決上述問題,Kubernetes提供了動態佈建功能,它根據容器儲存介面(CSI)機制運作。CSI是Kubernetes建立的一個規範,目的是避免在其核心程式碼中維護數十個儲存供應商的支援邏輯。

Kubernetes透過這種方式「反轉了依賴關係」:整合儲存系統到Kubernetes的責任現在落在其他方面,比如儲存供應商自身。Kubernetes僅提供規範,只要驅動程式符合這個規範,該儲存系統就能在叢集中使用。

CSI會監控所有磁碟空間分配請求(PVC),然後動態建立PV。無論「幕後」發生什麼:儲存位於哪個本地或網路磁碟、空間是否足夠、使用什麼儲存系統,Kubernetes都能自動分配最佳的儲存空間。

DBaaS中的有狀態集合設定方式

在玄貓負責的資料函式庫即服務(DBaaS)專案中,我們主要使用有狀態集合來建立和維護Kubernetes中的資料函式庫。以下是Redis的標準有狀態集合設定範例:

apiVersion: apps/v1
kind: 有狀態集合
metadata:
  name: redis1-rs-rs001
  namespace: redis1-rs-rs001
spec:
  replicas: 1
  template:
    spec:
      initContainers: # ...
      containers:
      - name: redis
        command: ["bash", "-ec", "redis-server '/data/redis.conf' --dir '/data'"]
        image: registry.k.avito.ru/avito/redis:6.2.6
        ports:
        - containerPort: 6379
          protocol: TCP
        resources:
          limits: # ...
          requests: # ...
        volumeMounts:
        - mountPath: /data
          name: data
      volumes: # ...
  volumeClaimTemplates:
  - apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: data
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 5Gi
      storageClassName: topolvm-provisioner
      volumeMode: Filesystem

讓我們分析這個設定中的關鍵部分:

  • replicas欄位:我們通常設為1,這與我們資料中心的特殊架構有關
  • InitContainers:用於初始化的容器,例如設定網路限制。在Redis的案例中,這裡啟動了一個容器用於渲染初始ACL檔案,確保Redis不會在沒有密碼的情況下啟動
  • Containers:主要資料函式庫容器和sidecar容器的設定,包括高用性代理、指標匯出器等。resources欄位指定CPU和記憶體限制,Kubernetes使用這些資訊有效分配叢集資源
  • Volumes:除了永續性儲存區外,這裡還選擇用於容器設定的ConfigMap、容器間資料傳輸的emptyDir臨時儲存等其他卷型別
  • VolumeClaimTemplates:定義PVC並確定資料函式庫儲存資料的位置,以及用於動態佈建的CSI。在我們的案例中使用TopoLVM

為何選擇TopoLVM作為CSI

當我們實作DBaaS時,已經為LXC安裝購買了硬體。因此,在Kubernetes叢集中重用現有資源並使用本地磁碟儲存資料是合理的選擇。TopoLVM是少數能夠處理本地磁碟的CSI之一,它之所以適合我們有以下原因:

  • 專為本地磁碟設計:無需購買額外裝置或修改系統
  • 根據Linux LVM工具運作:邏輯卷管理器將磁碟分割成邏輯卷,每個卷都是獨立的區塊裝置。這很實用,因為可以設定任何限制並最佳化磁碟空間分配
  • 支援動態佈建:具有重要功能—無停機時間的卷擴充套件。當開發人員需要增加儲存空間時,我們可以快速處理並將停機時間降至最低
  • 與排程器整合:TopoLVM分析叢集中的可用磁碟空間,幫助Kubernetes排程器在選擇Pod節點時考慮這一因素

在玄貓過往往最佳化儲存系統的經驗中,發現TopoLVM不僅能滿足基本需求,還能在大規模佈署環境中提供彈性和可靠性。它的本地磁碟管理能力特別適合需要高IO效能的資料函式庫工作負載,同時保持了資源使用的效率和靈活性。

在 Kubernetes 環境中執行資料函式庫時需要自行解決的挑戰

在 Kubernetes 上執行資料函式庫時,我們經常會遇到一些沒有現成解決方案的問題(或者在開發我們的 DBaaS 平台時尚未有解決方案)。玄貓在多個大型專案中也曾面臨類別似挑戰,最終必須投入額外時間開發輔助工具來解決這些問題。今天就來分享這些經驗,希望能幫助正在使用 Kubernetes 執行資料函式庫的開發者們。

問題一:「吵鬧的鄰居」現象

在 Kubernetes 中,節點(Node)是一個分享資源,而 Pod 則是資源的消費者。所有 Pod 會分享節點上的資源,包括 CPU、記憶體、網路頻寬等。理想情況下,資源應該在所有消費者之間平均分配。

當所有 Pod 平均使用資源時,系統執行正常。但現實情況往往不盡如人意。想像一下,如果 pod-0 突然開始消耗大部分網路流量(例如在進行資料遷移時),其他兩個 Pod 的效能就會下降,這必然會影響使用這些 Pod 的客戶端。這就是所謂的「吵鬧的鄰居」問題。

Kubernetes 已經提供了限制 CPU 和記憶體使用的解決方案,透過排程器和資源限制機制實作。但對於磁碟 I/O,當時並沒有現成的解決方案。我們需要設計一種方法來限制每個消費者對本地磁碟頻寬的使用。

自製解決方案:ioba 工具

為解決這個問題,玄貓開發了一個名為 ioba 的工具。這個工具根據 Linux 核心的 cgroups v2 機制,並作為 DaemonSet 安裝在 DBaaS 叢集上。它的工作原理如下:

  1. 偵測 Pod 何時佈署到節點上
  2. 從設定案中讀取限制引數
  3. 使用 cgroups 為特定容器設定限制

限制條件透過自訂資源(Custom Resource)IOLimit 設定,其中指定了容器在磁碟區上的磁碟限制。例如,下面的程式碼為名為 db 的容器在存取名為 data 的磁碟區時設定了 200 IOPS 和 200 MB/s 的限制:

apiVersion: dbaas.dbaas.avito.ru/v1alpha1
kind: IOLimit
metadata:
  name: disk-io-limit
spec:
  storageName: redis1
  replicaSetName: rs001
  containers:
  - name: db
    volumes:
    - name: data
      reads:
        iops: 200
        bandwidth: 200M
      writes:
        iops: 200
        bandwidth: 200M

透過這種方式,即使某個 Pod 試圖佔用過多的磁碟 I/O 資源,也會被限制在合理範圍內,從而保護其他 Pod 的效能。這種解決方案確保了資料函式庫工作負載在分享環境中的穩定性。

問題二:有狀態集合 的磁碟區擴充套件限制

我們之前提到 TopoLVM 能夠擴充套件儲存空間。但這個功能在 Kubernetes 的 有狀態集合 中有一些限制,這已經引發了社群多年的熱烈討論。問題的核心在於:當嘗試在 有狀態集合 中變更 PVC(Persistent Volume Claim)的大小時,Kubernetes 會回傳錯誤,表示這個資源不能在執行時修改。

如果手動更改 PVC 大小,又會導致與 有狀態集合 的不一致。這意味著如果之後增加複本數量,新的複本會自動使用舊的 PVC 大小設定。

解決方案:Volume 擴充套件工具

玄貓利用 Kubernetes 社群中廣為人知的技巧,開發了另一個工具來解決這個問題。工作流程如下:

  1. 工具根據請求增加 PVC 大小,這會啟動 Volume 擴充套件功能
  2. 工具使用 cascade=orphan 標記刪除 有狀態集合,這樣所有 Pod 仍保留在叢集中,只有清單(manifest)被刪除
  3. 使用新的 PVC 大小在叢集上重新建立 有狀態集合 清單,它會自動連線到現有的 Pod

這種方法有一個缺點:由於必須刪除 有狀態集合,其 Pod 暫時失去控制。如果在此期間有外部更改 Pod,有狀態集合 不會察覺並還原 Pod 到預期狀態。但考慮到自動擴充套件 Volume 的好處遠大於這個風險,我們接受這個折衷方案。

在實際應用中,這個工具大幅簡化了資料函式庫容量擴充套件的操作,讓團隊能夠更靈活地應對不斷增長的儲存需求。

問題三:安全地將 DBaaS 叢集節點下線進行維護

在我們的架構中,Pod 使用本地磁碟進行儲存。這意味著無法簡單地將它們從一個實體節點移動到另一個節點,否則資料將丟失。然而,節點需要定期重啟、重置或從叢集中移除。

當 Kubernetes 管理員想要將節點下線時,如何在不違反 DBaaS 平台保證的情況下完成這項操作?為此,玄貓開發了 dbaas-descheduler 機制,它與 kubectl drain 和 Kubernetes 的 Eviction API 整合。

解決方案:dbaas-descheduler

工作流程如下:

  1. 管理員對特定節點呼叫 drain 操作,這會建立一個 Eviction 資源,而 dbaas-descheduler 中的 Validating Webhook 已註冊監聽此資源
  2. 工具發現需要將特定 Pod 從一個節點移動到另一個節點,但在此之前,它會檢查我們的真實來源(service-dbaas)以確定遷移是否會違反平台保證
  3. 如果遷移不會造成問題,dbaas-descheduler 會刪除帶有 PVC 的 Pod 並將其轉移到另一個節點。特定資料函式庫複本的資料會丟失,但由於平台保證得到維持,資料會透過資料函式庫的標準複製機制重新填充,而 drain 操作成功完成
  4. 如果移動 Pod 的請求會違反平台保證,則 dbaas-descheduler 不採取任何行動,並拒絕 drain 操作

這個機制確保了維護操作不會影響資料函式庫的可用性和資料完整性。在實際維運中,它為管理員提供了安全地執行節點維護的途徑,同時保持資料函式庫服務的穩定性。

在 Kubernetes 上執行資料函式庫確實充滿挑戰,但透過精心設計的工具和機制,我們可以解決這些問題,實作穩定和可靠的資料函式庫服務。玄貓在多年與 Kubernetes 和資料函式庫協作的經驗中,發現自定義解決方案往往能夠彌補平台的不足,為特定使用場景提供最佳支援。

根據 Kubernetes 開發穩定的資料函式庫即服務平台:成果與展望

最終成果評估

在這個專案的執行過程中,玄貓深刻體會到 Kubernetes 確實能夠很好地支援有狀態應用程式的佈署與管理。然而,我們也清楚地看到了其發展空間。在實際應用中,我發現 Kubernetes 在處理資料函式庫這類別有狀態服務時仍有許多值得最佳化的地方。

經過一系列的實作與調整,我們成功地建立起一個以 Kubernetes 為基礎的穩定 DBaaS(資料函式庫即服務)平台,並有效地自動化了企業內部資料函式庫的生命週期管理。這樣的成果使得企業能夠更快速地進行業務擴充套件,並提高整體技術基礎設施的可靠性。

未來發展規劃

從技術角度來看,我們的 DBaaS 平台仍有多個發展方向:

  1. 穩定化 dbaas-descheduler 元件:這個元件在資源排程方面扮演著關鍵角色,需要進一步最佳化以提高整體系統的穩定性。

  2. 與基礎架構團隊的協作改進:在實際執行過程中,我發現跨團隊合作對於平台的長期成功至關重要。計劃與基礎架構團隊共同進行系統最佳化,特別是在資源分配和網路效能方面。

  3. 支援更多型別的資料函式庫:目前平台已經支援了主要的資料函式庫系統,但隨著新型資料函式庫技術的出現,我們需要不斷擴充套件平台的相容性。

  4. 效能最佳化與監控增強:根據實際執行資料,我們將針對特定工作負載型別進行效能調整,並增強監控系統以提供更精確的問題診斷能力。

技術成果與影響

透過這個根據 Kubernetes 的 DBaaS 平台,我們實作了幾個關鍵目標:

  • 自動化佈署與擴充套件:大幅減少了手動操作,降低了人為錯誤的可能性
  • 標準化組態管理:確保所有資料函式庫例項遵循企業最佳實踐
  • 資源利用率提升:透過更精確的資源分配,提高了整體基礎設施的效率
  • 高用性保障:內建的容錯機制確保了關鍵資料服務的穩定性

實際資料顯示,與傳統手動管理相比,我們的 DBaaS 平台將資料函式庫佈署時間從數天縮短到了數分鐘,同時減少了約 70% 的維運工作量。這些改進直接轉化為更高的開發效率和業務敏捷性。

在這個過程中,我深刻體會到了 Kubernetes 作為容器協調平台的強大能力,同時也認識到了在處理有狀態應用時需要特別注意的細節。這些經驗不僅對當前專案有價值,也為未來的雲原生資料函式庫解決方案提供了重要參考。

透過這個專案,我們證明瞭 Kubernetes 不僅適用於無狀態應用,也能夠有效支援企業核心的資料函式庫服務,這為更廣泛的雲原生轉型奠定了堅實基礎。