阿里巴巴雲原生應用安全防護實踐與 OpenKruise 的新領域

得益於 Kubernetes 面向終態的理念,雲原生架構天然具備高度自動化的能力。然而,面向終態的自動化是一把“雙刃劍”,它既爲應用帶來了聲明式的部署能力,同時也潛在地會將一些誤操作行爲被終態化放大。
因此,充分了解雲原生環境下那些潛在的影響應用安全的問題,提前掌握多方位的安全防護、攔截、限流、熔斷等技術手段來保障雲原生應用的運行時穩定性至關重要。

本文整理自作者阿里雲容器服務技術專家,OpenKruise 作者 & 初創人員之一,Kubernetes、OAM 社區貢獻者王思宇(酒祝)於 1 月 19 日在阿里雲開發者社區“週二開源日”的直播分享,介紹了雲原生環境下應用安全與可用性的“處處危機”,分享阿里巴巴保障雲原生應用運行時穩定性經驗,並且詳細解讀了後續這些能力將如何通過 OpenKruise 賦能給開源。

雲原生環境應用安全“危機”

1. 阿里巴巴雲原生應用部署結構

這裏的雲原生應用部署結構是在阿里巴巴原生環境最簡化的抽象圖,如下圖所示。

首先我們來看幾個 CRD。CloneSet CRD 可以理解成 deployment 的一個 workload,也就是給應用部署 Pod 的模板。有了 CloneSet CRD 之後,不同的業務以及不同的應用會建立對應的 CloneSet,在 CloneSet 下面再建立對應的 Pod,以及對 Pod 做一些部署、發佈相關的管理。

除了 CloneSet 之外,還提供了 SidecarSet CRD,這個 CRD 做的事情是在業務 Pod 創建階段注入 SidecarSetCRD 中定義的 Sidecar 容器。也就是說,在 CloneSet 中業務只需要定義 Pod 中的 app 容器,也就是業務容器。在 Pod 創建過程中,通過 SidecarSet 在其中定義業務中要注入哪些 sidecar 容器。

2. OpenKruise:阿里巴巴應用部署基座

開源的 OpenKruise 是阿里巴巴應用部署的基座。OpenKruise 提供了多種的 workload。其中包括:CloneSet、Advanced StatefulSet、SidecarSet、Advanced DaemonSet。

  • CloneSet:是面向無狀態應用部署的工具,也是阿里巴巴中使用規模最大的部分,絕大部分泛電商業務都是通過 CloneSet 來部署發佈,包括 UC 神馬、餓了麼、電商業務等。
  • Advanced StatefulSet:針對一個原生 StatefulSet 兼容的增強版本,是面向有狀態應用部署的工具,目前主要是用於中間件在雲原生環境的部署。
  • SidecarSet:是在阿里巴巴環境中 sidecar 生命週期管理的工具。阿里巴巴的運維容器,以及阿里內部的 Mesh 容器,都是通過 SidecarSet 定義、部署以及注入到業務 Pod 中的。
  • Advanced DaemonSet:是針對原生 DaemonSet 兼容增強版本。將宿主機級別的守護進程部署到所有節點上,包括各種用於給業務容器配置網絡、存儲的基礎組件。

介紹完基礎環境之後,我們已經對雲原生部署結構有了一個基本的瞭解。下面,我們來了解在雲原生部署結構之下存在哪些雲原生應用安全危機。

3. 雲原生應用安全危機

1)workload 級聯刪除

Workload 級聯刪除,這一點不只針對於 Kruise 的 CloneSet,對於 Deployment,對於原生的 StatefulSet 都存在類似的問題。指的是當我們刪除一個 Workload 之後,假設使用採用默認刪除,沒有使用 orphan 刪除這種策略的話,底下的 Pod 都會被刪掉,這裏存在一種誤刪風險。也就是說,一旦某個 Deployment 被誤刪,那麼它底下的所有 Pod 都會級聯被刪掉,導致整個應用不可用。如果有多個 Workload 被刪掉,就可能導致很多個業務出現不可用的情況,這是一個對可用性造成的風險。如下圖所示:

2)namespace 級聯刪除

