天天看點

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

作者 | 酒祝  阿裡雲技術專家、墨封  阿裡雲開發工程師

直播完整視訊回顧:

https://www.bilibili.com/video/BV1mK4y1t7WS/

關注“阿裡巴巴雲原生”公衆号,背景回複 “528” 即可下載下傳 PPT

5 月 28 日,我們發起了第 3 期 SIG Cloud-Provider-Alibaba 網研會直播。本次直播主要介紹了阿裡經濟體大規模應用上雲過程中遇到的核心部署問題、采取的對應解決方案,以及這些方案沉澱為通用化能力輸出開源後,如何幫助阿裡雲上的使用者提升應用部署釋出的效率與穩定性。

本文彙集了此次直播完整視訊回顧及資料下載下傳,并整理了直播過程中收集的問題和解答,希望能夠對大家有所幫助~

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

前言

随着近年來 Kubernetes 逐漸成為事實标準和大量應用的雲原生化,我們往往發現 Kubernetes 的原生 workload 對大規模化應用的支援并不十分“友好”。如何在 Kubernetes 上為應用提供更加完善、高效、靈活的部署釋出能力,成為了我們探索的目标。

本文将會介紹在阿裡經濟體全面接入雲原生的過程中,我們在應用部署方面所做的改進優化、實作功能更加完備的增強版 workload、并将其開源到社群,使得現在每一位 Kubernetes 開發者和阿裡雲上的使用者都能很便捷地使用上阿裡巴巴内部雲原生應用所統一使用的部署釋出能力。

阿裡應用場景與原生 workloads

阿裡巴巴容器化道路的起步在國内外都是比較領先的。容器這個技術概念雖然出現得很早,但一直到 2013 年 Docker 産品出現後才逐漸為人所熟知。而阿裡巴巴早在 2011 年就開始發展了基于 LXC 的容器技術,經過了幾代的系統演進,如今阿裡巴巴有着超過百萬的容器體量,這個規模在世界範圍内都是頂尖的。

随着雲技術發展和雲原生應用的興起,我們近兩年間逐漸将過去的容器遷到了基于 Kubernetes 的雲原生環境中。而在這其中,我們遇到了不少應用部署方面的問題。首先對于應用開發者來說,他們對遷移到雲原生環境的期望是:

  • 面向豐富業務場景的政策功能
  • 極緻的部署釋出效率
  • 運作時的穩定性和容錯能力

阿裡的應用場景非常複雜,基于 Kubernetes 之上生長着很多不同的 PaaS 二層,比如服務于電商業務的運維中台、規模化運維、中間件、Serverless、函數計算等,而每個平台都對部署、釋出要求各有不同。

我們再來看一下 Kubernete 原生所提供的兩種常用 workload 的能力:

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

簡單來說,Deployment 和 StatefulSet 在一些小規模的場景下是可以 work 的;但到了阿裡巴巴這種應用和容器的規模下,如果全量使用原生 workload 則是完全不現實的。目前阿裡内部容器叢集上的應用數量超過十萬、容器數量達到百萬,有部分重點核心應用甚至單個應用下就有上萬的容器。再結合上圖的問題,我們會發現不僅針對單個應用的釋出功能不足,而且當釋出高峰期大量應用同時在更新時,超大規模的 Pod 重建也成為一種“災難”。

阿裡自研的擴充 workloads

針對原生 workload 遠遠無法滿足應用場景的問題,我們從各種複雜的業務場景中抽象出共通的應用部署需求,據此開發了多種擴充 workload。在這些 workload 中我們做了大幅的增強和改進,但同時也會嚴格保證功能的通用化、不允許将業務邏輯耦合進來。

這裡我們重點介紹一下 CloneSet 與 Advanced StatefulSet。在阿裡内部雲原生環境下,幾乎全量的電商相關應用都統一采用 CloneSet 做部署釋出,而中間件等有狀态應用則使用了 Advanced StatefulSet 管理。

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

Advanced StatefulSet 顧名思義,是原生 StatefulSet 的增強版,預設行為與原生完全一緻,在此之外提供了原地更新、并行釋出(最大不可用)、釋出暫停等功能。而 CloneSet 則對标原生 Deployment,主要服務于無狀态應用,提供了最為全面豐富的部署釋出政策。

原地更新

