本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

Kubernetes StatefulSets 和 DaemonSets 更新

本文將討論 Kubernetes 的 DaemonSetStatefulSet API 物件的最新更新。我們將使用 Apache ZooKeeperApache Kafka StatefulSets 以及 Prometheus 節點匯出器 DaemonSet 來探索這些功能。

在 Kubernetes 1.6 中,我們將 RollingUpdate 更新策略新增到了 DaemonSet API 物件中。使用 RollingUpdate 策略配置您的 DaemonSets 會使 DaemonSet 控制器在更新其 spec.template 時對 DaemonSets 中的 Pods 執行自動滾動更新。

在 Kubernetes 1.7 中,我們增強了 DaemonSet 控制器,以跟蹤 DaemonSets 的 PodTemplateSpecs 的修訂歷史。這允許 DaemonSet 控制器回滾更新。我們還將 RollingUpdate 策略新增到了 StatefulSet API 物件中,並實現了 StatefulSet 控制器的修訂歷史跟蹤。此外,我們還添加了 Parallel Pod 管理策略,以支援需要具有唯一身份但不需要有序 Pod 建立和終止的有狀態應用程式。

StatefulSet 滾動更新和 Pod 管理策略

首先,我們將透過部署 ZooKeeper 叢集和 Kafka 叢集來演示如何使用 StatefulSet 滾動更新和 Pod 管理策略。

先決條件

要跟著操作,您需要設定一個至少有 3 個可排程節點的 Kubernetes 1.7 叢集。每個節點需要 1 CPU 和 2 GiB 可用記憶體。您還需要一個動態供應器,以允許 StatefulSet 控制器供應 6 個各 10 GiB 的持久卷 (PVs),或者您需要在部署 ZooKeeper 叢集或 Kafka 叢集之前手動供應 PVs。

部署 ZooKeeper 叢集

Apache ZooKeeper 是一個強一致性的分散式系統,供其他分散式系統用於叢集協調和配置管理。

注意:您可以使用此 zookeeper_mini.yaml 清單建立 ZooKeeper 叢集。您可以在 此處 瞭解更多關於在 Kubernetes 上執行 ZooKeeper 叢集的資訊,以及對 清單及其內容 的更深入解釋。

應用清單後,您將看到如下輸出。

$ kubectl apply -f zookeeper\_mini.yaml

service "zk-hs" created

service "zk-cs" created

poddisruptionbudget "zk-pdb" created

statefulset "zk" created

該清單使用 StatefulSet zk 建立了一個由三個 ZooKeeper 伺服器組成的叢集;一個無頭服務 zk-hs,用於控制叢集的域;一個服務 zk-cs,客戶端可以使用它連線到就緒的 ZooKeeper 例項;以及一個 PodDisruptionBudget zk-pdb,允許進行一次計劃中斷。(請注意,雖然這個叢集適用於演示目的,但其大小不適合生產使用。)

如果您在另一個終端中使用 kubectl get 觀察 Pod 建立,您會看到與 OrderedReady 策略(實現 StatefulSet 完整保證的預設策略)不同,zk StatefulSet 中的所有 Pod 都並行建立。

$ kubectl get po -lapp=zk -w

NAME           READY         STATUS        RESTARTS     AGE


zk-0           0/1             Pending      0                   0s


zk-0           0/1             Pending     0                  0s


zk-1           0/1             Pending     0                  0s


zk-1           0/1             Pending     0                  0s


zk-0           0/1             ContainerCreating      0                  0s


zk-2           0/1             Pending      0                  0s


zk-1           0/1             ContainerCreating     0                  0s


zk-2           0/1             Pending      0                  0s


zk-2           0/1             ContainerCreating      0                  0s


zk-0           0/1             Running     0                  10s


zk-2           0/1             Running     0                  11s


zk-1           0/1             Running      0                  19s


zk-0           1/1             Running      0                  20s


zk-1           1/1             Running      0                  30s


zk-2           1/1             Running      0                  30s

這是因為 zookeeper_mini.yaml 清單將 StatefulSet 的 podManagementPolicy 設定為 Parallel。

