OpenKruise 如何實現 K8s 社區首個規模化鏡像預熱能力

前言

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

OpenKruise 在 2021.3.4 發佈了最新的 v0.8.0 版本(ChangeLog),其中一個主要變動是新增了 鏡像預熱 能力。本文由《通過 OpenKruise 實現大規模集羣 鏡像預熱&部署發佈加速實踐》分享整理爲文字,爲大家介紹我們所提供的鏡像預熱能力的需求來源、實現方式以及使用場景。

背景:爲什麼鏡像預熱能力是必要的

“鏡像” 也算是 Docker 爲容器領域帶來的一大創新。在 Docker 之前,雖然 Linux 已經提供了 cgroup 隔離,儘管阿里巴巴從 2011 年已經逐漸基於 LXC 開始容器化,但都缺乏鏡像這種對運行環境的封裝。不過呢,儘管鏡像爲我們帶來了諸多好處,但不可否認在實際場景中我們也面臨各種各樣拉鏡像帶來的問題,其中最常見的一點就是拉鏡像的耗時。

我們過去聽到過很多用戶對容器化的期待和認識,比如 “極致彈性”、“秒級擴容”、“高效發佈” 等等,但結合 Kubernetes 中一個標準的 Pod 創建過程來看,和用戶的期望還是有一定差距的(假設 Pod 中包含 sidecar、app 兩個容器):

正常來說,對於小規模集羣中調度、分配/掛載遠程盤、分配網絡等操作耗時較小,對於大規模集羣需要做一定優化,但都還在可控的範圍內。然而對於拉鏡像的耗時,在大規模彈性的集羣中則尤爲棘手,即使採用了 P2P 等技術手段來優化,對於一個較大的業務鏡像還是可能需要較長的時間來拉取,與用戶所期望的擴容、發佈速度不符。

而我們如果能做到將 sidecar 容器的鏡像、以及業務容器的基礎鏡像提前在節點上拉取好,則 Pod 創建過程可以大幅縮短,其中拉鏡像的耗時甚至能優化 70% 以上:

而 Kubernetes 自身是沒有提供任何面向鏡像的操作能力的,圍繞 Kubernetes 的生態來看,目前也沒有比較成熟的規模化鏡像預熱產品。這是我們在 OpenKruise 中提供鏡像預熱的原因,並且這套鏡像預熱能力已經在阿里巴巴內部的雲原生環境大面積落地,在後文的實踐中也會簡單介紹我們的基本用法。

OpenKruise 是如何實現鏡像預熱的

OpenKruise 實現鏡像預熱的原理,要先從它的運行架構看起:

從 v0.8.0 開始,安裝了 Kruise 之後,有兩個在 kruise-system 命名空間下的組件:kruise-manager 與 kruise-daemon。前者是一個由 Deployment 部署的中心化組件,一個 kruise-manager 容器(進程)中包含了多個 controller 和 webhook;後者則由 DaemonSet 部署到集羣中的節點上,通過與 CRI 交互來繞過 Kubelet 完成一些擴展能力(比如拉取鏡像、重啓容器等)。

因此,Kruise 會爲每個節點(Node)創建一個同名對應的自定義資源:NodeImage,而每個節點的 NodeImage 裏寫明瞭在這個節點上需要預熱哪些鏡像,因此這個節點上的 kruise-daemon 只要按照 NodeImage 來執行鏡像的拉取任務即可:

如上圖所示,我們在 NodeImage 中能指定要拉取的鏡像名、tag、拉取的策略,比如單次拉取的超時、失敗重試次數、任務的 deadline、TTL 時間等等。

有了 NodeImage,我們也就擁有了最基本的鏡像預熱能力了,不過還不能完全滿足大規模場景的預熱需求。在一個有 5k 個節點的集羣中,要用戶去一個個更新 NodeImage 資源來做預熱顯然是不夠友好的。因此,Kruise 還提供了一個更高抽象的自定義資源 ImagePullJob:

如上圖所示,在 ImagePullJob 中用戶可以指定一個鏡像要在哪些範圍的節點上批量做預熱,以及這個 job 的拉取策略、生命週期等。一個 ImagePullJob 創建後,會被 kruise-manager 中的 imagepulljob-controller 接收到並處理,將其分解並寫入到所有匹配節點的 NodeImage 中,以此來完成規模化的預熱。

整體的流程如下:

而有了鏡像預熱能力後,我們怎麼去使用它,或者說什麼場景下需要來使用呢?接下來我們介紹下鏡像預熱在阿里巴巴中的幾種常見使用方式。

常見的鏡像預熱使用方式有哪些

1. 基礎鏡像 – 集羣維度預熱

最常見的預熱場景,是在整個集羣維度持續預熱一些基礎鏡像:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
  name: base-image-job
spec:
  image: xxx/base-image:latest
  parallelism: 10
  completionPolicy:
    type: Never
  pullPolicy:
    backoffLimit: 3
    timeoutSeconds: 300

