StatefulSet: Kubernetes 中對有狀態應用的運行和伸縮

在最新發布的 Kubernetes 1.5 咱們將過去的 PetSet 功能升級到了 Beta 版本,並從新命名爲StatefulSet。除了依照社區民意改了名字以外,這一 API 對象並無太大變化,不過咱們在向集合裏部署 Pod 的過程當中加入了「每索引最多一個」的語義。有了順序部署、順序終結、惟一網絡名稱以及持久穩定的存儲,咱們認爲,對於大量的有狀態容器化負載,咱們已經具有了必定的支持能力。咱們並非宣稱這一功能已經徹底完成,可是咱們相信他已經處於一個可用狀態,而且咱們會在推進其正式發佈的過程當中保持其兼容性。html

StatefulSet 的採用時機

在 Kubernetes 中,Deployment 和 ReplicaSets 都是運行無狀態應用的有效手段。但這兩種方式對於有狀態應用來講就不太合適了。StatefulSet 的目的就是給爲數衆多的有狀態負載提供正確的控制器支持。然而須要注意的是,不必定全部的有存儲應用都是適合移植到 Kubernetes 上的,在移植存儲層和編排框架以前,須要回答如下幾個問題。git

應用是否可使用遠程存儲?

目前,咱們推薦用遠程存儲來使用 StatefulSets,就要對由於網絡形成的存儲性能損失有一個準備:即便是專門優化的實例,也沒法同本地加載的 SSD 相提並論。你的雲中的網絡存儲,可以知足 SLA 要求麼?若是答案是確定的,那麼利用 StatefulSet 運行這些應用,就可以得到自動化的優點。若是應用所在的 Node 發生故障,包含應用的 Pod 會調度到其餘 Node 上,在這以後會從新加載他的網絡存儲以及其中的數據。github

這些應用是否有伸縮需求?

用 StatefulSet 運行應用會帶來什麼好處呢?你的整個組織是否只須要一個應用實例?對該應用的伸縮是否會引發問題?若是你只須要較少的應用實例數量,這些實例可以知足組織現有的須要,並且能夠預見的是,應用的負載不會很快增加,那麼你的本地應用可能無需移植。服務器

然而,若是你的系統是微服務所構成的生態系統,就會比較頻繁的交付新服務,若是更近一步,服務是有狀態的,那麼 Kubernetes 的自動化和健壯性特性會對你的系統有很大幫助。若是你已經在使用 Kubernetes 來管理你的無狀態服務,你可能會想要在同一個體系中管理你的有狀態應用。網絡

預期性能增加的重要性?

Kubernetes 還不支持網絡或存儲在 Pod 之間的隔離。若是你的應用不巧和嘈雜的鄰居共享同一個節點,會致使你的 QPS 降低。解決方式是把 Pod 調度爲該 Node 的惟一租戶(獨佔服務器),或者使用互斥規則來隔離會爭用網絡和磁盤的 Pod,可是這就意味着用戶必須鑑別和處置(競爭)熱點。app

若是榨乾有狀態應用的最大 QPS 不是你的首要目標,並且你願意也有能力處理競爭問題,似的有狀態應用可以達到 SLA 須要,又若是對服務的移植、伸縮和從新調度是你的主要需求,Kubernetes 和 StatefulSet 可能就是解決問題的好方案了。框架

你的應用是否須要特定的硬件或者實例類型

若是你的有狀態應用在高端硬件或高規格實例上運行,而其餘應用在通用硬件或者低規格實例上運行,你可能不想部署一個異構的集羣。若是能夠把全部應用都部署到統一實例規格的實例上,那麼你就可以從 Kubernetes 得到動態資源調度和健壯性的好處。less

實踐環節 – ZooKeeper

有兩個緣由讓 [ZooKeeper] 成爲 StatefulSet 的好例子。首先,StatefulSet 在其中演示了運行分佈式、強一致性存儲的應用的能力;其次,ZooKeeper 也是 Apache Hadoop 和 Apache Kafka 在 Kubernetes 上運行的前置條件。在 Kubernetes 文檔中有一個 深度教程 說明了在 Kubernetes 集羣上部署 ZooKeeper Ensemble 的過程,這裏會簡要描述一下其中的關鍵特性。分佈式

建立 ZooKeeper 的 Ensemble

建立 Ensemble 很容易,只要用 kubectl create 來根據定義來建立對象就能夠了。微服務

$ kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes.github.io/master/docs/tutorials/stateful-application/zookeeper.yaml
service "zk-headless" created
configmap "zk-config" created
poddisruptionbudget "zk-budget" created
statefulset "zk" created

接下來 StatefulSet 控制器開始順序建立各個 Pod,在建立後續 Pod 以前,首先要等前面的 Pod 運行成功並進入到就緒狀態。

$ kubectl get -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      0/1       Pending   0          0s
zk-0      0/1       Pending   0         0s
zk-0      0/1       Pending   0         7s
zk-0      0/1       ContainerCreating   0         7s
zk-0      0/1       Running   0         38s
zk-0      1/1       Running   0         58s
zk-1      0/1       Pending   0         1s
zk-1      0/1       Pending   0         1s
zk-1      0/1       ContainerCreating   0         1s
zk-1      0/1       Running   0         33s
zk-1      1/1       Running   0         51s
zk-2      0/1       Pending   0         0s
zk-2      0/1       Pending   0         0s
zk-2      0/1       ContainerCreating   0         0s
zk-2      0/1       Running   0         25s
zk-2      1/1       Running   0         40s

檢查一下 StatefulSet 中每一個 Pod 的主機名稱,你會發現 Pod 的主機名也包含了 Pod 的順序:

$ for i in 0 1 2; do kubectl exec zk-$i -- hostname; done
zk-0
zk-1
zk-2

ZooKeeper 在一個名爲 「myid」 的文件中保存了每一個服務器的惟一標識符。這個標識符只是天然數。在 Ensemble 的服務器中,」myid」 文件中保存的數字就是 Pod 主機名中的順序號加一。

$ for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done
myid zk-0
1
myid zk-1
2
myid zk-2
3

基於主機名,每一個 Pod 都有獨立的網絡地址,這個網域由 zk-headless 這一 Headless 服務所控制。

$  for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done
zk-0.zk-headless.default.svc.cluster.local
zk-1.zk-headless.default.svc.cluster.local
zk-2.zk-headless.default.svc.cluster.local

Pod 具備了惟一的序號和網絡地址,就能夠用來在 ZooKeeper 的配置文件中設置 Ensemble 成員了。

kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg
clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInteval=1
server.1=zk-0.zk-headless.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-headless.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-headless.default.svc.cluster.local:2888:3888

StatefulSet 讓用戶能夠用穩定、可重複的方式來部署 ZooKeeper。不會建立具備重複 ID 的服務器,服務器之間能夠經過穩定的網絡地址互相通訊,由於 Ensemble 具備穩定的成員關係,所以 Leader 選拔和寫入複製能力也獲得了保障。

檢查 Ensemble 工做情況的最簡單方式就是向一臺服務器寫入一個值,而後從另外一臺服務器中讀取。能夠利用 ZooKeeper 自帶的 「zkCli.sh」 腳原本建立包含數據的 ZNode。

$  kubectl exec zk-0 zkCli.sh create /hello world
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
Created /hello

使用同一腳本,能夠從 Ensemble 另一臺服務器中讀取數據。

$  kubectl exec zk-1 zkCli.sh get /hello 
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
...

能夠用刪除 zk StatefulSet 的方式停掉 Ensemble。

$  kubectl delete statefulset zk
statefulset "zk" deleted

級聯刪除會銷燬 StatefulSet 中的每一個 Pod,而且按照建立順序的反序來執行,只有在成功終結後面一個以後,纔會繼續下一個刪除操做。

$  kubectl get pods -w -l app=zk
NAME      READY     STATUS    RESTARTS   AGE
zk-0      1/1       Running   0          14m
zk-1      1/1       Running   0          13m
zk-2      1/1       Running   0          12m
NAME      READY     STATUS        RESTARTS   AGE
zk-2      1/1       Terminating   0          12m
zk-1      1/1       Terminating   0         13m
zk-0      1/1       Terminating   0         14m
zk-2      0/1       Terminating   0         13m
zk-2      0/1       Terminating   0         13m
zk-2      0/1       Terminating   0         13m
zk-1      0/1       Terminating   0         14m
zk-1      0/1       Terminating   0         14m
zk-1      0/1       Terminating   0         14m
zk-0      0/1       Terminating   0         15m
zk-0      0/1       Terminating   0         15m
zk-0      0/1       Terminating   0         15m

可使用 kubectl apply 命令來重建 zk StatefulSet,並從新部署 Ensemble。