CloneSet、Advanced StatefulSet 均支援指定 Pod 更新方式:

  1. ReCreate:重建 Pod 更新,和原生 Deployment/StatefulSet 一緻;
  2. InPlaceIfPossible:如果隻修改 image 和 metadata 中的 labels/annotations 等字段,則觸發 Pod 原地更新;如果修改了其他 template spec 中的字段,則退化到 Pod 重建更新;
  3. InPlaceOnly:隻允許修改 image 和 metadata 中的 labels/annotations 等字段,隻會使用原地更新。

所謂原地更新,就是在更新 template 模闆的時候,workload 不會把原 Pod 删除、建立,而是直接在原 Pod 對象上更新對應的 image 等資料。

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

如上圖所示,在原地更新的時候 CloneSet 隻會更新 Pod spec 中對應容器的 image,而後 kubelet 看到 Pod 中這個容器的定義發生了變化,則會把對應的容器停掉、拉取新的鏡像、并使用新鏡像建立啟動容器。另外可以看到在過程中,這個 Pod 的 sandbox 容器以及其他本次未更新的容器還一直處于正常運作狀态,隻有需要更新的容器會受到影響。

原地更新給我們帶來的好處實在太多了:

  • 首先就是釋出效率大大提升了,根據非完全統計資料,在阿裡環境下原地更新至少比完全重建更新提升了 80% 以上的釋出速度:不僅省去了排程、配置設定網絡、配置設定遠端盤的耗時,連拉取新鏡像的時候都得益于 node 上已有舊鏡像、隻需要拉取較少的增量 layer);
  • IP 不變、更新過程 Pod 網絡不斷,除本次更新外的其他容器保持正常運作;
  • Volume 不變,完全複用原容器的挂載裝置;
  • 保障了叢集确定性,使排布拓撲能通過大促驗證。

後續我們将會有專文講解阿裡在 Kubernetes 之上做的原地更新,意義非常重大。如果沒有了原地更新,阿裡巴巴内部超大規模的應用場景幾乎是無法在原生 Kubernetes 環境上完美落地的,我們也鼓勵每一位 Kubernetes 使用者都應該“體驗”一下原地更新,它給我們帶來了不同于 Kubernetes 傳統釋出模式的變革。

流式+分批釋出

前一章我們提到了,目前 Deployment 支援 maxUnavailable/maxSurge 的流式更新,而 StatefulSet 支援 partition 的分批更新。但問題在于,Deployment 無法灰階分批,而 StatefulSet 則隻能一個一個 Pod 串行釋出,沒辦法并行的流式更新。

首先要說的是,我們将 maxUnavailable 引入了 Advanced StatefulSet。原生 StatefulSet 的 one by one 釋出,大家其實可以了解為一個強制 maxUnavailable=1 的過程,而 Advanced StatefulSet 中如果我們配置了更大的 maxUnavailable,那麼就支援并行釋出更多的 Pod 了。

然後我們再來看一下 CloneSet,它支援原生 Deployment 和 StatefulSet 的全部釋出政策,包括 maxUnavailable、maxSurge、partition。那麼 CloneSet 是如何把它們結合在一起的呢?我們來看一個例子:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
# ...
spec:
  replicas: 5          # Pod 總數為 5
  updateStrategy:
    type: InPlaceIfPossible
    maxSurge: 20%      # 多擴出來 5 * 20% = 1 個 Pod (rounding up)
    maxUnavailable: 0  # 保證釋出過程 5 - 0 = 5 個 Pod 可用
    partition: 3       # 保留 3 個舊版本 Pod (隻釋出 5 - 3 = 2 個 Pod)           

針對這個副本數為 5 的 CloneSet,如果我們修改了 template 中的 image,同時配置:maxSurge=20%  maxUnavailable=0  partition=3。當開始釋出後:

  1. 先擴出來 1 個新版本的 Pod,5 個存量 Pod 保持不動;
  2. 新 Pod ready 後,逐漸把舊版本 Pod 做原地更新;
  3. 直到剩餘 3 個舊版本 Pod 時,因為滿足了 partition 終态,會把新版本 Pod 再删除 1 個;
  4. 此時 Pod 總數仍然為 5,其中 3 個舊版本、1 個新版本。

如果我們接下來把 partition 調整為 0,則 CloneSet 還是會先擴出 1 個額外的新版 Pod,随後逐漸将所有 Pod 更新到新版,最終再次删除一個 Pod,達到 5 個副本全量更新的終态。

釋出順序可配置

