OpenKruise v0.9.0 版本發佈:新增 Pod 重啓、刪除防禦等重磅功能

頭圖.jpg

做者 | 王思宇(酒祝)
Photo Creidt@ 王思宇(酒祝)html

背景


OpenKruise 是阿里雲開源的雲原生應用自動化管理套件,也是當前託管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 項目。它來自阿里巴巴多年來容器化、雲原生的技術沉澱,是阿里內部生產環境大規模應用的基於 Kubernetes 之上的標準擴展組件,也是緊貼上游社區標準、適應互聯網規模化場景的技術理念與最佳實踐。

OpenKruise 在 2021 年 5 月 20 日發佈了最新的 v0.9.0 版本(ChangeLog​),新增了 Pod 容器重啓、資源級聯刪除防禦等重磅功能,本文如下對新版本作總體的概覽介紹。
node

Pod 容器重啓/重建


「重啓」 是一個很樸素的需求,即便平常運維的訴求,也是技術領域較爲常見的 「恢復手段」。而在原生的 Kubernetes 中,並無提供任何對容器粒度的操做能力,Pod 做爲最小操做單元也只有建立、刪除兩種操做方式。

有的同窗可能會問,在雲原生時代,爲何用戶還要關注容器重啓這種運維操做呢?在理想的 Serverless 模式下,業務只須要關心服務自身就好吧?

這來自於雲原生架構和過去傳統基礎基礎設施的差別性。在傳統的物理機、虛擬機時代,一臺機器上每每會部署和運行多個應用的實例,而且機器和應用的生命週期是不一樣的;在這種狀況下,應用實例的重啓可能僅僅是一條 systemctl 或 supervisor 之類的指令,而無需將整個機器重啓。然而,在容器與雲原生模式下,應用的生命週期是和 Pod 容器綁定的;即常規狀況下,一個容器只運行一個應用進程,一個 Pod 也只提供一個應用實例的服務。

基於上述的限制,目前原生 Kubernetes 之下是沒有 API 來爲上層業務提供容器(應用)重啓能力的。而 Kruise v0.9.0 版本提供了一種單 Pod 維度的容器重啓能力,兼容 1.16 及以上版本的標準 Kubernetes 集羣。在安裝或升級 Kruise​ 以後,只須要建立 ContainerRecreateRequest(簡稱 CRR) 對象來指定重啓,最簡單的 YAML 以下:nginx

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 還提供了多種可選的重啓策略:
git

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,並等待這段時間後再開始執行重建。github

    • 注:該功能依賴於 KruisePodReadinessGate 這個 feature-gate 要打開,後者會在每一個 Pod 建立的時候注入一個 readinessGate。不然,默認只會給 Kruise workload 建立的 Pod 注入 readinessGate,也就是說只有這些 Pod 才能在 CRR 重建時使用 unreadyGracePeriodSeconds。
  • minStartedSeconds:重建後新容器至少保持運行這段時間,才認爲該容器重建成功。
  • activeDeadlineSeconds:若是 CRR 執行超過這個時間,則直接標記爲結束(未完成的容器標記爲失敗)。
  • ttlSecondsAfterFinished:CRR 結束後,過了這段時間自動被刪除掉。

實現原理:當用戶建立了 CRR 後,通過了 kruise-manager 中心端的初步處理,會被 Pod 所在節點上的 kruise-daemon 收到並開始執行。執行的過程以下:
api

  1. 若是 Pod 容器定義了 preStop,kruise-daemon 會先走 CRI 運行時 exec 到容器中執行 preStop。
  2. 若是沒有 preStop 或執行完成,kruise-daemon 調用 CRI 接口將容器中止。
  3. kubelet 感知到容器退出,則會新建一個 「序號」 遞增的新容器,並開始啓動(以及執行 postStart)。
  4. kruise-daemon 感知到新容器啓動成功,上報 CRR 重啓完成。

1.png

上述的容器 「序號」 其實就對應了 Pod status 中 kubelet 上報的 restartCount。所以,在容器重啓後會看到 Pod 的 restartCount 增長。另外,由於容器發生了重建,以前臨時寫到舊容器 rootfs 中的文件會丟失,可是 volume mount 掛載卷中的數據仍然存在。
bash

級聯刪除防禦


Kubernetes 的面向終態自動化是一把 「雙刃劍」,它既爲應用帶來了聲明式的部署能力,同時也潛在地會將一些誤操做行爲被終態化放大。例如它的 「級聯刪除」 機制,即正常狀況(非 orphan 刪除)下一旦父類資源被刪除,則全部子類資源都會被關聯刪除:
架構

  1. 刪除一個 CRD,其全部對應的 CR 都被清理掉。
  2. 刪除一個 namespace,這個命名空間下包括 Pod 在內全部資源都被一塊兒刪除。
  3. 刪除一個 workload(Deployment/StatefulSet/...),則下屬全部 Pod 被刪除。

相似這種 「級聯刪除」 帶來的故障,咱們已經聽到很多社區 K8s 用戶和開發者帶來的抱怨。對於任何一家企業來講,其生產環境發生這種規模誤刪除都是不可承受之痛,阿里巴巴也不例外。

所以,在 Kruise v0.9.0 版本中,咱們將阿里內部所作的防級聯刪除能力輸出到社區,指望能爲更多的用戶帶來穩定性保障。在當前版本中若是須要使用該功能,則在安裝或升級 Kruise 的時候須要顯式打開 ResourcesDeletionProtection 這個 feature-gate。

對於須要防禦刪除的資源對象,用戶能夠給其打上 policy.kruise.io/delete-protection 標籤,value 能夠有兩種:
併發

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

目前支持的資源類型、以及 cascading 級聯關係以下:

2.jpgapp

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 原地升級速度會獲得大幅度提升。

在當前版本中若是須要使用該功能,則在安裝或升級 Kruise 的時候須要顯式打開 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 指定刪除生效。

也就是說,當用戶經過 podsToDeleteapps.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。
  • 對於一個 CloneSet maxUnavailable=1, maxSurge=1 且有一個 pod-a 處於不可用狀態, 若是你對另外一個 pod-b 指定刪除, 那麼 CloneSet 會先新建一個 Pod、等待它 ready,最後再刪除 pod-b
  • 對於一個 CloneSet maxUnavailable=1, maxSurge=1 且有一個 pod-a 處於不可用狀態, 若是你對這個 pod-a 指定刪除, 那麼 CloneSet 會當即刪除它,而後建立一個新 Pod。
  • ...

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 支持了 「終態回滾」 功能。若是在安裝或升級 Kruise 的時候打開了 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 加入釘釘交流羣!

相關文章
相關標籤/搜索