$  kubectl apply -f http://k8s.io/docs/tutorials/stateful-application/zookeeper.yaml
service "zk-headless" configured
configmap "zk-config" configured
statefulset "zk" created

若是使用 「zkCli.sh」 腳原本嘗試獲取刪除 StatefulSet 以前寫入的數據,會發現數據依然存在。

$  kubectl exec zk-2 zkCli.sh get /hello 
...

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
world
...

及時全部的 Pod 都被銷燬,他們一旦被從新調度,StatefulSet 也能保證 Ensemble 可以選拔新的 Leader 並繼續提供服務。

Node 故障的容錯

ZooKeeper 會在 Ensmble 的服務器中複製他的狀態機,用於應對 Node 故障。缺省狀況下 Kubernetes 調度器能夠在同一個 Node 上部署屬於 zk StatefulSet 的多個 Pod,假設 zk-0 和 zk-1 兩個 Pod 被部署在同一個 Node 上,若是這一 Node 出現故障,ZooKeepers Ensemble 會由於數量不足形成沒法提交寫入,ZooKeeper 會出現服務中斷,直到 Pod 被從新調度。

在集羣中,建議爲關鍵進程預留更多資源,這樣就能保證故障狀況發生的時候可以迅速從新調度 Pod,縮短故障時間。

若是這樣沒法 SLA 規定的停機時間,那麼就應該使用 PodAntiAffinity( Pod 互斥性)註解。用來建立 Ensemble 的定義文件中就包含了這樣的註解,他會要求 Kubernetes 調度器不要把 zk StatefulSet 中的多個 Pod 部署在同一 Node 上。

計劃內維護的容錯

用於建立 ZooKeeper Ensemble 的描述文件還建立了一個 PodDistruptionBudget( Pod 中斷預算 )對象:zk-budget。zk-budget 用於指示 Kubernetes, 這一服務可以容忍的中斷 Pod (不健康 Pod)的上限。

{
  "podAntiAffinity": {
    "requiredDuringSchedulingRequiredDuringExecution": [
      {
        "labelSelector": {
          "matchExpressions": [
            {
              "key": "app",
              "operator": "In",
              "values": [
                "zk-headless"
              ]
            }
          ]
        },
        "topologyKey": "kubernetes.io/hostname"
      }
    ]
  }
}
$ kubectl get poddisruptionbudget zk-budget
NAME        MIN-AVAILABLE   ALLOWED-DISRUPTIONS   AGE
zk-budget   2               1                     2h

zk-budget 定義,至少要有兩個處於可用狀態的成員才能保障 Ensemble 的健康。若是在離線以前對 Node 進行 Drain 操做,若是這一操做過程當中終止的 Pod 會違反預算,Drain 操做就會失敗。若是使用 kubectl drain,來對 Node 進行 cordon 操做並驅逐全部其中運行的 Node,PodDistruption 讓你能夠確認這一操做不會中斷有狀態應用的服務。

更進一步

由於 Kubernetes 的開發工做目標是可用,咱們但願得到更多來自用戶的設想。若是你想要幫咱們處理問題,能夠看看 GitHub 上關於 statful 的 Issues。然而爲了 API 的易於理解,咱們並不許備實現全部的功能請求。咱們會優先實現一些可以對全部有狀態應用產生改善的功能,例如滾動更新支持、Node 升級的集成、使用高速的本地存儲等。StatefulSet 的目的在於支持儘量多而不是所有的有狀態應用。基於這些考慮,咱們會避免依賴隱藏特性或者技術的方式來充實 StatefulSet。每一個人均可以開發一個想 StatefulSets 的控制器。咱們稱之爲 「making it forkable」。明年,咱們但願更多的流行的有狀態應用可以有本身的社區支持的獨立控制器或 「操做器」。咱們已經據說了 etcd、Redis 和 ZooKeeper 的自定義控制器的開發工做。咱們期待更多相似案例不斷涌現。

ETCD 和 Prometheus 的 Operator 來自 CoreOS,演示了一種超出 StatefulSet 能力的自動化和集成能力。另外一方面,使用 Deployment 和 StatefulSet 這樣的通用控制器可以用同一種對象管理大量有狀態應用。Kubernetes 用戶有了運行有狀態應用的能力,並且能夠自行在兩種方式之中進行選擇。


轉自https://www.kubernetes.org.cn/1130.html

相關文章
相關標籤/搜索