對于原生的 Deployment 和 StatefulSet,使用者是無法配置釋出順序的。Deployment 下的 Pod 釋出順序完全依賴于它修改 ReplicaSet 後的擴縮順序,而 StatefulSet 則嚴格按照 order 的反序來做一一更新。

但在 CloneSet 和 Advanced StatefulSet 中,我們增加了釋出順序的可配置能力,使使用者可以定制自己的釋出順序。目前可以通過以下兩種釋出優先級和一種釋出打散政策來定義順序:

  • 優先級(1):按給定 label key,在釋出時根據 Pod labels 中這個 key 對應的 value 值作為權重:
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
  # ...
  updateStrategy:
    priorityStrategy:
      orderPriority:
        - orderedKey: some-label-key           
  • 優先級(2):按 selector 比對計算權重,釋出時根據 Pod 對多個 weight selector 的比對情況計算權重總和:
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
  # ...
  updateStrategy:
    priorityStrategy:
      weightPriority:
      - weight: 50
        matchSelector:
          matchLabels:
            test-key: foo
      - weight: 30
        matchSelector:
          matchLabels:
            test-key: bar           
  • 打散:将比對 key-value 的 Pod 打散到不同批次中釋出:
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
  # ...
  updateStrategy:
    scatterStrategy:
    - key: some-label-key
      value: foo           

可能有同學會問為什麼要配置釋出順序呢?比如 zookeeper 這類應用在釋出時,需要先把所有非主節點更新,最後再更新主節點,這樣才能保證在整個釋出過程中隻會發生一次切主。這時使用者就可以通過流程打标、或者寫一個 operator 自動為 zookeeper 的 Pod 打上節點職責的标簽,而後配置非主節點的釋出權重較大,使得釋出時能夠盡量減少切主的次數。

sidecar 容器管理

輕量化容器也是阿裡巴巴在雲原生階段的一次重大改革,過去阿裡的容器絕大多數都是以“富容器”的方式運作的,所謂“富容器”即在一個容器中既運作業務、也跑着各種各樣的插件和守護程序。而在雲原生時代,我們在逐漸把原先“富容器”中的旁路插件拆分到獨立的 sidecar 容器中,使主容器逐漸回歸業務自身。

這裡對于拆分的好處就不贅述了,我們來看下另一個問題,就是拆分之後這些 sidecar 容器如何做管理呢?最直覺的方式是在每個應用的 workload 中顯示去定義 Pod 中需要的 sidecar,但這樣帶來的問題很多:

  1. 當應用和 workload 數量衆多時,我們很難統一的 sidecar 增減管理;
  2. 應用開發者不知道(甚至也不關心)自己的應用需要配置哪些 sidecar 容器;
  3. 當 sidecar 鏡像需要更新時,要把所有應用的 workload 全部更新一遍,很不現實。

是以,我們設計了 SidecarSet,将 sidecar 容器的定義與應用 workload 解耦。應用開發者們不再需要再關心自己的 workload 中需要寫哪些 sidecar 容器,而通過原地更新, sidecar 維護者們也可以自主地管理和更新 sidecar 容器。

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

開放能力應用

到了這裡,大家是不是對阿裡巴巴的應用部署模式有了一個基本的了解呢?其實上述的能力都已經開源到了社群,我們的項目就叫做

OpenKruise

,目前它已經提供了 5 種擴充 workload:

  • CloneSet :提供了更加高效、确定可控的應用管理和部署能力,支援優雅原地更新、指定删除、釋出順序可配置、并行/灰階釋出等豐富的政策,可以滿足更多樣化的應用場景;
  • Advanced StatefulSet :基于原生 StatefulSet 之上的增強版本,預設行為與原生完全一緻,在此之外提供了原地更新、并行釋出(最大不可用)、釋出暫停等功能;
  • SidecarSet :對 sidecar 容器做統一管理,在滿足 selector 條件的 Pod 中注入指定的 sidecar 容器;
  • UnitedDeployment :通過多個 subset workload 将應用部署到多個可用區;
  • BroadcastJob :配置一個 job,在叢集中所有滿足條件的 Node 上都跑一個 Pod 任務。

此外,我們還有更多的擴充能力還在開源的路上!近期,我們會将内部的 Advanced DaemonSet 開放到 OpenKruise 中,它在原生 DaemonSet 的 maxUnavailable 之上,額外提供了如分批、selector 等釋出政策,分批的功能使 DaemonSet 在釋出的時候能夠隻更新其中部分 Pod,而 selector 更是允許釋出的時候指定先在符合某些标簽的 node 上更新,這為我們在大規模叢集中更新 DaemonSet 帶來了灰階能力和穩定性的保障。