那麼我們再往上看,如果 Namespace 被刪掉,那麼整個 Namespace 底下的所有資源,包括 Deployment、CloneSet 這些 Workload,也包括 Pod、Service 等所有資源都會被刪除,這是一種很高的誤刪風險。

3)CRD 級聯刪除

如果有用 Helm 部署的同學可能會遇到過類似的情況,也就是如果你的 Helm 中包含了一些 CRD,這些 CRD 都被定義在 template 中, 那麼當 Helm uninstall 的時候,基本上這些 CRD 都會被 Helm 包級聯刪除掉,包括有人手動誤刪了某個 CRD,那麼 CRD 底下對應的 CR 都會被清理。這是一個很高的風險。

如果 CRD 是 CloneSet 這種 Workload 級別的 CRD,那麼一旦刪除這個 CRD 之後,會導致所有 CRD 底下的 CloneSet 的 CR 對象全部被刪掉,從而導致所有的業務 Pod 全部被刪掉。也就是說,刪除一個 Workload,只是這個 Workload 底下的 Pod 被刪掉;刪除一個 Namespace 可能只是 Namespace 底下的 Pod 被刪掉。但如果像阿里巴巴這種場景下,如果有人把 CloneSet 或者一些很關鍵的 CRD 刪掉的話 ,其實很可能導致整個集羣環境所有 NameSpace 底下的 Pod 都會被級聯刪掉,或者說都會處於應用不可用的狀態,造成雲原生環境對於應用可用性的風險。如下圖所示:

從上文可以看出來,雲原生這種理念架構爲我們帶來的好處是面向終態,也就是說我們定義終態,從而整個 Kubernetes 集羣就會向終態靠攏。而一旦出現一些誤操作導致定義了一種錯誤的終態,那麼 Kubernetes 也會向錯誤的終態靠攏,導致出現錯誤的結果,從而影響到整個應用的可用性。因此我們說,面向終態是一把“雙刃劍”。

4)併發 Pod 更新/驅逐/刪除

除了幾種誤刪的情況,還有更多針對可用性的風險。如下圖所示,假設左邊 CloneSetA 部署了兩個 Pod,這兩個 Pod 中又被 SidecarSet 注入了對應的 sidecar 容器。在這種情況下,如果通過 CloneSet 做應用發佈,假設說我們設置的 Max Available 是 50%,也就是說,兩個 Pod 是逐個升級,前一個升級完成,後一個才能開始升級,默認情況下這種發佈策略是沒有問題的。

但是如果 Pod 有多個 Owner,比如 CloneSet 是其中一個 Owner,CloneSet 對上面的 Pod 開始做原地升級,SidecarSet 對第二個 Pod 做 sidecar 的原地升級,那麼同一時刻可能這個應用的兩個 Pod 都在被升級。因爲在 CloneSet 定義了 Max Unavailable 是 50%,從它的視角來看,只要選取兩個 Pod 中的一個開始做升級。CloneSet 本身是無法感知到其它控制器甚至其他人爲的行爲去對其它 Pod 做操作,缺乏全局視角,每一個控制器都認爲自己在升級的 Pod 是符合升級策略,符合最大不可用測略。但當多個控制器同時開始工作的時候,可能會導致整個應用 100% 不可用。

如上圖右邊的情況,CloneSetC 底下有 3 個 Pod,如果它開始做升級的時候只升級其中一個 Pod,假設是重建升級,它會把舊版本 Pod 刪掉,先建新版本 Pod。在這過程中,假設另外兩個 Pod 一個可能被 Kubelet,或者 kube-controller-manager 中的 node lifecycle controller 驅逐,這時候已經有兩個 Pod 不可用,已經超過 Workload 中定義的最大不可用發佈策略。在這個過程中,還可能有一些 Pod 被其他一些控制器其他有人工手動刪除。種種可能性導致一個 Workload 下 Pod 的不可用數量,很可能是超過本身 workload 中定義的不可用發佈策略的。

也就是說,在 Deployment 中定義了 Max Unavailable 是 25%,那麼 Deployment 在發佈的時候,從它自身角度來看保證 25% 的 Pod 在被髮布。其他 75% 的 Pod 並不保證完全可用,這 75% 的 Pod 可能被 Kubelet 驅逐、可能被人爲手動刪除、可能被 SidecarSet 外部熱升級等等,種種情況可能會導致 Deployment 超過 50% 不可用,甚至更高,使整個應用受到影響。