apiVersion: apps/v1beta1  
kind: StatefulSet  
metadata:  
   name: zk  

spec:  
   serviceName: zk-hs  

   replicas: 3  

   updateStrategy:  

       type: RollingUpdate  

   podManagementPolicy: Parallel  

 ...

許多分散式系統,如 ZooKeeper,不需要對其程序進行有序的建立和終止。您可以使用 Parallel Pod 管理策略來加速管理這些系統的 StatefulSets 的建立和刪除。請注意,當使用 Parallel Pod 管理時,如果 StatefulSet 控制器未能建立 Pod,它不會阻塞。當 StatefulSet 的 podManagementPolicy 設定為 OrderedReady 時,將執行有序的順序 Pod 建立和終止。

部署 Kafka 叢集

Apache Kafka 是一個流行的分散式流平臺。Kafka 生產者將資料寫入分割槽主題,這些主題以可配置的複製因子儲存在代理叢集上。消費者從儲存在代理上的分割槽中消費生成的資料。

注意:清單內容的詳細資訊可在 此處 找到。您可以在 此處 瞭解更多關於在 Kubernetes 上執行 Kafka 叢集的資訊。

要建立叢集,您只需下載並應用 kafka_mini.yaml 清單。應用清單後,您將看到如下輸出:

$ kubectl apply -f kafka\_mini.yaml

service "kafka-hs" created

poddisruptionbudget "kafka-pdb" created

statefulset "kafka" created

該清單使用 kafka StatefulSet 建立了一個由三個代理組成的叢集;一個無頭服務 kafka-hs,用於控制代理的域;以及一個 PodDisruptionBudget kafka-pdb,允許進行一次計劃中斷。這些代理配置為透過 zk-cs 服務連線到我們上面建立的 ZooKeeper 叢集。與上面部署的 ZooKeeper 叢集一樣,這個 Kafka 叢集適用於演示目的,但其大小可能不適合生產使用。

如果您觀察 Pod 的建立過程,您會注意到,與上面建立的 ZooKeeper 叢集一樣,Kafka 叢集也使用 Parallel podManagementPolicy。

$ kubectl get po -lapp=kafka -w

NAME           READY         STATUS        RESTARTS     AGE


kafka-0     0/1             Pending      0                   0s


kafka-0     0/1             Pending      0                  0s


kafka-1     0/1             Pending      0                  0s


kafka-1     0/1             Pending      0                  0s


kafka-2     0/1             Pending      0                  0s


kafka-0     0/1             ContainerCreating     0                  0s


kafka-2     0/1             Pending      0                  0s


kafka-1     0/1             ContainerCreating     0                  0s


kafka-1     0/1             Running     0                  11s


kafka-0     0/1             Running     0                  19s


kafka-1     1/1             Running     0                  23s


kafka-0     1/1             Running     0                  32s

生產和消費資料

您可以使用 kubectl run 執行 kafka-topics.sh 指令碼來建立一個名為 test 的主題。

$ kubectl run -ti --image=gcr.io/google\_containers/kubernetes-kafka:1.0-10.2.1 createtopic --restart=Never --rm -- kafka-topics.sh --create \

\> --topic test \

\> --zookeeper zk-cs.default.svc.cluster.local:2181 \

\> --partitions 1 \

\> --replication-factor 3

現在您可以使用 kubectl run 執行 kafka-console-consumer.sh 命令來監聽訊息。

$ kubectl run -ti --image=gcr.io/google\_containers/kubnetes-kafka:1.0-10.2.1 consume --restart=Never --rm -- kafka-console-consumer.sh --topic test --bootstrap-server kafka-0.kafka-hs.default.svc.cluster.local:9093

在另一個終端中,您可以執行 kafka-console-producer.sh 命令。

$kubectl run -ti --image=gcr.io/google\_containers/kubernetes-kafka:1.0-10.2.1 produce --restart=Never --rm \

\>   -- kafka-console-producer.sh --topic test --broker-list kafka-0.kafka-hs.default.svc.cluster.local:9093,kafka-1.kafka-hs.default.svc.cluster.local:9093,kafka-2.kafka-hs.default.svc.cluster.local:9093

