天天看點

OpenKruise v0.9.0 版本釋出:新增 Pod 重新開機、删除防護等重磅功能背景Pod 容器重新開機/重建級聯删除防護CloneSet 新增功能SidecarSet最後

OpenKruise v0.9.0 版本釋出:新增 Pod 重新開機、删除防護等重磅功能背景Pod 容器重新開機/重建級聯删除防護CloneSet 新增功能SidecarSet最後

作者 | 王思宇(酒祝)

Photo Creidt@ 王思宇(酒祝)

背景

OpenKruise

是阿裡雲開源的雲原生應用自動化管理套件,也是目前托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 項目。它來自阿裡巴巴多年來容器化、雲原生的技術沉澱,是阿裡内部生産環境大規模應用的基于 Kubernetes 之上的标準擴充元件,也是緊貼上遊社群标準、适應網際網路規模化場景的技術理念與最佳實踐。

OpenKruise 在 2021 年 5 月 20 日釋出了最新的 v0.9.0 版本(

ChangeLog

​),新增了 Pod 容器重新開機、資源級聯删除防護等重磅功能,本文以下對新版本做整體的概覽介紹。

Pod 容器重新開機/重建

“重新開機” 是一個很樸素的需求,即使日常運維的訴求,也是技術領域較為常見的 “恢複手段”。而在原生的 Kubernetes 中,并沒有提供任何對容器粒度的操作能力,Pod 作為最小操作單元也隻有建立、删除兩種操作方式。

有的同學可能會問,在雲原生時代,為什麼使用者還要關注容器重新開機這種運維操作呢?在理想的 Serverless 模式下,業務隻需要關心服務自身就好吧?

這來自于雲原生架構和過去傳統基礎基礎設施的差異性。在傳統的實體機、虛拟機時代,一台機器上往往會部署和運作多個應用的執行個體,并且機器和應用的生命周期是不同的;在這種情況下,應用執行個體的重新開機可能僅僅是一條 systemctl 或 supervisor 之類的指令,而無需将整個機器重新開機。然而,在容器與雲原生模式下,應用的生命周期是和 Pod 容器綁定的;即正常情況下,一個容器隻運作一個應用程序,一個 Pod 也隻提供一個應用執行個體的服務。

基于上述的限制,目前原生 Kubernetes 之下是沒有 API 來為上層業務提供容器(應用)重新開機能力的。而 Kruise v0.9.0 版本提供了一種單 Pod 次元的容器重新開機能力,相容 1.16 及以上版本的标準 Kubernetes 叢集。在

安裝或更新 Kruise

​ 之後,隻需要建立 ContainerRecreateRequest(簡稱 CRR) 對象來指定重新開機,最簡單的 YAML 如下:

apiVersion: apps.kruise.io/v1alpha1
kind: ContainerRecreateRequest
metadata:
  namespace: pod-namespace
  name: xxx
spec:
  podName: pod-name
  containers:
  - name: app
  - name: sidecar           

其中,namespace 需要與要操作的 Pod 在同一個命名空間,name 可自選。spec 中 podName 是 Pod 名字,containers 清單則可以指定 Pod 中一個或多個容器名來執行重新開機。

除了上述必選字段外,CRR 還提供了多種可選的重新開機政策:

spec:
  # ...
  strategy:
    failurePolicy: Fail
    orderedRecreate: false
    terminationGracePeriodSeconds: 30
    unreadyGracePeriodSeconds: 3
    minStartedSeconds: 10
  activeDeadlineSeconds: 300
  ttlSecondsAfterFinished: 1800           
  • failurePolicy:Fail 或 Ignore,預設 Fail;表示一旦有某個容器停止或重建失敗,CRR 立即結束。
  • orderedRecreate:預設 false;true 表示清單有多個容器時,等前一個容器重建完成了,再開始重建下一個。
  • terminationGracePeriodSeconds:等待容器優雅退出的時間,不填預設用 Pod 中定義的時間。
  • unreadyGracePeriodSeconds:在重建之前先把 Pod 設為 not ready,并等待這段時間後再開始執行重建。
    • 注:該功能依賴于 KruisePodReadinessGate 這個 feature-gate 要打開,後者會在每個 Pod 建立的時候注入一個 readinessGate。否則,預設隻會給 Kruise workload 建立的 Pod 注入 readinessGate,也就是說隻有這些 Pod 才能在 CRR 重建時使用 unreadyGracePeriodSeconds。
  • minStartedSeconds:重建後新容器至少保持運作這段時間,才認為該容器重建成功。
  • activeDeadlineSeconds:如果 CRR 執行超過這個時間,則直接标記為結束(未完成的容器标記為失敗)。
  • ttlSecondsAfterFinished:CRR 結束後,過了這段時間自動被删除掉。