雲原生應用安全防護實踐

針對以上種種危機,我們能採取怎麼樣的措施,保證原生環境下應用安全的可用性、安全性。下面介紹一些實踐的經驗。

1. 防護實踐 - 防級聯刪除

由於級聯刪除對應用可用性危害非常大,包括了刪除 CRD 節點,刪除 Namespace 節點,以及刪除 Workload 節點。防級聯刪除定義了針對多種資源,包括 CRD、Namespace、包括原生 Deployment 在內的各種 Workload 等,對這些資源提供了針對的 labels 定義。

下面是針對各種重要節點防級聯刪除的語名:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  labels:
    policy.kruise.io/disable-cascading-deletion: true

---

apiVersion: v1
kind: Namespace
metadata:
  labels:
    policy.kruise.io/disable-cascading-deletion: true

---

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    policy.kruise.io/disable-cascading-deletion: true

---

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  labels:
    policy.kruise.io/disable-cascading-deletion: true

labels 定義是關閉級聯刪除,用戶的任何 CRD、Namespace、workload 裏帶有防級聯刪除標識之後,kruise 就會保證資源防級聯刪除校驗。也就是說,當用戶刪除一個 CRD 時,如果這個 CRD 裏帶有防級聯刪除這個 label,那麼 kruise 就會去查看 CRD 底下是否還有存量 CR,如果有存量 CR 那麼 kruise 會禁止 CRD 刪除。

同理,在 Namespace 刪除時,也會校驗 Namespace 底下是否還有存量的運行狀態的 Pod,如果有,會禁止用戶直接刪除 Namespace。

對於 workload 邏輯相對簡單,就對於 Deployment、CloneSet、SidecarSet,當用戶去刪除 workload 時,如果 workload 中用戶已經定義了防級聯刪除的 label,那麼 kruise 會檢查 workload 的 replica 是否爲 0,如果 replica 大於 0,那麼 kruise 是禁止用戶直接刪除帶有防級聯刪除標識的 workload。也就是說,當一個存量 Deployment,如果 replicas 大於 0 的情況下,如果 Deployment 中存在帶有防級聯刪除標識,kruise 禁止用戶直接刪除。

如果真的需要刪除 Deployment 有兩種辦法:

  • 第一,先把 replica 調爲 “0”,這時底下 Pod 開始被刪除,這時刪除 Deployment 是沒問題的。
  • 第二,可以把 Deployment 中防級聯刪除標識去掉。

以上是關於防級聯刪除的介紹,大家應該將防級聯刪除理解成安全防護最基礎的一個策略,因爲級聯刪除是 Kubernetes 中非常危險的一個面向終態的能力。

2. 防護實踐 – Pod 刪除流控 & 熔斷

針對 Pod 刪除流控 & 熔斷的策略,指的是用戶調用、或用控制器用 K8s 去做 Pod 驅逐時,一旦出現誤操作或者出現邏輯異常,很可能導致在整個 K8s 集羣範圍內出現 Pod 大規模刪除的情況。針對這種情況做了 Pod 刪除留空策略,或者說是一個 CRD。這個 CRD 用戶可以定義在一個集羣中,不同的時間窗口內,最多有多少 Pod 允許被刪除。

apiVersion: policy.kruise.io/v1alpha1
kind: PodDeletionFlowControl
metadata:
  # ...
spec:
  limitRules:
  - interval: 10m
    limit: 100
  - interval: 1h
    limit: 500
  - interval: 24h
    limit: 5000
  whiteListSelector:
    matchExpressions:
    - key: xxx
      operator: In
      value: foo

如上面這個例子,10 分鐘之內最多允許 100 個 Pod 被刪除,1 小時之內最多允許 500 個 Pod 被刪除,24 小時內最多允許 5000 個 Pod 被刪除。當然也可以定義一些白名單,比如有些測試應用,每天頻繁地巡檢、測試,頻繁刪除會影響整個流控,可以提供一個白名單,符合白名單的應用不計算在窗口內。

