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

將有狀態的應用程序部署到Kubernetes是棘手的。 StatefulSet使它變得容易得多,可是它們仍然不能解決全部問題。最大的挑戰之一是如何縮小StatefulSet而不將數據留在斷開鏈接的PersistentVolume成爲孤立對象上。在這篇博客中,我將描述該問題和兩種可能的解決方案。git

經過StatefulSet建立的每一個Pod都有本身的PersistentVolumeClaim(PVC)和PersistentVolume(PV)。當按一個副本按比例縮小StatefulSet的大小時,其Pod之一將終止,但關聯的PersistentVolumeClaim和綁定到其的PersistentVolume保持不變。在隨後擴大規模時,它們會從新鏈接到Pod。github

imgScaling a StatefulSetapi

如今,想象一下使用StatefulSet部署一個有狀態的應用程序,其數據在其pod中進行分區。每一個實例僅保存和處理一部分數據。當您縮小有狀態應用的規模時,其中一個實例將終止,其數據應從新分配到其他的Pod。若是您不從新分配數據,則在再次進行擴展以前,它仍然不可訪問。app

imgRedistributing data on scale-downide

在正常關機期間從新分發數據

您可能會想:「既然Kubernetes支持Pod正常關閉的機制,那麼Pod是否能夠在關閉過程當中簡單地將其數據從新分配給其餘實例呢?」事實上,它不能。爲何不這樣作有兩個緣由:post

  • Pod(或更確切地說,其容器)可能會收到除縮容之外的其餘緣由的終止信號。容器中運行的應用程序不知道爲何終止該程序,所以不知道是否要清空數據。
  • 即便該應用程序能夠區分是縮容仍是因爲其餘緣由而終止,它也須要保證即便通過數小時或數天也能夠完成關閉程序。 Kubernetes不提供該保證。若是應用程序進程在關閉過程當中死掉,它將不會從新啓動,所以也就沒有機會徹底分發數據。

所以,相信在正常關閉期間Pod可以從新分發(或以其餘方式處理其全部數據)並非一個好主意,而且會致使系統很是脆弱。測試

使用 tear-down 容器?

若是您不是Kubernetes的新手,那麼你極可能知道什麼是初始化容器。它們在容器的主要容器以前運行,而且必須在主要容器啓動以前所有完成。code

若是咱們有tear-down容器(相似於init容器),可是在Pod的主容器終止後又會運行,該怎麼辦?他們能夠在咱們的有狀態Pod中執行數據從新分發嗎?對象

img

假設tear-down容器可以肯定Pod是否因爲縮容而終止。並假設Kubernetes(更具體地說是Kubelet)將確保tear-down容器成功完成(經過在每次返回非零退出代碼時從新啓動它)。若是這兩個假設都成立,咱們將擁有一種機制,可確保有狀態的容器始終可以按比例縮小規模從新分配其數據。blog

可是?

可悲的是,當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,與使用註釋相比,一切都變得更好了。

相關文章
相關標籤/搜索