而後續,我們還計劃将阿裡巴巴内部擴充的 HPA、排程插件等通用化能力開放出來,讓每一位 Kubernetes 開發者和阿裡雲上的使用者都能很便捷地使用上阿裡内部開發應用的雲原生增強能力。

最後,我們也歡迎每一位雲原生愛好者來共同參與 OpenKruise 的建設。與其他一些開源項目不同,OpenKruise 并不是阿裡内部代碼的複刻;恰恰相反,OpenKruise Github 倉庫是阿裡内部代碼庫的 upstream。是以,每一行你貢獻的代碼,都将運作在阿裡内部的所有 Kubernetes 叢集中、都将共同支撐了阿裡巴巴全球頂尖規模的應用場景!

如何為雲原生應用帶來穩定高效的部署能力?前言阿裡應用場景與原生 workloads阿裡自研的擴充 workloads開放能力應用Q & A

(釘釘掃碼加入交流群)

Q & A

Q1:目前阿裡最大規模的業務 pod 數量有多少,釋出一次需要多少時間?

A1:這個隻能透露數量目前最大規模的單個應用下數量是以萬為機關的,一次釋出時間要看具體分批灰階的時長了。如果分批較多、觀察時間較長的話,可能是會持續一兩周的。

Q2:pod 的資源 request 和 limit 是怎麼配置的?request 和 limit 是什麼比例來配置?過多的 request 造成浪費,過少可能會導緻熱點 node 負載超高。

A2:這個主要還是根據應用的需求來定的,目前大部分線上應用都是 1:1 的關系,部分離線和job 類型的會配置 request>limit。

Q3:kruise 更新問題,更新 kurise apiversion 版本的情況下,原有的版本的部署如何更新?

A3:目前 kruise 中資源的 apiVersion 還都是統一的。我們計劃在今年下半年将部分較為成熟的 workload 進行版本更新,使用者在自己的 K8s 叢集内更新後,存量的舊版本資源會自動通過 conversion 更新到新版本。

Q4:OpenKruise 有提供 go-client 嗎?

A4:目前提供兩個方式:1. 引入 github.com/openkruise/kruise/pkg/client 包,下面有生成好的 clientset / informer / lister 等工具;2. 使用 controller-runtime 的使用者(包括 kubebuilder、operator-sdk),直接引入 github.com/openkruise/kruise-api 輕量化依賴,然後加到 scheme 裡就能直接用了。

Q5:阿裡 K8s 版本更新是如何做的?

A5:阿裡集團内部使用 Kube-On-Kube 的架構進行大規模的 Kubernetes 叢集管理,用一個元 K8s 叢集管理成百上千個業務 K8s 叢集。其中元叢集版本較為穩定,業務叢集會進行頻繁更新,業務叢集的更新流程事實上就是對元叢集中的 workloads(原生 workloads 以及 kruise workloads) 進行版本或配置更新,與正常情況我們對業務 workloads 的更新流程相似。

Q6:這個灰階之後,流量是怎麼切的?

A6:在原地更新前,kruise 會先通過 readinessGate 将 Pod 置為 not-ready,此時 endpoint 等控制器會感覺到并把 Pod 從端點摘掉。然後 kruise 更新 pod image 觸發容器重建,完成後再把 Pod 改為 ready。

Q7:daemonset 的分批是通過類似 deployment 的暫停功能實作的麼?統計已經釋出數量然後暫停,然後繼續,然後再暫停。

A7:總體過程上類似,更新過程中對新舊版本進行統計并判斷是否已達到指定終态。但相比 deployment,daemonset 需要處理比較複雜的邊界情況(例如初次釋出時叢集中并沒有指定的 Pod),具體細節可以持續關注我們即将開源的代碼。

Q8:多叢集釋出頁面上怎麼開始釋出的?

A8:直播中示範的是一個 demo 的釋出系統結合 Kruise Workloads 的例子,從互動上是通過使用者選擇對應的叢集,點選開始執行進行釋出;從實作上實際是對新版本的 YAML 與叢集中的 YAML 計算 diff 後 Patch 進叢集,再操作 DaemonSet 的控制字段(partition / paused 等),控制灰階程序。

阿裡巴巴雲原生 關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的公衆号。”