實作原理:當使用者建立了 CRR 後,經過了 kruise-manager 中心端的初步處理,會被 Pod 所在節點上的 kruise-daemon 收到并開始執行。執行的過程如下:

  1. 如果 Pod 容器定義了 preStop,kruise-daemon 會先走 CRI 運作時 exec 到容器中執行 preStop。
  2. 如果沒有 preStop 或執行完成,kruise-daemon 調用 CRI 接口将容器停止。
  3. kubelet 感覺到容器退出,則會建立一個 “序号” 遞增的新容器,并開始啟動(以及執行 postStart)。
  4. kruise-daemon 感覺到新容器啟動成功,上報 CRR 重新開機完成。
OpenKruise v0.9.0 版本釋出:新增 Pod 重新開機、删除防護等重磅功能背景Pod 容器重新開機/重建級聯删除防護CloneSet 新增功能SidecarSet最後

上述的容器 “序号” 其實就對應了 Pod status 中 kubelet 上報的 restartCount。是以,在容器重新開機後會看到 Pod 的 restartCount 增加。另外,因為容器發生了重建,之前臨時寫到舊容器 rootfs 中的檔案會丢失,但是 volume mount 挂載卷中的資料仍然存在。

級聯删除防護

Kubernetes 的面向終态自動化是一把 “雙刃劍”,它既為應用帶來了聲明式的部署能力,同時也潛在地會将一些誤操作行為被終态化放大。例如它的 “級聯删除” 機制,即正常情況(非 orphan 删除)下一旦父類資源被删除,則所有子類資源都會被關聯删除:

  1. 删除一個 CRD,其所有對應的 CR 都被清理掉。
  2. 删除一個 namespace,這個命名空間下包括 Pod 在内所有資源都被一起删除。
  3. 删除一個 workload(Deployment/StatefulSet/...),則下屬所有 Pod 被删除。

類似這種 “級聯删除” 帶來的故障,我們已經聽到不少社群 K8s 使用者和開發者帶來的抱怨。對于任何一家企業來說,其生産環境發生這種規模誤删除都是不可承受之痛,阿裡巴巴也不例外。

是以,在 Kruise v0.9.0 版本中,我們将阿裡内部所做的防級聯删除能力輸出到社群,期望能為更多的使用者帶來穩定性保障。在目前版本中如果需要使用該功能,則在

的時候需要顯式打開

ResourcesDeletionProtection

這個 feature-gate。

對于需要防護删除的資源對象,使用者可以給其打上

policy.kruise.io/delete-protection

标簽,value 可以有兩種:

  • Always: 表示這個對象禁止被删除,除非上述 label 被去掉。
  • Cascading:這個對象如果還有可用的下屬資源,則禁止被删除。

目前支援的資源類型、以及 cascading 級聯關系如下:

OpenKruise v0.9.0 版本釋出:新增 Pod 重新開機、删除防護等重磅功能背景Pod 容器重新開機/重建級聯删除防護CloneSet 新增功能SidecarSet最後

CloneSet 新增功能

1. 删除優先級

controller.kubernetes.io/pod-deletion-cost

​ 是從 Kubernetes 1.21 版本後加入的 annotation,ReplicaSet 在縮容時會參考這個 cost 數值來排序。CloneSet 從 Kruise v0.9.0 版本後也同樣支援了這個功能。