第二個終端的輸出出現在第一個終端中。如果您在更新叢集時繼續生產和消費訊息,您會注意到沒有訊息丟失。當分割槽的主導者在單個代理更新時發生變化時,您可能會看到錯誤訊息,但客戶端會重試直到訊息被提交。這是由於 StatefulSet 滾動更新的有序、順序性,我們將在下一節中進一步探討。

更新 Kafka 叢集

StatefulSet 更新與 DaemonSet 更新類似,都是透過設定相應 API 物件的 spec.updateStrategy 來配置的。當更新策略設定為 OnDelete 時,當 StatefulSet 或 DaemonSet 中的 Pod 被刪除時,相應的控制器才會建立新的 Pod。當更新策略設定為 RollingUpdate 時,當 DaemonSet 或 StatefulSet 的 spec.template 欄位被修改時,控制器將刪除並重新建立 Pod。您可以使用滾動更新來更改 StatefulSet 或 DaemonSet 中 Pod 的配置(透過環境變數或命令列引數)、資源請求、資源限制、容器映象、標籤和/或註解。請注意,所有更新都是破壞性的,總是要求銷燬並重新建立 DaemonSet 或 StatefulSet 中的每個 Pod。StatefulSet 滾動更新與 DaemonSet 滾動更新不同,Pod 的終止和建立是有序且順序的。

您可以修補 Kafka StatefulSet 以將 CPU 資源請求減少到 250m。

$ kubectl patch sts kafka --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"250m"}]'

statefulset "kafka" patched

如果您觀察 StatefulSet 中 Pod 的狀態,您會看到每個 Pod 都以逆序(從序數最大的 Pod 開始,逐漸到最小的 Pod)被刪除並重新建立。控制器會等待每個已更新的 Pod 執行並就緒,然後才更新後續的 Pod。

$kubectl get po -lapp=kafka -w

NAME           READY         STATUS       RESTARTS     AGE


kafka-0     1/1             Running     0                   13m


kafka-1     1/1             Running     0                   13m


kafka-2     1/1             Running     0                   13m


kafka-2     1/1             Terminating     0                 14m


kafka-2     0/1             Terminating     0                 14m


kafka-2     0/1             Terminating     0                 14m


kafka-2     0/1             Terminating     0                 14m


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             ContainerCreating     0                 0s


kafka-2     0/1             Running     0                 10s


kafka-2     1/1             Running     0                 21s


kafka-1     1/1             Terminating     0                 14m


kafka-1     0/1             Terminating     0                 14m


kafka-1     0/1             Terminating     0                 14m


kafka-1     0/1             Terminating     0                 14m


kafka-1     0/1             Pending     0                 0s


kafka-1     0/1             Pending     0                 0s


kafka-1     0/1             ContainerCreating     0                 0s


kafka-1     0/1             Running     0                 11s


kafka-1     1/1             Running     0                 21s


kafka-0     1/1             Terminating     0                 14m


kafka-0     0/1             Terminating     0                 14m


kafka-0     0/1             Terminating     0                 14m


kafka-0     0/1             Terminating     0                 14m


kafka-0     0/1             Pending     0                 0s


kafka-0     0/1             Pending     0                 0s


kafka-0     0/1             ContainerCreating     0                 0s


kafka-0     0/1             Running     0                 10s


kafka-0     1/1             Running     0                 22s

請注意,在更新過程中,非計劃中斷不會導致意外更新。也就是說,StatefulSet 控制器將始終以正確的版本重新建立 Pod,以確保更新的順序得以保留。如果一個 Pod 被刪除,並且它已經更新過,它將從 StatefulSet 的 spec.template 的更新版本建立。如果該 Pod 尚未更新,它將從 StatefulSet 的 spec.template 的先前版本建立。我們將在以下章節中進一步探討這一點。

暫存更新

根據您的組織處理部署和配置修改的方式,您可能希望或需要在允許推出程序之前暫存 StatefulSet 的更新。您可以透過為 RollingUpdate 設定分割槽來實現此目的。當 StatefulSet 控制器檢測到 StatefulSet 的 updateStrategy 中存在分割槽時,它將僅將 StatefulSet 的 spec.template 的更新版本應用於其序號大於或等於分割槽值的 Pod。

