1. 程式人生 > 實用技巧 >kubernetes中有狀態應用的優雅縮容

kubernetes中有狀態應用的優雅縮容

將有狀態的應用程式部署到Kubernetes是棘手的。 StatefulSet使它變得容易得多,但是它們仍然不能解決所有問題。最大的挑戰之一是如何縮小StatefulSet而不將資料留在斷開連線的PersistentVolume成為孤立物件上。在這篇部落格中,我將描述該問題和兩種可能的解決方案。

通過StatefulSet建立的每個Pod都有自己的PersistentVolumeClaim(PVC)和PersistentVolume(PV)。當按一個副本按比例縮小StatefulSet的大小時,其Pod之一將終止,但關聯的PersistentVolumeClaim和繫結到其的PersistentVolume保持不變。在隨後擴大規模時,它們會重新連線到Pod。

Scaling a StatefulSet

現在,想象一下使用StatefulSet部署一個有狀態的應用程式,其資料在其pod中進行分割槽。每個例項僅儲存和處理一部分資料。當您縮小有狀態應用的規模時,其中一個例項將終止,其資料應重新分配到其餘的Pod。如果您不重新分配資料,則在再次進行擴充套件之前,它仍然不可訪問。

Redistributing data on scale-down

在正常關機期間重新分發資料

您可能會想:“既然Kubernetes支援Pod正常關閉的機制,那麼Pod是否可以在關閉過程中簡單地將其資料重新分配給其他例項呢?”事實上,它不能。為什麼不這樣做有兩個原因:

  • Pod(或更確切地說,其容器)可能會收到除縮容以外的其他原因的終止訊號。容器中執行的應用程式不知道為什麼終止該程式,因此不知道是否要清空資料。
  • 即使該應用程式可以區分是縮容還是由於其他原因而終止,它也需要保證即使經過數小時或數天也可以完成關閉程式。 Kubernetes不提供該保證。如果應用程式程序在關閉過程中死掉,它將不會重新啟動,因此也就沒有機會完全分發資料。

因此,相信在正常關閉期間Pod能夠重新分發(或以其他方式處理其所有資料)並不是一個好主意,並且會導致系統非常脆弱。

使用 tear-down 容器?

如果您不是Kubernetes的新手,那麼你很可能知道什麼是初始化容器。它們在容器的主要容器之前執行,並且必須在主要容器啟動之前全部完成。

如果我們有tear-down容器(類似於init容器),但是在Pod的主容器終止後又會執行,該怎麼辦?他們可以在我們的有狀態Pod中執行資料重新分發嗎?

假設tear-down容器能夠確定Pod是否由於縮容而終止。並假設Kubernetes(更具體地說是Kubelet)將確保tear-down容器成功完成(通過在每次返回非零退出程式碼時重新啟動它)。如果這兩個假設都成立,我們將擁有一種機制,可確保有狀態的容器始終能夠按比例縮小規模重新分配其資料。

但是?

可悲的是,當tear-down容器本身發生瞬態錯誤,並且一次或多次重新啟動容器最終使它成功完成時,像上述的tear-down容器機制將只處理那些情況。但是,在tear-down過程中託管Pod的叢集節點死掉的那些不幸時刻又如何呢?顯然,該過程無法完成,因此無法訪問資料。

現在很明顯,我們不應該在Pod關閉時執行資料重新分配。相反,我們應該建立一個新的Pod(可能安排在一個完全不同的叢集節點上)以執行重新分發過程。

這為我們帶來了以下解決方案:

縮小StatefulSet時,必須建立一個新的容器並將其繫結到孤立的PersistentVolumeClaim。我們稱其為“drain pod”,因為它的工作是將資料重新分發到其他地方(或以其他方式處理)。Pod必須有權訪問孤立的資料,並且可以使用它做任何想做的事情。由於每個應用程式的重新分發程式差異很大,因此新的容器應該是完全可配置的-使用者應該能夠在drain Pod內執行他們想要的任何容器。

StatefulSet Drain Controller

由於StatefulSet控制器當前尚不提供此功能,因此我們可以實現一個額外的控制器,其唯一目的是處理StatefulSet縮容。我最近實現了這種控制器的概念驗證。您可以在GitHub上找到原始碼:

