在技術社群中,常有一種看法認為在Kubernetes(k8s)中執行資料函式庫是件複雜與不可靠的事。經過多年與容器化資料函式庫打交道的經驗,我認為這種觀點值得商榷。只要對系統進行適當的調整與設計,其帶來的效益絕對超過投入:業務能夠更快速地成長和擴充套件。

本文將分享在Kubernetes中管理有狀態應用的實戰經驗,特別是資料函式庫即服務(DBaaS)的實作方式,以及如何自動化資料函式庫的完整生命週期管理。無論你是Kubernetes新手,還是想要大規模佈署容器化資料函式庫的工程師,都能從中獲得實用的見解。

深入理解:有狀態集合與Deployment的關鍵差異

應用程式基本上可分為兩種型別:有狀態(Stateful)和無狀態(Stateless)。有狀態應用的運作依賴於其持久化的資料,例如寫入到本地硬碟的資料;而無狀態應用則不依賴於特定資料,因此更容易遷移和管理。在Kubernetes中,佈署這兩種應用分別使用有狀態集合Deployment兩種資源型別。

讓我們透過實際範例來比較這兩種佈署方式的差異。以下是簡化後的YAML清單,為了精簡我省略了部分欄位,但可以看出主要差異在於與volume相關的設定:

  • 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,從AGE欄位可以看出它們幾乎在同一時間被建立。相比之下,有狀態集合預設採用順序佈署方式,一個Pod成功啟動後才會開始下一個。

這種順序佈署有其合理性:如果某個Pod佈署失敗,可能表示底層儲存系統出現問題,此時繼續佈署其他Pod沒有意義。當然,如果你的應用程式特性允許,有狀態集合也支援透過parallel pod management設定實作平行佈署。

2. Pod命名的可預測性

有狀態集合建立的Pod會按順序編號,例如web-0、web-1等。而Deployment則會產生帶有隨機字串的Pod名稱。

這種命名差異對有狀態應用至關重要。想像一個場景:使用者透過hostname存取一個特定的Pod及其儲存資料。如果這個Pod因某種原因終止,而重建時名稱發生改變(如Deployment的行為),使用者將無法使用同樣的地址找到原來的資料。而有狀態集合會以相同名稱重建Pod,確保它能重新掛載原有的儲存卷,保持資料存取的連續性。

3. 動態儲存設定機制

有狀態集合設定中的volumeClaimTemplates部分定義了儲存卷的規格,包括名稱、大小、存取模式等:

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

這實際上代表了一個較複雜的機制:透過Persistent Volume Claim(PVC)實作動態儲存設定。在深入討論這一點前,讓我們先了解Kubernetes如何實作持久化儲存。

Kubernetes中的持久化儲存架構

假設我們有一個應用程式需要儲存資料(比如貓咪圖片)。在Kubernetes中,Persistent Volume(PV)是提供這種持久化儲存的抽象層。傳統上,叢集管理員需要手動設定PV,指定儲存大小和供應商特定的引數。

為何 Kubernetes 需要 PVC?深入解析儲存架構與 TopoLVM

為何 PVC 是必要的中間層

在 Kubernetes 的儲存架構中,我們可能會疑惑:為什麼 Pod 不能直接使用永續性儲存區(PV)?為何需要永續性儲存區宣告(PVC)作為中間層?這有幾個關鍵原因:

  • 抽象化與隱藏複雜性:PVC 有效隱藏了底層儲存的細節,使用者不需要了解資料實際儲存在哪種儲存系統上
  • 資源最佳化:若允許使用者直接選擇 PV,他們可能會不考慮最佳資源分配,直接選擇最大的可用卷宗

這種抽象層設計體現了 Kubernetes 的核心理念—將基礎設施細節與應用佈署分離,讓開發者專注於應用本身而非底層技術。

靜態佈建的挑戰

傳統的靜態佈建模式面臨一個明顯問題:管理員需要手動建立所有 PV。當系統規模較小時,這種方式或許可行,但在大型環境中(如需要上千個 PV)時,手動管理變得極為繁瑣與容易出錯。

這正是動態佈建發揮作用的地方。動態佈建根據容器儲存介面(CSI)機制運作。CSI 是 Kubernetes 制定的一個規範,目的是避免在核心程式碼中支援數十種儲存供應商。

CSI:責任反轉的設計模式

Kubernetes 團隊巧妙地「反轉了依賴關係」:將儲存系統與 Kubernetes 整合的責任轉移給了儲存供應商。Kubernetes 僅提供規範,只要儲存驅動程式符合這些規範,就能在叢集中使用該儲存系統。