除了白名單之外,可能 90% 的常規應用或者核心應用,是受到刪除流控保護的。一旦存在規模性誤刪除操作,就會被刪除流控以及熔斷機制保護。包括在保護之後或者觸發閾值之後,最好提供這種報警機制、監控機制,讓集羣的管理者能快速的感知到線上出現的熔斷事件。還包括幫助管理者去判斷熔斷事件是一個正常的事件,還是一個異常的事件。

如果在這段時間內,需要存在很多刪除請求,可以把對應策略值相應放大。如果真的是一些誤刪除,攔截到之後,及時根據請求來源做溯源,及時在搜索層面做熔斷,拒絕這些請求。

3. 防護實踐 - 應用維度不可用數量保護

對應用維度不可用數量保護,對於 K8s 原生,原生的 Kubernetes 提供了 PDB(PodDisruptionBudge) 策略,但是 PDB 只能攔截 Pod eviction 驅逐操作,也就是 Pod 驅逐操作。

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: xxx
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: xxx

上面的這個例子,假設其中有 5 個 Pod,這時定義了 minAvailable=2,就保證最少有 2 個 Pod 處於可用。一旦有 3 個 Pod 不可用,還剩下 2 個 Pod 可用,這時候如果 Pod eviction 針對存量 2 個 Pod 做驅逐,這個時候 PDB 會保護 Pod 可用性,拒絕這次驅逐操作。但是相應的如果對存量 2 個 Pod 做刪除或者原地升級,或者去做其他導致 Pod 不可用的事情,PDB 是沒有辦法攔截,尤其是針對 Pod 刪除請求,比 Pod 驅逐更爲常見,但是 PDB 是沒辦法攔截刪除等請求。

對於這些問題,阿里巴巴做了 PodUnavailableBudget 攔截操作,也就是 PUB。這裏的 Unavailable 能做的操作就更多了,基本上所有可能導致 Pod 不可用的操作,都在 PodUnavailableBudget 保護範圍內,包括了驅逐請求、Pod 刪除請求,應用原地升級、Sidecar 原地升級、容器重啓等,所有導致應用不可用的操作都會被 PUB 攔截。

如下面這個例子:

apiVersion: policy.kruise.io/v1alpha1
kind: PodUnavailableBudget
spec:
  #selector:
  #  app: xxx
  targetRef:
    apiVersion: apps.kruise.io
    kind: CloneSet
    name: app-xxx
  maxUnavailable: 25%
  # minAvailable: 15
status:
  deletedPods:
    pod-uid-xxx: "116894821"
  unavailablePods:
    pod-name-xxx: "116893007"
  unavailableAllowed: 2
  currentAvailable: 17
  desiredAvailable: 15
  totalReplicas: 20

定義了一個 PUB,這個 PUB 可以像原生 PDB 一樣寫一個 selector 範圍,也可以通過 targetRef 直接關聯到某一個 Workload,保護範圍就是在 Workload 底下的所有 Pod,同樣也可以定義最大不可用數量,以及最小可用數量。

假設對於 CloneSet 底下總共 20 個 Pod,當定義了 maxUnavailable:25% 時,一定要保證至少有 15 個 Pod 處於可用狀態。也就是說,PUB 會保證這 20 個 Pod 中最多有 5 個處於不可用狀態。回到我們之前在“危機”部分講到的一個例子,如果這 20 個 Pod 同時在被 Cloneset 發佈,以及被 Kubelet 驅逐,或是人工手動刪除,一旦 Pod 不可用數量超過 5 個,不管是 Kubelet 對剩餘 15 個 Pod 做驅逐,還是人爲手動刪除剩餘的某些 Pod,這些操作都會被 PUB 所攔截,這種策略能完全保證應用在部署過程中的可用性。PUB 可以保護的範圍比 PDB 大很多,包括在實際使用過程中預期之外的一些刪除請求、升級請求,從而保證整個應用在運行時的穩定性和可用性。

4. 防護實踐 - PUB/PDB 自動生成