luksa/statefulset-scaledown-controllergithub.com

下面我們解釋一下它是如何工作的。

在將控制器部署到Kubernetes集群后,您只需在StatefulSet清單中添加註釋,即可將drain容器模板新增到任何StatefulSet中。這是一個例子:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: datastore
  annotations:
    statefulsets.kubernetes.io/drainer-pod-template: |
      {
        "metadata": {
          "labels": {
            "app": "datastore-drainer"
          }
        },
        "spec": {
          "containers": [
            {
              "name": "drainer",
              "image": "my-drain-container",
              "volumeMounts": [
                {
                  "name": "data",
                  "mountPath": "/var/data"
                }
              ]
            }
          ]
        }
      }
spec:
  ...

該模板與StatefulSet中的主要Pod模板沒有太大區別,只不過它是通過註釋定義的。您可以像平常一樣部署和擴充套件StatefulSet。

當控制器檢測到按比例縮小了StatefulSet時,它將根據指定的模板建立新的drain容器,並確保將其繫結到PersistentVolumeClaim,該PersistentVolumeClaim先前已繫結至因按比例縮小而刪除的有狀態容器。

Drain容器獲得與已刪除的有狀態容器相同的身份(即名稱和主機名)。這樣做有兩個原因:

  • 一些有狀態的應用程式需要穩定的身份-這也可能在資料重新分發過程中適用。
  • 如果在執行drain過程時再次擴容StatefulSet,則這將阻止StatefulSet控制器建立重複的容器並將其附加到同一PVC。

如果drain pod或其主機節點崩潰,則drain pod將重新安排到另一個節點上,在該節點上可以重試/恢復其操作。Drain pod完成後, Pod和PVC將被刪除。備份StatefulSet時,將建立一個新的PVC。

示例

首先部署drain控制器:

$ kubectl apply -f https://raw.githubusercontent.com/luksa/statefulset-drain-controller/master/artifacts/cluster-scoped.yaml

接著部署示例StatefulSet:

$ kubectl apply -f https://raw.githubusercontent.com/luksa/statefulset-drain-controller/master/example/statefulset.yaml

這將執行三個有狀態的Pod。將StatefulSet縮小為兩個時,您會看到其中一個Pod開始終止。然後,刪除Pod後,drain控制器將立即建立一個具有相同名稱的新drain Pod:

$ kubectl scale statefulset datastore --replicas 2
statefulset.apps/datastore scaled
$ kubectl get po
NAME          READY     STATUS        RESTARTS   AGE
datastore-0   1/1       Running       0          3m
datastore-1   1/1       Running       0          2m
datastore-2   1/1       Terminating   0          49s
$ kubectl get po
NAME          READY     STATUS    RESTARTS   AGE
datastore-0   1/1       Running   0          3m
datastore-1   1/1       Running   0          3m
datastore-2   1/1       Running   0          5s    <-- the drain pod

當drain pod 完成其工作時,控制器將其刪除並刪除PVC:

$ kubectl get po
NAME          READY     STATUS    RESTARTS   AGE
datastore-0   1/1       Running   0          3m
datastore-1   1/1       Running   0          3m
$ kubectl get pvc
NAME               STATUS    VOLUME             CAPACITY   ...
data-datastore-0   Bound     pvc-57224b8f-...   1Mi        ...
data-datastore-1   Bound     pvc-5acaf078-...   1Mi        ...

控制器的另一個好處是它可以釋放PersistentVolume,因為它不再受PersistentVolumeClaim約束。如果您的叢集在雲環境中執行,則可以降低儲存成本。

總結

請記住,這僅是概念驗證。要成為StatefulSet縮容問題的正確解決方案,需要進行大量工作和測試。理想情況下,Kubernetes StatefulSet控制器本身將支援這樣的執行drain容器,而不是需要一個與原始控制器競爭的附加控制器(當您縮容並立即再次擴容時)。

通過將此功能直接整合到Kubernetes中,可以在StatefulSet規範中用常規欄位替換註釋,因此它將具有模板,volumeClaimTemplatesrainePodTemplate,與使用註釋相比,一切都變得更好了。