CSI 監控所有磁碟空間分配請求(PVC),然後動態建立 PV。無論底層使用何種儲存技術,無論是本地或網路磁碟,Kubernetes 都能自動為應用分配最佳的儲存空間。

DBaaS 中的 有狀態集合 設定例項

在玄貓的資料函式庫即服務(DBaaS)實踐中,我們使用 有狀態集合 來佈署和管理資料函式庫。以 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 檔案
  • containers:包含資料函式庫主容器和輔助容器(如 HA 代理、指標匯出器等)的設定,以及 CPU 和記憶體的資源限制
  • volumes:除了永續性儲存區外,還包括用於容器設定的 ConfigMap、容器間資料傳輸的 emptyDir 等
  • volumeClaimTemplates:定義 PVC 設定,指定資料函式庫儲存位置和使用的 CSI 驅動程式(此例中為 TopoLVM)

為何選擇 TopoLVM 作為 CSI 實作

在實作 DBaaS 時,我們已有為 LXC 安裝購買的硬體,因此重用這些資源並在 Kubernetes 叢集中使用本地磁碟是最合理的選擇。TopoLVM 是少數能與本地磁碟協作的 CSI 驅動程式之一,它具有以下優勢:

  • 專為本地磁碟設計:無需額外購買裝置或修改系統
  • 根據 Linux LVM(邏輯卷管理器):將磁碟分割為邏輯卷,每個卷都是獨立的區塊裝置,便於設定限制和最佳分配空間
  • 支援動態佈建和無停機卷擴充套件:當開發人員需要增加儲存空間時,可以快速實作與幾乎不影響服務
  • 與排程器整合:TopoLVM 分析叢集中的可用磁碟空間,協助 Kubernetes 排程器在選擇 Pod 佈署節點時考慮儲存資源

TopoLVM 的這些特性使其成為管理本地儲存資源的理想選擇,尤其是在需要高效能和靈活性的資料函式庫應用場景中。透過這種方式,我們能夠在保持現有硬體投資的同時,提供現代化的容器化資料函式庫服務。

在實際執行中,TopoLVM 的無停機卷擴充套件功能特別有價值。當應用需求增長時,我們能夠在幾乎不影響服務的情況下擴充套件儲存空間,這對於生產環境中的資料函式庫服務至關重要。此外,與排程器的整合確保了資源分配的最佳化,避免了某些節點儲存資源過度使用而其他節點閒置的情況。

在Kubernetes中執行資料函式庫時需要自製解決方案的問題

在Kubernetes環境中管理資料函式庫時,我們常會遇到一些沒有現成解決方案的問題(或者在我們開發DBaaS平台時尚未有解決方案)。玄貓在實務中也遇到了這些挑戰,不得不花費額外時間開發了幾個自製工具。儘管過程耗時,但最終結果證明這些努力是值得的。

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

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

當所有Pod均衡使用資源時,系統執行正常。但假設某個Pod(例如pod-0)突然開始消耗大量網路流量——比如在進行資料遷移時,其他Pod的效能就會受到影響,進而影響到使用這些Pod的客戶。這就是所謂的「吵鬧的鄰居」問題。

Kubernetes對CPU和記憶體資源有內建的限制機制——透過排程器和資源限制來實作。然而,對於磁碟I/O,在我們開發DBaaS平台時並沒有現成的解決方案。我們需要想辦法限制每個使用者對本地磁碟頻寬的使用。

為此,玄貓開發了一個名為ioba的自製工具。它根據Linux核心的cgroups v2機制,以DaemonSet形式佈署在DBaaS叢集上。這個工具能夠:

  • 偵測Pod在節點上佈署的時機
  • 從設定讀取限制引數
  • 透過cgroups為特定容器設定限制

限制引數以Custom Resource IOLimit的形式定義,其中指定了容器在儲存捲上的磁碟限制。例如,以下程式碼為名為db的容器在名為data的Volume上設定了200 IOPS和200MB/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

這個設定確保了即使在高負載情況下,每個容器也只能使用其被分配的磁碟資源,有效防止了「吵鬧的鄰居」問題。

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

前面玄貓提到TopoLVM具有擴充套件儲存容量的能力。但這個功能在Kubernetes的有狀態集合上有特定限制,這個問題在社群中已經爭論多年。問題的核心是:當嘗試在執行時修改有狀態集合中的PVC大小時,Kubernetes會拒絕操作並報錯,表示該資源無法在執行時修改。