使用者可以把這個 annotation 配置到 pod 上,它的 value 數值是 int 類型,表示這個 pod 相較于同個 CloneSet 下其他 pod 的 "删除代價",代價越小的 pod 删除優先級相對越高。沒有設定這個 annotation 的 pod 預設 deletion cost 是 0。

注意這個删除順序并不是強制保證的,因為真實的 pod 的删除類似于下述順序:

  1. 未排程 < 已排程
  2. PodPending < PodUnknown < PodRunning
  3. Not ready < ready
  4. 較小 pod-deletion cost < 較大 pod-deletion cost
  5. 處于 Ready 時間較短 < 較長
  6. 容器重新開機次數較多 < 較少
  7. 建立時間較短 < 較長

2. 配合原地更新的鏡像預熱

當使用 CloneSet 做應用原地更新時,隻會更新容器鏡像、而 Pod 不會發生重建。這就保證了 Pod 更新前後所在 node 不會發生變化,進而在原地更新的過程中,如果 CloneSet 提前在所有 Pod 節點上先把新版本鏡像拉取好,則在後續的釋出批次中 Pod 原地更新速度會得到大幅度提高。

在目前版本中如果需要使用該功能,則在

PreDownloadImageForInPlaceUpdate

這個 feature-gate。打開後,當使用者更新了 CloneSet template 中的鏡像、且釋出政策支援原地更新,則 CloneSet 會自動為這個新鏡像建立 ImagePullJob 對象(OpenKruise 提供的批量鏡像預熱功能),來提前在 Pod 所在節點上預熱新鏡像。

預設情況下 CloneSet 給 ImagePullJob 配置的并發度是 1,也就是一個個節點拉鏡像。如果需要調整,你可以在 CloneSet annotation 上設定其鏡像預熱時的并發度:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  annotations:
    apps.kruise.io/image-predownload-parallelism: "5"           

3. 先擴再縮的 Pod 置換方式

在過去版本中,CloneSet 的 maxUnavailable、maxSurge 政策隻對應用釋出過程生效。而從 Kruise v0.9.0 版本開始,這兩個政策同樣會對 Pod 指定删除生效。

也就是說,當使用者通過

podsToDelete

apps.kruise.io/specified-delete: true

方式(具體見官網文檔)來指定一個或多個 Pod 期望删除時,CloneSet 隻會在目前不可用 Pod 數量(相對于 replicas 總數)小于 maxUnavailable 的時候才執行删除。同時,如果使用者配置了 maxSurge 政策,則 CloneSet 有可能會先建立一個新 Pod、等待新 Pod ready、再删除指定的舊 Pod。

具體采用什麼樣的置換方式,取決于當時的 maxUnavailable 和實際不可用 Pod 數量。比如:

  • 對于一個 CloneSet

    maxUnavailable=2, maxSurge=1

    且有一個

    pod-a

    處于不可用狀态, 如果你對另一個

    pod-b

    指定删除, 那麼 CloneSet 會立即删除它,然後建立一個新 Pod。
  • maxUnavailable=1, maxSurge=1

    pod-a

    pod-b

    指定删除, 那麼 CloneSet 會先建立一個 Pod、等待它 ready,最後再删除

    pod-b

  • maxUnavailable=1, maxSurge=1

    pod-a

    處于不可用狀态, 如果你對這個

    pod-a

  • ...

4. 基于 partition 終态的高效復原

在原生的 workload 中,Deployment 自身釋出不支援灰階釋出,StatefulSet 有 partition 語義來允許使用者控制灰階更新的數量;而 Kruise workload 如 CloneSet、Advanced StatefulSet,也都提供了 partition 來支援灰階分批。

對于 CloneSet,Partition 的語義是保留舊版本 Pod 的數量或百分比。比如說一個 100 個副本的 CloneSet,在更新鏡像時将 partition 數值階段性改為 80 -> 60 -> 40 -> 20 -> 0,則完成了分 5 批次釋出。

但過去,不管是 Deployment、StatefulSet 還是 CloneSet,在釋出的過程中如果想要復原,都必須将 template 資訊(鏡像)重新改回老版本。後兩者在灰階的過程中,将 partition 調小會觸發舊版本更新為新版本,但再次 partition 調大則不會處理。