您可以修補 kafka StatefulSet,為 RollingUpdate 更新策略新增一個分割槽。如果您將分割槽設定為大於或等於 StatefulSet 的 spec.replicas 的數字(如下所示),則您隨後對 StatefulSet 的 spec.template 執行的任何更新都將被暫存以進行滾動釋出,但 StatefulSet 控制器不會啟動滾動更新。

$ kubectl patch sts kafka -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'

statefulset "kafka" patched

如果您修補 StatefulSet 將請求的 CPU 設定為 0.3,您會注意到沒有 Pod 被更新。

$ kubectl patch sts kafka --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'

statefulset "kafka" patched

即使您刪除一個 Pod 並等待 StatefulSet 控制器重新建立它,您也會注意到該 Pod 是以當前 CPU 請求重新建立的。

$   kubectl delete po kafka-1


pod "kafka-1" deleted


$ kubectl get po kafka-1 -w

NAME           READY         STATUS                           RESTARTS     AGE


kafka-1     0/1             ContainerCreating     0                   10s


kafka-1     0/1             Running     0                 19s


kafka-1     1/1             Running     0                 21s



$ kubectl get po kafka-1 -o yaml

apiVersion: v1

kind: Pod

metadata:

   ...


       resources:


           requests:


               cpu: 250m


               memory: 1Gi

金絲雀釋出

通常,我們希望在全域性推出映象更新或配置更改之前,在應用程式的單個例項上驗證其是否按預期工作。如果您將上面建立的分割槽修改為 2,StatefulSet 控制器將推出一個 金絲雀,可用於驗證更新是否按預期工作。

$ kubectl patch sts kafka -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'

statefulset "kafka" patched

您可以觀察 StatefulSet 控制器更新 kafka-2 Pod,並在更新完成後暫停。

$   kubectl get po -lapp=kafka -w


NAME           READY         STATUS       RESTARTS     AGE


kafka-0     1/1             Running     0                   50m


kafka-1     1/1             Running     0                   10m


kafka-2     1/1             Running     0                   29s


kafka-2     1/1             Terminating     0                 34s


kafka-2     0/1             Terminating     0                 38s


kafka-2     0/1             Terminating     0                 39s


kafka-2     0/1             Terminating     0                 39s


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             Terminating     0                 20s


kafka-2     0/1             Terminating     0                 20s


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             Pending     0                 0s


kafka-2     0/1             ContainerCreating     0                 0s


kafka-2     0/1             Running     0                 19s


kafka-2     1/1             Running     0                 22s

分階段推出

與金絲雀釋出類似,您可以根據分階段的進展(例如,線性、幾何或指數釋出)推出更新。

如果您修補 kafka StatefulSet 將分割槽設定為 1,StatefulSet 控制器將再更新一個代理。

$ kubectl patch sts kafka -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'

statefulset "kafka" patched

如果將其設定為 0,StatefulSet 控制器將更新最後一個代理並完成更新。

$ kubectl patch sts kafka -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'

statefulset "kafka" patched

請注意,您不必將分割槽遞減 1。對於更大的 StatefulSet,例如,擁有 100 個副本的 StatefulSet,您可能會使用更像 100、99、90、50、0 這樣的進展。在這種情況下,您將暫存更新,部署一個金絲雀,推出 10 個例項,更新百分之五十的 Pod,然後完成更新。

清理

要刪除上面建立的 API 物件,您可以使用 kubectl delete 命令刪除用於建立 ZooKeeper 叢集和 Kafka 叢集的兩個清單。

$ kubectl delete -f kafka\_mini.yaml

service "kafka-hs" deleted

poddisruptionbudget "kafka-pdb" deleted

Statefulset “kafka” deleted


$ kubectl delete -f zookeeper\_mini.yaml

service "zk-hs" deleted

service "zk-cs" deleted

poddisruptionbudget "zk-pdb" deleted

statefulset "zk" deleted