對於真正的 Depoyment 應用開發者、運維人員來說,一般而言,只需要定義自身 workload 中 template,業務方只關心 Depoyment templatek 中業務的版本、環境變量、端口、提供的服務,但我們很難去強制每一個業務方在定義應用時,另外寫一個 PUB 或者 PDB 保護策略的 CR。那麼,我們怎樣對每一個應用提供自動保護呢?

在阿里巴巴內部,我們針對每個 Workload 提供自動生成 PUB/PDB 的能力。比如說,如果用戶此時新創建了一個 Deployment,會通過控制器自動爲該 Deployment 生成一個匹配的 PUB。這個自動生成的功能即能支持原生 Deployment/StatefulSet,也支持 Kruise 的 CloneSet / Advanced StatefulSet / UnitedDeployment。第二,默認根據 strategy 中 maxUnavailable 策略。第三,允許 annotation 中單獨定義保護策略。如下面的語句所示:

apiVersion: apps
kind: Deployment
metadata:
  name: deploy-foo
  annotations:
    policy.kruise.io/generate-pub: "true"
    policy.kruise.io/generate-pub-maxUnavailable: "20%"
    # policy.kruise.io/generate-pub-minAvailable: "80%"
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  # ...

---
# auto generate:
apiVersion: policy.kruise.io/v1alpha1
kind: PodUnavailableBudget
spec:
  targetRef:
    apiVersion: apps
    kind: Deployment
    name: deploy-foo
  maxUnavailable: 20%

自動生成的 PUB/PDB 內部填寫的 maxUnavailable,既可以讓用戶在 kruise 中指定定義。比如用戶可以直接把 http://kruise.io/generate-pub:"true",也可以 http://kruise.io/generate-pub-maxUnavailable:"20%",可以讓用戶指定應用最多允許有多少個不可用。這是用戶指定的策略。

如果用戶沒有指定策略,會根據在發佈策略中存在的maxUnavailable生成 PUB。就是指在發佈的階段,有多少個不可用數量,做爲應用運行時最大不可能數量。這是允許單獨定義策略。

OpenKruise 的新領域

1. OpenKruise 介紹

最後,和大家介紹上述開放的能力在 OpenKruise 新領域如何去開放,以及怎麼拓展對 OpenKruise 的認知。OpenKruise 是阿里雲開源的 Kubernetes 擴展應用負載項目,本質上是圍繞 Kubernetes 雲原生應用去做一系列自動化能力的引擎,同時也是阿里巴巴經濟體上雲全面使用的部署基座。

OpenKruise 的定位,做的不是一個完整的平臺,更類似於是 Kubernetes 中一個拓展的產品。這個拓展的產品作爲一個 add on 的組件,提供了一系列針對在 Kubernetes 中部署應用,以及後續保護防護應用可用、圍繞雲原生應用的一些自動化的能力,這些拓展能力或者增強能力,是原生 Kubernetes 所不具備,但也是迫切需要它所擁有這些能力,是阿里巴巴內部在雲原生逐漸演進過程中去沉澱的一些通用能力。

目前,Kruise 提供了以下 workload 控制器:

  • CloneSet:提供了更加高效、確定可控的應用管理和部署能力,支持優雅原地升級、指定刪除、發佈順序可配置、並行/灰度發佈等豐富的策略,可以滿足更多樣化的應用場景。
  • Advanced StatefulSet:基於原生 StatefulSet 之上的增強版本,默認行爲與原生完全一致,在此之外提供了原地升級、並行發佈(最大不可用)、發佈暫停等功能。
  • SidecarSet:對 sidecar 容器做統一管理,在滿足 selector 條件的 Pod 中注入指定的 sidecar 容器。
  • UnitedDeployment:通過多個 subset workload 將應用部署到多個可用區。
  • BroadcastJob:配置一個 job 在集羣中所有滿足條件的 Node 上都跑一個 Pod 任務。
  • Advanced DaemonSet:基於原生 DaemonSet 之上的增強版本,默認行爲與原生一致,在此之外提供了灰度分批、按 Nodelabel 選擇、暫停、熱升級等發佈策略。
  • AdvancedCronJob:一個擴展的 CronJob 控制器,目前 template 模板支持配置使用 Job 或 BroadcastJob。

2. 原生 workload 能力缺陷