如上述 ImagePullJob 有幾個特徵:

  1. 不配置 selector 規則,即默認整個集羣維度預熱存量的節點上統一預熱後續新增(導入)的節點上也會立即自動做預熱
  2. 採用 Never 的 completionPolicy 策略來長期運行Never 策略表明這個 job 持續做預熱,不會結束(除非被刪除)Never 策略下,ImagePullJob 每隔 24h 左右會觸發在所有匹配的節點上重試拉取一次,也就是每天都會確保一次鏡像存在

根據我們的經驗,一個集羣中預熱基礎鏡像的 ImagePullJob 在 10~30 個左右,具體視集羣以及業務場景而定。

2. sidecar 鏡像 – 集羣維度預熱

我們同樣也可以對一些 sidecar 的鏡像做預熱,尤其是那些基本上每個業務 Pod 中都會帶有的基礎 sidecar:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
  name: sidecar-image-job
spec:
  image: xxx/sidecar-image:latest
  parallelism: 20
  completionPolicy:
    type: Always
    activeDeadlineSeconds: 1800
    ttlSecondsAfterFinished: 300
  pullPolicy:
    backoffLimit: 3
    timeoutSeconds: 300

如上述 ImagePullJob 有幾個特徵:

  1. 不配置 selector,默認整個集羣維度預熱,這一點與基礎鏡像類似
  2. 採用 Always 策略一次性預熱所有節點做一次預熱整個 job 預熱超時時間 30minjob 完成後過 5min 自動刪除

當然,這裏的 sidecar 預熱也可以配置爲 Never 策略,視場景而定。以我們的經驗來看,尤其在 sidecar 做版本迭代、鏡像升級的時候,提前做一次規模化的鏡像預熱,可以大幅提升後續 Pod 擴容、發佈的速度。

3. 特殊業務鏡像 – 資源池維度預熱

對於一些多租的 Kubernetes 集羣中會存在多個不同的業務資源池,其中可能需要將一些特定的業務鏡像按資源池維度來預熱:

apiVersion: apps.kruise.io/v1alpha1
kind: ImagePullJob
metadata:
  name: serverless-job
spec:
  image: xxx/serverless-image:latest
  parallelism: 10
  completionPolicy:
    type: Never
  pullPolicy:
    backoffLimit: 3
    timeoutSeconds: 300
  selector:
    matchLabels:
      resource-pool: serverless

如上述 ImagePullJob 有幾個特徵:

  1. 採用 Never 策略長期預熱
  2. 指定 selector 預熱範圍,是匹配 resource-pool=serverless 標籤的節點

當然,這裏只是以資源池爲例,用戶可以根據自身的場景來定義在哪些節點上預熱某種鏡像。

版本前瞻:原地升級與預熱的結合

最後,再來介紹下 OpenKruise 的下個版本(v0.9.0)中,我們會基於當前的鏡像預熱實現怎樣的增強能力呢?

之前對 OpenKruise 瞭解過的同學一定知道,我們提供的一大特性就是 “原地升級”,即打破了 Kubernetes 原生 workload 發佈時必須將 Pod 刪除、重建的模式,支持在原 Pod 上只更新其中某個容器的鏡像。對原地升級原理感興趣的同學可以讀這篇文章:《揭祕:如何爲 Kubernetes 實現原地升級?》。

由於原地升級避免了 Pod 刪除、重建的過程,它本身已經能爲我們帶來了如下的好處:

  • 節省了調度的耗時,Pod 的位置、資源都不發生變化
  • 節省了分配網絡的耗時,Pod 還使用原有的 IP
  • 節省了分配、掛載遠程盤的耗時,Pod 還使用原有的 PV(且都是已經在 Node 上掛載好的)
  • 節省了大部分拉取鏡像的耗時,因爲節點上已經存在了應用的舊鏡像,當拉取新版本鏡像時只需要下載少數的幾層 layer
  • 原地升級 Pod 中某個容器時,其他容器保持正常運行,網絡、存儲均不受影響

其中,“節省了大部分拉取鏡像的耗時” 後,只需要下載新鏡像上層的部分 layer 即可。而我們有沒有可能把這個鏡像拉取時間徹底優化掉呢?答案是肯定的。

如上圖所示,在下個版本中 OpenKruise 的 CloneSet 將支持發佈過程自動鏡像預熱。當用戶還在灰度升級第一批 Pod 的時候,Kruise 會提前在後續 Pod 所在節點上把新版本的鏡像預熱好。這樣一來,在後續批次的 Pod 做原地升級時候,新鏡像都已經在節點上準備好了,也就節省了真正發佈過程中的拉鏡像耗時。

當然,這種 “發佈+預熱” 的模式也只適用於 OpenKruise 的原地升級場景。對於原生 workload 如 Deployment 而言,由於發佈時 Pod 是新建出來的,我們無法提前預知到它會被調度到的節點,自然也就沒辦法提前把鏡像預熱好了。

作者 | 王思宇(酒祝)

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章