根據設計,StatefulSet 控制器不會刪除任何持久卷宣告 (PVC):為 ZooKeeper 叢集和 Kafka 叢集建立的 PVC 必須手動刪除。根據您叢集的儲存回收策略,您可能還需要手動刪除支援 PV。

DaemonSet 滾動更新、歷史記錄和回滾

在本節中,我們將向您展示如何對 DaemonSet 執行滾動更新、檢視其歷史記錄,然後在錯誤部署後執行回滾。我們將使用 DaemonSet 在叢集中的每個 Kubernetes 節點上部署 Prometheus 節點匯出器。這些節點匯出器將節點指標匯出到 Prometheus 監控系統。為簡單起見,我們省略了 Prometheus 伺服器 的安裝以及與 DaemonSet Pod 通訊的服務

先決條件

要按照本節操作,您需要一個可用的 Kubernetes 1.7 叢集和 kubectl 1.7 或更高版本。如果您已經完成了第一節,可以使用相同的叢集。

DaemonSet 滾動更新首先,準備節點匯出器 DaemonSet 清單,以在叢集中的每個節點上執行 v0.13 Prometheus 節點匯出器

$ cat \>\> node-exporter-v0.13.yaml \<\<EOF

apiVersion: extensions/v1beta1  
kind: DaemonSet  
metadata:  
   name: node-exporter  

spec:  
   updateStrategy:  

       type: RollingUpdate  

   template:  

       metadata:  

           labels:  

               app: node-exporter  

           name: node-exporter  

       spec:  

           containers:  

           - image: prom/node-exporter:v0.13.0  

               name: node-exporter  

               ports:  

               - containerPort: 9100  

                   hostPort: 9100  

                   name: scrape  

           hostNetwork: true  

           hostPID: true


EOF

請注意,您需要透過顯式地將 DaemonSet .spec.updateStrategy.type 設定為 RollingUpdate 來啟用 DaemonSet 滾動更新功能。

應用清單以建立節點匯出器 DaemonSet

$ kubectl apply -f node-exporter-v0.13.yaml --record

daemonset "node-exporter" created

等待第一次 DaemonSet 推出完成

$ kubectl rollout status ds node-exporter  
daemon set "node-exporter" successfully rolled out

您應該會看到您的每個節點都執行著一個節點匯出器 Pod

$ kubectl get pods -l app=node-exporter -o wide

要對節點匯出器 DaemonSet 執行滾動更新,請準備一個包含 v0.14 Prometheus 節點匯出器的清單