根據 Deployment 去 CloneSet、AdcancedStatefulSet 是因爲原生 workload 能力缺陷有很多。大家可以看到,基本上從 Kubernetes 1.10 版本之後,其實其他的功能,包括 pod 裏面,它的字段還是在不斷豐富,包括更多的 pod 的能力支持、更多的策略等,但是對於 workload 層面,就是 deployment 和 StatefulSet 層面,已經不傾向於做任何改動。社區在這背後的考慮是因爲在不同公司、不同業務場景下,應用部署發佈層面需求很多。

Kubernetes 原生提供的 Deployment,是面向一些最通用最基礎的一些環境,沒辦法用它去滿足所有的業務場景,但實際上社區是非常鼓勵有更高需求,更大更復雜場景規模需求的用戶,自行通過 CRD 去拓展編寫,利用更強大的 workload,來滿足不同的業務的場景需求。

3. OpenKruise與原生能力對比

橙色:開源中 / 綠色:已開源

那麼,對於這場景而言,Kruise 已經做了比較完備的一個無狀態以及有狀態應用的部署,通過上圖表格能看到 Kruise 提供的 workload 和原生 deployment、StatefulSet、DaemonSet 的對比。

4. OpenKruise 2021 規劃

如上圖所示,OpenKruise 是一個雲原生應用自動化引擎,目前提供的 workload 能力在應用部署,但不會僅侷限於應用部署這一個領域的。

1)風險防控

在 2021 年上半年的規劃中,我們會針對上面講到的雲原生應用的風險和防控的策略,會通過 OpenKruise 輸出給社區。包括 CRD 刪除防護、級聯刪除防護、全局 Pod 刪除流控、Pod 刪除/驅逐/原地升級防護、自動爲 workload 生成 PDB/PUB 等。

2)Kruise-daemo

除此之外之前 OpenKruise 只是作爲一箇中心的控制器部署,下個版本中會提供一個 Kruise-daemon 通過 daemon set 部署到每個節點上,可以幫用戶去做一些鏡像預熱,發佈加速,容器重啓 ,單機調度優化的一些策略。

3)ControllerMesh

ControllerMesh 是 OpenKruise 提供出來幫助用戶管理用戶集羣中其他運行時的一些控制器運行時的能力,通過流量控制等方式解決傳統控制器單住模式帶來的種種問題。

最後,在 OpenKruise 項目社區建設方面,已經在 2020 年 11 月 11 號經 CNCF 技術監督委員會全體成員投票,一致同意正式進入 CNCF Sanbox,在整個過程中也得到了 CNCF 積極的迴應,表示 OpenKruise 項目與 CNCF 倡導的理念很契合,鼓勵有更多像 OpenKruise 這樣能做一些通用化的,面向更復雜的場景,更大規模的一些這種自主的 Workload 能力的項目出現。

現在已經有很多公司在使用 OpenKruise 的這些能力,比如:

  • 基於原地升級、灰度發佈等需求,攜程在生產環境使用CloneSet、AdvancedStatefulSet 來分別管理無狀態、有狀態應用的服務,單集羣 Kruise workload 數量達到萬級別。
  • OPPO 公司不僅大規模使用了 OpenKruise,還在下游配合其定製化的 Kubernetes 進一步加強了原地升級的能力,廣泛應用在多個業務的後端運行服務中,通過原地更新覆蓋了 87% 左右的升級部署需求。
  • 此外,國內的用戶還有蘇寧、鬥魚 TV、有贊、比心、Boss 直聘、申通、小紅書、VIPKID、掌門教育、杭銀消費、萬翼 科技、多點 Dmall、佐疆科技、享住智慧、艾佳生活、永輝科技中心、跟誰學,國外的用戶有 Lyft、Bringg、 Arkane Systems 等。
  • Maintainer 5 位成員來自阿里巴巴、騰訊、Lyft
  • 51 位貢獻者
    • 國內:阿里雲、螞蟻集團、攜程、騰訊、拼多多...
    • 國外:微軟、Lyft、Spectro Cloud、Dsicord...
  • 2000+ GitHub Stars
  • 300+ Forks

作者 | 王思宇(酒祝)

原文鏈接

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

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