雖然可以手動修改PVC大小,但這會導致與有狀態集合的不一致性。如果之後增加副本數量,新副本會自動使用舊的PVC容量設定。

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

  1. 工具根據請求增加PVC大小,這會觸發Volume擴充套件功能
  2. 工具使用cascade=orphan標誌刪除有狀態集合。這導致所有Pod保留在叢集中,僅刪除了清單檔
  3. 使用新的PVC大小重新建立有狀態集合清單。它會自動繫結到現有的Pod上

這種方法有一個缺點:由於我們需要刪除有狀態集合,其Pod暫時失去控制。如果此時有外部變更Pod,有狀態集合不會察覺並還原Pod至期望狀態。但考慮到自動Volume擴充套件的巨大好處,玄貓認為這是值得的權衡。

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

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

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

其工作原理如下:

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

這種機制確保了我們可以安全地維護Kubernetes節點,同時保持資料函式庫服務的可靠性和資料完整性。透過這些自製解決方案,玄貓成功解決了在Kubernetes上執行資料函式庫時面臨的特殊挑戰,使DBaaS平台能夠提供穩定與可靠的服務。

在實際操作中,這些工具已經證明瞭它們的價值,特別是在大規模環境中。雖然開發自製解決方案需要額外的時間和資源,但它們提供了精確符合我們特定需求的功能,這是現成解決方案無法提供的。隨著Kubernetes生態系統的不斷發展,我們期待看到更多這些問題的標準解決方案出現,但在此之前,這些自製工具將繼續發揮重要作用。

根據 Kubernetes 構建 DBaaS 平台:成果

最終成果與挑戰

在這個專案的最後階段,玄貓發現 Kubernetes 確實非常適合用於有狀態(Stateful)應用程式的佈署與管理,但同時也明確看到了許多可以改進的空間。目前我們仍有幾項工作尚待完成,包括:

  • 最佳化 dbaas-descheduler 的穩定性
  • 與基礎架構團隊協作進行系統改善
  • 擴充套件平台以支援更多種類別的資料函式庫
  • 其他功能性與非功能性的提升

儘管如此,根據 Kubernetes 我們已經成功建立了一個穩定可靠的 DBaaS 平台,並實作了資料函式庫生命週期的自動化管理。這個可靠的平台為公司提供了更快速擴充套件的能力,大幅提升了開發效率與系統可靠性。

在實際應用中,我們達成了多項關鍵績效指標的顯著提升。這包括資料函式庫佈署時間的縮短、系統可用性的提高、維護成本的降低,以及開發人員生產力的增加。這些成果不僅證明瞭我們技術選擇的正確性,也為未來的發展奠定了堅實的基礎。

從專案中獲得的經驗

這個專案給了玄貓許多寶貴的經驗。首先,它證明瞭 Kubernetes 作為容器協調平台,除了無狀態應用外,在處理有狀態的資料函式庫服務時也具有強大的能力。其次,我瞭解到基礎設施即程式碼(Infrastructure as Code)的方法對於管理複雜系統的重要性。

從技術選型角度看,選擇 Kubernetes 作為基礎平台是正確的決定,它提供了我們所需的彈性、可擴充套件性和自動化能力。然而,這也帶來了一些挑戰,特別是在處理資料函式庫特有的需求時,如資料永續性、備份還原和高用性等方面。

從團隊協作的角度來看,這個專案也強調了跨團隊合作的重要性。DBaaS 平台的成功不僅依賴於平台團隊的努力,還需要與基礎架構團隊、資料函式庫工作者和應用開發團隊的密切合作。

我們計劃在幾個關鍵方向上繼續改進 DBaaS 平台:

  1. 多資料函式庫支援:擴充套件平台以支援更多種類別的資料函式庫系統,包括 NoSQL 資料函式庫和新興的分散式資料函式庫

  2. 自動化最佳化:實作更人工智慧的資源管理和效能最佳化,如自動調整資料函式庫設定引數

  3. 災難還原增強:改進備份和還原機制,實作更快速和可靠的災難還原能力

  4. 多雲支援:擴充套件平台以支援多雲環境,提供更大的彈性和避免廠商鎖定

  5. 自助服務增強:開發更直觀的自助服務介面,使開發人員能更輕鬆地管理他們的資料函式庫

這個專案是一個很好的例子,說明瞭如何利用現代容器技術來解決傳統資料函式函式倉管理的挑戰。透過將 Kubernetes 的強大功能與資料函式函式倉管理的最佳實踐相結合,我們建立了一個能夠滿足當前需求並為未來發展做好準備的平台。

在技術快速變化的今天,這種靈活與可擴充套件的方法對於建立能夠適應未來需求的系統至關重要。透過持續改進和創新,我相信我們的 DBaaS 平台將繼續為組織提供價值,並支援其業務目標的實作。