$ cat node-exporter-v0.13.yaml ```  sed "s/v0.13.0/v0.14.0/g" \> node-exporter-v0.14.yaml

然後應用 v0.14 節點匯出器 DaemonSet

$ kubectl apply -f node-exporter-v0.14.yaml --record

daemonset "node-exporter" configured

等待 DaemonSet 滾動更新完成

$ kubectl rollout status ds node-exporter

...

Waiting for rollout to finish: 3 out of 4 new pods have been updated...  
Waiting for rollout to finish: 3 of 4 updated pods are available...  
daemon set "node-exporter" successfully rolled out

我們剛剛透過更新 DaemonSet 模板觸發了 DaemonSet 滾動更新。預設情況下,每次會終止一箇舊的 DaemonSet Pod 並建立一個新的 DaemonSet Pod。

現在,我們將透過將映象更新為無效值來導致推出失敗

$ cat node-exporter-v0.13.yaml | sed "s/v0.13.0/bad/g" \> node-exporter-bad.yaml


$ kubectl apply -f node-exporter-bad.yaml --record

daemonset "node-exporter" configured

請注意,推出從未完成

$ kubectl rollout status ds node-exporter   
Waiting for rollout to finish: 0 out of 4 new pods have been updated...  
Waiting for rollout to finish: 1 out of 4 new pods have been updated…

# Use ^C to exit

這種行為是預期的。我們之前提到,DaemonSet 滾動更新一次殺死並建立一個 Pod。由於新的 Pod 永遠無法變為可用,因此推出會被暫停,從而防止無效規範傳播到多個節點。StatefulSet 滾動更新在失敗部署方面實現了相同的行為。不成功的更新將被阻塞,直到透過回滾或透過使用有效規範向前滾動來糾正。

$ kubectl get pods -l app=node-exporter

NAME                                   READY         STATUS                 RESTARTS     AGE


node-exporter-f2n14     0/1             ErrImagePull     0                   3m


...


# N = number of nodes

$ kubectl get ds node-exporter  
NAME                       DESIRED     CURRENT     READY         UP-TO-DATE     AVAILABLE     NODE SELECTOR     AGE  

node-exporter     N                 N                 N-1             1                       N                     \<none\>                   46m

DaemonSet 歷史記錄、回滾和向前滾動

接下來,執行回滾。檢視節點匯出器 DaemonSet 的推出歷史記錄

$ kubectl rollout history ds node-exporter   
daemonsets "node-exporter"  
REVISION               CHANGE-CAUSE  

1                             kubectl apply --filename=node-exporter-v0.13.yaml --record=true  

2                             kubectl apply --filename=node-exporter-v0.14.yaml --record=true


3                             kubectl apply --filename=node-exporter-bad.yaml --record=true

檢查您想要回滾到的修訂版本的詳細資訊

$ kubectl rollout history ds node-exporter --revision=2  
daemonsets "node-exporter" with revision #2  
Pod Template:  
   Labels:             app=node-exporter  

   Containers:  

     node-exporter:  

       Image:           prom/node-exporter:v0.14.0  

       Port:             9100/TCP  

       Environment:               \<none\>  

       Mounts:         \<none\>  

   Volumes:           \<none\>

您可以快速回滾到透過 kubectl rollout history 找到的任何 DaemonSet 修訂版。

# Roll back to the last revision

$ kubectl rollout undo ds node-exporter   
daemonset "node-exporter" rolled back


# Or use --to-revision to roll back to a specific revision

$ kubectl rollout undo ds node-exporter --to-revision=2  
daemonset "node-exporter" rolled back

DaemonSet 的回滾是透過向前滾動完成的。因此,回滾後,DaemonSet 版本 2 變為版本 4(當前版本)

$ kubectl rollout history ds node-exporter   
daemonsets "node-exporter"  
REVISION               CHANGE-CAUSE  

1                             kubectl apply --filename=node-exporter-v0.13.yaml --record=true  

3                             kubectl apply --filename=node-exporter-bad.yaml --record=true  

4                             kubectl apply --filename=node-exporter-v0.14.yaml --record=true

節點匯出器 DaemonSet 現在再次健康

$ kubectl rollout status ds node-exporter  
daemon set "node-exporter" successfully rolled out


# N = number of nodes

$ kubectl get ds node-exporter

NAME                       DESIRED     CURRENT     READY         UP-TO-DATE     AVAILABLE     NODE SELECTOR     AGE  

node-exporter     N                 N                 N                 N                       N                     \<none\>                   46m

如果回滾時指定了當前 DaemonSet 修訂版本,則回滾將被跳過。

$ kubectl rollout undo ds node-exporter --to-revision=4  
daemonset "node-exporter" skipped rollback (current template already matches revision 4)

如果找不到 DaemonSet 修訂版本,您將看到來自 kubectl 的此抱怨。

$ kubectl rollout undo ds node-exporter --to-revision=10  
error: unable to find specified revision 10 in history

請注意,kubectl rollout history 和 kubectl rollout status 也支援 StatefulSet!

清理

$ kubectl delete ds node-exporter

DaemonSet 和 StatefulSet 的下一步

滾動更新和回滾彌補了 DaemonSet 和 StatefulSet 的一個重要功能空白。在規劃 Kubernetes 1.8 時,我們希望繼續專注於推進核心控制器達到 GA。這可能意味著一些高階功能請求(例如自動回滾、早期故障檢測)將被推遲,以確保核心控制器的一致性、可用性和穩定性。我們歡迎反饋和貢獻,因此請隨時在 Slack 上與我們聯絡,在 Stack Overflow 上提問,或在 GitHub 上開啟問題或拉取請求。