從 v0.9.0 版本開始,CloneSet 的 partition 支援了 “終态復原” 功能。如果在

的時候打開了

CloneSetPartitionRollback

這個 feature-gate,則當使用者将 partition 調大時,CloneSet 會将對應數量的新版本 Pod 重新復原到老版本。

這樣帶來的好處是顯而易見的:在灰階釋出的過程中,隻需要前後調節 partition 數值,就能靈活得控制新舊版本的比例數量。但需要注意的是,CloneSet 所依據的 “新舊版本” 對應的是其 status 中的 updateRevision 和 currentRevision:

  • updateRevision:對應目前 CloneSet 所定義的 template 版本。
  • currentRevision:該 CloneSet 前一次全量釋出成功的 template 版本。

5. 短 hash

預設情況下,CloneSet 在 Pod label 中設定的

controller-revision-hash

值為 ControllerRevision 的完整名字,比如:

apiVersion: v1
kind: Pod
metadata:
  labels:
    controller-revision-hash: demo-cloneset-956df7994           

它是通過 CloneSet 名字和 ControllerRevision hash 值拼接而成。通常 hash 值長度為 8~10 個字元,而 Kubernetes 中的 label 值不能超過 63 個字元。是以 CloneSet 的名字一般是不能超過 52 個字元的,如果超過了,則無法成功建立出 Pod。

在 v0.9.0 版本引入了

CloneSetShortHash

新的 feature-gate。如果它被打開,CloneSet 隻會将 Pod 中的

controller-revision-hash

的值隻設定為 hash 值,比如 956df7994,是以 CloneSet 名字的長度不會有任何限制了。(即使啟用該功能,CloneSet 仍然會識别和管理過去存量的 revision label 為完整格式的 Pod。)

SidecarSet

sidecar 熱更新功能

SidecarSet 是 Kruise 提供的獨立管理 sidecar 容器的 workload。使用者可以通過 SidecarSet,來在一定範圍的 Pod 中注入和更新指定的 sidecar 容器。

預設情況下,sidecar 的獨立原地更新是先停止舊版本的容器,然後建立新版本的容器。這種方式更加适合不影響Pod服務可用性的sidecar容器,比如說日志收集 agent,但是對于很多代理或運作時的 sidecar 容器,例如 Istio Envoy,這種更新方法就有問題了。Envoy 作為 Pod 中的一個代理容器,代理了所有的流量,如果直接重新開機更新,Pod 服務的可用性會受到影響。如果需要單獨更新 envoy sidecar,就需要複雜的 grace 終止和協調機制。是以我們為這種 sidecar 容器的更新提供了一種新的解決方案,即熱更新(hot upgrade)。

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
spec:
  # ...
  containers:
  - name: nginx-sidecar
    image: nginx:1.18
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/bash
          - -c
          - /usr/local/bin/nginx-agent migrate
    upgradeStrategy:
      upgradeType: HotUpgrade
      hotUpgradeEmptyImage: empty:1.0.0           
  • upgradeType: HotUpgrade代表該sidecar容器的類型是hot upgrade,将執行熱更新方案hotUpgradeEmptyImage: 當熱更新sidecar容器時,業務必須要提供一個empty容器用于熱更新過程中的容器切換。empty容器同sidecar容器具有相同的配置(除了鏡像位址),例如:command, lifecycle, probe等,但是它不做任何工作。
  • lifecycle.postStart: 狀态遷移,該過程完成熱更新過程中的狀态遷移,該腳本需要由業務根據自身的特點自行實作,例如:nginx熱更新需要完成Listen FD共享以及流量排水(reload)。

具體 sidecar 注入和熱更新流程,請參考

官網文檔

最後

了解上述能力的更多資訊,可以通路

。對 OpenKruise 感興趣的同學歡迎參與我們的社群建設,已經使用了 OpenKruise 項目的使用者請在

issue

​ 中登記。

釘釘搜尋群号 23330762 加入釘釘交流群!

繼續閱讀