簡介: OpenKruise 是阿里雲開源的雲原生應用自動化管理套件,也是當前託管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 項目。它來自阿里巴巴多年來容器化、雲原生的技術沉澱,是阿里內部生產環境大規模應用的基於 Kubernetes 之上的標準擴展組件,也是緊貼上游社區標準、適應互聯網規模化場景的技術理念與最佳實踐。
前言
OpenKruise 是阿里雲開源的雲原生應用自動化管理套件,也是當前託管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 項目。它來自阿里巴巴多年來容器化、雲原生的技術沉澱,是阿里內部生產環境大規模應用的基於 Kubernetes 之上的標準擴展組件,也是緊貼上游社區標準、適應互聯網規模化場景的技術理念與最佳實踐。
OpenKruise 在 2021 年 3 月 4 日發佈了最新的 v0.8.0版本(ChangeLog),其中增強了 SidecarSet 的能力,特別是對日誌管理類 Sidecar 有了更加完善的支持。
背景
Sidecar 是雲原生中一種非常重要的容器設計模式,它將輔助能力從主容器中剝離出來成為單獨的 sidecar 容器。在微服務架構中,通常也使用 sidecar 模式將微服務中的配置管理、服務發現、路由、熔斷等通用能力從主程序中剝離出來,從而極大降低了微服務架構中的複雜性。隨着 Service Mesh 的逐步風靡,sidecar 模式也日益深入人心,在阿里巴巴集團內部也大量使用 sidecar 模式來管理諸如運維、安全、消息中間件等通用組件。
在 Kubernetes 集羣中,Pod 不僅可以實現主容器與 sidecar 容器的構建,同時提供了許多功能強大的 workload(例如:deployment、statefulset)來對 Pod 進行管理、升級。但是隨着 Kubernetes 集羣上的業務日益增多,sidecar 容器的種類與規模也隨之日益龐大,對線上 sidecar 容器的管理和升級成為了愈發繁雜的工作:
- 業務 Pod 裏面包含了運維、安全、代理等多個 sidecar 容器,業務線同學不僅要完成自身主容器的配置,而且還需要熟悉這些 sidecar 容器的配置,這不僅增加了業務同學的工作量,同時也無形增加了 sidecar 容器配置的風險。
- sidecar 容器的升級需要連同業務主容器一起重啓(deployment、statefulset 等 workload 基於 Pod 銷燬、重建的模式,來實現 Pod 的滾動升級),推動和升級支撐着線上數百款業務的 sidecar 容器,必然存在着極大的業務阻力。
- 作為 sidecar 容器的提供者對線上諸多各種配置以及版本的 sidecar 容器沒有直接有效的升級手段,這對 sidecar 容器的管理意味着極大的潛在風險。
阿里巴巴集團內部擁有着百萬級的容器數量連同上面承載的上千個業務,因此,sidecar 容器的管理與升級也就成為了亟待完善的主題。因此,我們總結了內部許多 sidecar 容器的通用化需求,並將其沉澱到 OpenKruise 上面,最終抽象為 SidecarSet 作為管理和升級種類繁多 sidecar 容器的利器。
OpenKruise SidecarSet
SidecarSet 是 OpenKruise 中針對 sidecar 抽象出來的概念,負責注入和升級 Kubernetes 集羣中的 sidecar 容器,是 OpenKruise 的核心 workload 之一。它提供了非常豐富的功能,用户使用 SidecarSet 可以非常方便實現 sidecar 容器的管理。主要特性如下:
- 配置單獨管理:為每一個 sidecar 容器配置單獨的 SidecarSet 配置,方便管理。
- 自動注入:在新建、擴容、重建 pod 的場景中,實現 sidecar 容器的自動注入。
- 原地升級:支持不重建 pod 的方式完成 sidecar 容器的原地升級,不影響業務主容器,幷包含豐富的灰度發佈策略
注意:針對 Pod 中包含多個容器的模式,其中對外提供主要業務邏輯能力的容器稱之為 主容器,其它一些如日誌採集、安全、代理等輔助能力的容器稱之為 Sidecar 容器。例如:一個對外提供 web 能力的 pod,nginx 容器提供主要的 web server 能力即為主容器,logtail 容器負責採集、上報 nginx 日誌即為 Sidecar 容器。本文中的 SidecarSet 資源抽象也是為解決 Sidecar 容器的一些問題。
- Sidecar logging architectures
=================================
應用日誌可以讓你瞭解應用內部的運行狀況,日誌對調試問題和監控集羣活動非常有用。應用容器化後,最簡單且最廣泛採用的日誌記錄方式就是寫入標準輸出和標準錯誤。
但是,在當前分佈式系統、大規模集羣的時代下,上述方案還不足以達到生產環境的標準。首先,對於分佈式系統而言,日誌都是分散在單個容器裏面,沒有一個統一彙總的地方。其次,如果發生容器崩潰、Pod 被驅逐等場景,會出現日誌丟失的情況。因此,需要一種更加可靠,獨立於容器生命週期的日誌解決方案。
Sidecar logging architectures 是將 logging agent 放到一個獨立的 sidecar 容器中,通過共享日誌目錄的方式,實現容器日誌的採集,然後存儲到日誌平台的後端存儲。
阿里巴巴以及螞蟻集團內部同樣也是基於這種架構實現了容器的日誌採集,下面我將介 紹OpenKruise SidecarSet 如何助力 Sidecar 日誌架構在 Kubernetes 集羣中的大規模落地實踐。
- 自動注入
========
OpenKruise SidecarSet 基於 Kubernetes AdmissionWebhook 機制實現了 sidecar 容器的自動注入,因此只要將 sidecar 配置到 SidecarSet 中,不管用户用 CloneSet、Deployment、StatefulSet 等任何方式部署,擴出來的 Pod 中都會注入定義好的 sidecar 容器。
Sidecar 容器的所有者只需要配置自身的 SidecarSet,就可以在業務無感知的情況下完成 sidecar 容器的注入,這種方式極大的降低了 sidecar 容器使用的門檻,也方便了 sidecar 所有者的管理工作。為了滿足 sidecar 注入的多種場景,SidecarSet 除 containers 之外還擴展瞭如下字段:
# sidecarset.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: test-sidecarset
spec:
# 通過selector選擇pod
selector:
matchLabels:
app: web-server
# 指定 namespace 生效
namespace: ns-1
# container definition
containers:
- name: logtail
image: logtail:1.0.0
# 共享指定卷
volumeMounts:
- name: web-log
mountPath: /var/log/web
# 共享所有卷
shareVolumePolicy: disabled
# 環境變量共享
transferEnv:
- sourceContainerName: web-server
# TZ代表時區,例如:web-server容器中存在環境變量 TZ=Asia/Shanghai
envName: TZ
volumes:
- name: web-log
emptyDir: {}
- Pod 選擇器支持 selector 來選擇要注入的 Pod,如示例中將選擇 labels[app] = web-server 的 pod,將 logtail 容器注入進去,也可以在所有的 pod 中添加一個 labels[inject/logtail] = true 的方式,來實現全局性的 sidecar 注入。namespace:sidecarSet 默認是全局生效的,如果只想對某一個 namespace 生效,則配置該參數。
- 數據卷共享共享指定卷:通過 volumeMounts 和 volumes 可以完成與主容器的特定卷的共享,如示例中通過共享 web-log volume 來達到日誌採集的效果。共享所有卷:通過 shareVolumePolicy = enabled | disabled 來控制是否掛載 pod 主容器的所有卷卷,常用於日誌收集等 sidecar,配置為 enabled 後會把應用容器中所有掛載點注入 sidecar 同一路經下(sidecar 中本身就有聲明的數據卷和掛載點除外)。
- 環境變量共享:可以通過 transferEnv 從其它容器中獲取環境變量,會把名為 sourceContainerName 容器中名為 envName 的環境變量拷貝到本 sidecar 容器,如示例中日誌 sidecar 容器共享了主容器的時區 TZ,這在海外環境中尤其常見。
注意:Kubernetes 社區對於已經創建的 Pod 不允許修改 container 數量,所以上述注入能力只能發生在 Pod 創建階段,對於已經創建的 Pod 需要通過重建的方式來注入。
- 原地升級
========
SidecarSet 不僅實現 sidecar 容器的注入,而且複用了 OpenKruise 中原地升級的特性,實現了在不重啓 Pod 和主容器的前提下單獨升級 sidecar 容器的能力。由於這種升級方式基本上能做到業務方無感知的程度,所以 sidecar 容器的升級已不再是上下交困的難題,從而極大解放了 sidecar 的所有者,提升了 sidecar 版本迭代的速度。
注意:Kubernetes 社區對於已經創建的 Pod 只允許修改 container.image 字段,因此對於 sidecar 容器的修改包含除 container.image 的其它字段,則需要通過 Pod 重建的方式,不能直接原地升級。
為了滿足一些複雜的 sidecar 升級場景,SidecarSet 除了原地升級以外,還提供了非常豐富的灰度發佈策略。
- 灰度發佈
========
灰度發佈應該算是日常發佈中最常見的一種手段,它能夠比較平滑的完成 sidecar 容器的發佈,尤其是在大規模集羣的場景下,強烈建議使用這種方式。下面是首批暫停,後續基於最大不可用滾動發佈的例子,假設一個有 1000 個 pod 需要發佈:
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: sidecarset
spec:
# ...
updateStrategy:
type: RollingUpdate
partition: 980
maxUnavailable: 10%
上述配置首先發布(1000 - 980)= 20 個 pod 之後就會暫停發佈,業務可以觀察一段時間發現 sidecar 容器正常後,調整重新 update SidecarSet 配置:
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: sidecarset
spec:
# ...
updateStrategy:
type: RollingUpdate
maxUnavailable: 10%
這樣調整後,對於餘下的 980 個 pod,將會按照最大不可用的數量(10% * 1000 = 100)的順序進行發佈,直到所有的 pod 都發布完成。
Partition 的語義是保留舊版本 Pod 的數量或百分比,默認為 0。這裏的 partition 不表示任何 order 序號。如果在發佈過程中設置了 partition:
- 如果是數字,控制器會將 (replicas - partition) 數量的 Pod 更新到最新版本。
- 如果是百分比,控制器會將 (replicas * (100% - partition)) 數量的 Pod 更新到最新版本。
MaxUnavailable 是發佈過程中保證的,同一時間下最大不可用的 Pod 數量,默認值為 1。用户可以將其設置為絕對值或百分比(百分比會被控制器按照 selected pod 做基數來計算出一個背後的絕對值)。
注意:maxUnavailable 和 partition 兩個值是沒有必然關聯。舉例:
- 當 {matched pod}=100,partition=50,maxUnavailable=10,控制器會發布 50 個 Pod 到新版本,但是發佈窗口為 10,即同一時間只會發佈 10 個 Pod,每發佈好一個 Pod 才會再找一個發佈,直到 50 個發佈完成。
- 當 {matched pod}=100,partition=80,maxUnavailable=30,控制器會發布 20 個 Pod 到新版本,因為滿足 maxUnavailable 數量,所以這 20 個 Pod 會同時發佈。
- 金絲雀發佈
=========
對於有金絲雀發佈需求的業務,可以通過 strategy.selector 來實現。方式:對於需要率先金絲雀灰度的 pod 打上固定的 labels[canary.release] = true,再通過
strategy.selector.matchLabels 來選中該 pod。
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: sidecarset
spec:
# ...
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
- canary.release: true
maxUnavailable: 10%
上述配置只會發佈打上金絲雀 labels 的容器,在完成金絲雀驗證之後,通過將 updateStrategy.selector 配置去掉,就會繼續通過最大不可用來滾動發佈。
- 打散發布
========
SidecarSet 對於 pod 的升級順序,默認按照如下規則:
- 對升級的 pod 集合,保證多次升級的順序一致。
- 選擇優先順序是(越小優先級越高):unscheduled < scheduled, pending < unknown < running, not-ready < ready, newer pods < older pods。
除了上述默認發佈順序之外,scatter 打散策略允許用户自定義將符合某些標籤的 Pod 打散到整個發佈過程中。比如,對於像 logtail 這種全局性的 sidecar container,一個集羣當中很可能注入了幾十個業務 pod,因此可以使用基於 應用名 的方式來打散 logtail 的方式進行發佈,實現不同應用間打散灰度發佈的效果,並且這種方式可以同最大不可用一起使用。
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: sidecarset
spec:
# ...
updateStrategy:
type: RollingUpdate
# 配置pod labels,假設所有的pod都包含labels[app_name]
scatterStrategy:
- key: app_name
value: nginx
- key: app_name
value: web-server
- key: app_name
value: api-gateway
maxUnavailable: 10%
注意:當前版本必須要列舉所有的應用名稱,我們將在下個版本支持只配置 label key 的智能打散方式。
- 實踐
======
阿里巴巴以及螞蟻集團內部已經大規模的使用 SidecarSet 來管理 sidecar 容器,下面我將通過日誌採集 Logtail sidecar 來作為一個示例。
- 基於 sidecarSet.yaml 配置文件創建 SidecarSet 資源。
# sidecarset.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: logtail-sidecarset
spec:
selector:
matchLabels:
app: nginx
updateStrategy:
type: RollingUpdate
maxUnavailable: 10%
containers:
- name: logtail
image: log-service/logtail:0.16.16
# when recevie sigterm, logtail will delay 10 seconds and then stop
command:
- sh
- -c
- /usr/local/ilogtail/run_logtail.sh 10
livenessProbe:
exec:
command:
- /etc/init.d/ilogtaild
- status
resources:
limits:
memory: 512Mi
requests:
cpu: 10m
memory: 30Mi
##### share this volume
volumeMounts:
- name: nginx-log
mountPath: /var/log/nginx
transferEnv:
- sourceContainerName: nginx
envName: TZ
volumes:
- name: nginx-log
emptyDir: {}
- 基於 pod.yaml 創建 Pod。
apiVersion: v1
kind: Pod
metadata:
labels:
# matches the SidecarSet's selector
app: nginx
name: test-pod
spec:
containers:
- name: nginx
image: log-service/docker-log-test:latest
command: ["/bin/mock_log"]
args: ["--log-type=nginx", "--stdout=false", "--stderr=true", "--path=/var/log/nginx/access.log", "--total-count=1000000000", "--logs-per-sec=100"]
volumeMounts:
- name: nginx-log
mountPath: /var/log/nginx
envs:
- name: TZ
value: Asia/Shanghai
volumes:
- name: nginx-log
emptyDir: {}
- 創建這個 Pod,你會發現其中被注入了 logtail 容器:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
test-pod 2/2 Running 0 118s
$ kubectl get pods test-pod -o yaml |grep 'logtail:0.16.16'
image: log-service/logtail:0.16.16
- 此時,SidecarSet status 被更新為:
$ kubectl get sidecarset logtail-sidecarset -o yaml | grep -A4 status
status:
matchedPods: 1
observedGeneration: 1
readyPods: 1
updatedPods: 1
- 更新 sidecarSet 中 sidecar container 的 image logtail:0.16.18。
$ kubectl edit sidecarsets logtail-sidecarset
# sidecarset.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: logtail-sidecarset
spec:
containers:
- name: logtail
image: log-service/logtail:0.16.18
- 此時,發現 pod 中的 logtail 容器已經被更新為了 logtail:0.16.18 版本,並且 pod 以及其它的容器沒有重啓。
$ kubectl get pods |grep test-pod
test-pod 2/2 Running 1 7m34s
$ kubectl get pods test-pod -o yaml |grep 'image: logtail:0.16.18'
image: log-service/logtail:0.16.18
$ kubectl describe pods test-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Killing 5m47s kubelet Container logtail definition changed, will be restarted
Normal Pulling 5m17s kubelet Pulling image "log-service/logtail:0.16.18"
Normal Created 5m5s (x2 over 12m) kubelet Created container logtail
Normal Started 5m5s (x2 over 12m) kubelet Started container logtail
Normal Pulled 5m5s kubelet Successfully pulled image "log-service/logtail:0.16.18"
總結
本次 OpenKruise v0.8.0 版本的升級,SidecarSet 特性主要是完善了日誌管理類 Sidecar 場景的能力,後續我們在持續深耕 SidecarSet 穩定性、性能的同時,也將覆蓋更多的場景,比如下一個版本將會增加針對 Service Mesh 場景的支持。同時,我們也歡迎更多的同學參與到 OpenKruise 社區來,共同建設一個場景更加豐富、完善的 K8s 應用管理、交付擴展能力,能夠面向更加規模化、複雜化、極致性能的場景。
作者:趙明山(立衡)
原文鏈接
本文為阿里雲原創內容,未經允許不得轉載