作者| 趙明山(立衡)
前言
OpenKruise 是阿里雲開源的雲原生應用自動化管理套件,也是當前托管在 Cloud Native Computing Foundation ( CNCF ) 下的 Sandbox 項目。它來自阿里巴巴多年來容器化、雲原生的技術沉淀,是阿里內部生產環境大規模應用的基於 Kubernetes 之上的標准擴展組件,也是緊貼上游社區標准、適應互聯網規模化場景的技術理念與最佳實踐。
OpenKruise 在 2021.5.20 發布了最新的 v0.9.0版本( ChangeLog ),上一篇文章我們介紹了新增 Pod 重啟、刪除防護等重磅功能,今天向大家介紹另一個核心特性,即 SidecarSet 基於上一個版本擴展了特別針對 Service Mesh 場景的支持。
背景:如何獨立升級 Mesh 容器
SidecarSet 是 Kruise 提供的獨立管理 Sidecar 容器的 workload。用戶通過 SidecarSet 能夠便利的完成對 Sidecar 容器的自動注入和獨立升級,詳情請參考:OpenKruise 官網
默認情況下,Sidecar 的獨立升級順序是先停止舊版本的容器,然后再創建新版本的容器。這種方式尤其適合不影響 Pod 服務可用性的 Sidecar 容器,例如日志收集 agent ,但是對於很多代理或運行時的 Sidecar 容器,如 Istio Envoy,這種升級方法就有問題了。Envoy 作為 Pod 中的一個 Proxy 容器代理了所有的流量,這種場景下如果直接重啟升級,Pod 服務的可用性必然會受到影響,因此需要考慮應用自身的發布和容量情況,無法完全獨立於應用做 Sidecar 的發布。
阿里巴巴集團內部擁有上萬的 Pod 都是基於 Service Mesh 來實現相互間的通信,由於 Mesh 容器升級會導致業務 Pod 的不可用,因而 Mesh 容器的升級將會極大阻礙 Service Mesh 的迭代。針對這種場景,我們同集團內部的 Service Mesh 團隊一起合作實現了 Mesh 容器的熱升級能力。本文將重點介紹在實現 mesh 容器熱升級能力的過程中 SidecarSet 是扮演了怎樣的重要角色。
SidecarSet 助力 Mesh 容器無損熱升級
Mesh 容器不能像日志采集類容器直接原地升級,其原因在於:Mesh 容器必須要不間斷地對外提供服務,而獨立升級方式會導致 Mesh 服務存在一段不可用時間。雖然社區中已有一些知名的 Mesh 服務如 Envoy 、Mosn 等默認能夠提供平滑升級的能力,但是這些升級方式無法與雲原生進行恰當地結合,且 kubernetes 本身也缺乏對此類 Sidecar 容器的升級方案。
OpenKruise SidecarSet 為此類 Mesh 容器提供了 Sidecar 熱升級機制,能夠通過雲原生的方式助力 Mesh 容器實現無損熱升級。
apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
metadata:
name: hotupgrade-sidecarset
spec:
selector:
matchLabels:
app: hotupgrade
containers:
- name: sidecar
image: openkruise/hotupgrade-sample:sidecarv1
imagePullPolicy: Always
lifecycle:
postStart:
exec:
command:
- /bin/sh
- /migrate.sh
upgradeStrategy:
upgradeType: HotUpgrade
hotUpgradeEmptyImage: openkruise/hotupgrade-sample:empty
- **upgradeType **: HotUpgrade 代表該 sidecar 容器的類型是 Hot upgrade ,即熱升級方案。
- **HotUpgradeEmptyImage **: 當熱升級 Sidecar 容器時,業務須要提供一個 empty 容器用於熱升級過程中的容器切換。Empty 容器同 Sidecar 容器具有相同的配置(鏡像地址除外),例如 command , lifecycle , probe 等。
SidecarSet 熱升級機制主要包含注入熱升級 Sidecar 容器和 Mesh 容器平滑升級兩個過程。
注入熱升級 Sidecar 容器
針對熱升級類型的 Sidecar 容器,在 Pod 創建時 SidecarSet Webhook 將會注入兩個容器:
- {Sidecar.name} -1: 如下圖所示 envoy -1,這個容器代表正在實際工作的 sidecar 容器,例如:envoy :1.16.0
- {Sidecar.name} -2: 如下圖所示 envoy-2,這個容器是業務提供的 HotUpgradeEmptyImage 容器,例如:empty :1.0
上述 Empty 容器在 Mesh 容器運行過程中,並沒有做任何實際的工作。
Mesh 容器平滑升級
熱升級流程主要分為一下三個步驟:
-
Upgrade: 將 Empty 容器替換為最新版本的 Sidecar 容器,例如:envoy-2.Image = envoy:1.17.0
-
Migration : 執行 Sidecar 容器的 PostStartHook 腳本,完成 mesh 服務的平滑升級
-
Reset: Mesh 服務平滑升級后,將老版本 Sidecar 容器替換為 Empty 容器,例如:envoy-1.Image = empty : 1.0
僅需上述三個步驟即可完成熱升級中的全部流程,若對 Pod 執行多次熱升級,則重復執行上述三個步驟即可。
Migration 核心邏輯
SidecarSet 熱升級機制不僅完成了 Mesh 容器的切換,並且提供了新老版本的協調機制( PostStartHook ),但是至此還只是萬里長征的第一步,Mesh 容器同時還需要提供 PostSartHook 腳本來完成 Mesh 服務自身的平滑升級(上述 Migration 過程),如:Envoy 熱重啟、Mosn 無損重啟。
Mesh 容器一般都是通過監聽固定端口來對外提供服務,此類 Mesh 容器的migration 過程可以概括為:通過 UDS 傳遞 ListenFD 和停止 Accpet 、開始排水。針對不支持熱重啟的 Mesh 容器可以參考此過程完成改造,邏輯圖如下:
熱升級 Migration Demo
不同 Mesh 容器對外提供的服務以及內部實現邏輯各有差異,進而具體的 Migration也有所不同,上述邏輯只是對其中一些要點做了一些總結,希望能對有需要的各位有所裨益,同時在 Github 上面我們也提供了一個熱升級 Migration Demo 以供參考,下面將對其中的一些關鍵代碼進行介紹。
1. 協商機制
Mesh 容器啟動邏輯首先就需要判斷第一次啟動還是熱升級平滑遷移過程,為了減少Mesh 容器溝通成本,Kruise 在兩個 sidecar 容器中注入了兩個環境變量 SIDECARSET_VERSION 和 SIDECARSET_VERSION_ALT ,通過判斷兩個環境變量的值來判斷是否是熱升級過程以及當前 sidecar 容器是新版本還是老版本。
// return two parameters:
// 1. (bool) indicates whether it is hot upgrade process
// 2. (bool ) when isHotUpgrading=true, the current sidecar is newer or older
func isHotUpgradeProcess() (bool, bool) {
// 當前sidecar容器的版本
version := os.Getenv("SIDECARSET_VERSION")
// 對端sidecar容器的版本
versionAlt := os.Getenv("SIDECARSET_VERSION_ALT")
// 當對端sidecar容器version是"0"時,表明當前沒有在熱升級過程
if versionAlt == "0" {
return false, false
}
// 在熱升級過程中
versionInt, _ := strconv.Atoi(version)
versionAltInt, _ := strconv.Atoi(versionAlt)
// version是單調遞增的int類型,新版本的version值會更大
return true, versionInt > versionAltInt
}
2. ListenFD 遷移
通過 Unix Domain Socket 實現 ListenFD 在不同容器間的遷移,此步同樣也是熱升級中非常關鍵的一步,代碼示例如下:
// 為了代碼的簡潔,所有的失敗都將不捕獲
/* 老版本sidecar通過Unix Domain Socket遷移ListenFD到新版本sidecar */
// tcpLn *net.TCPListener
f, _ := tcpLn.File()
fdnum := f.Fd()
data := syscall.UnixRights(int(fdnum))
// 與新版本sidecar容器通過 Unix Domain Socket建立鏈接
raddr, _ := net.ResolveUnixAddr("unix", "/dev/shm/migrate.sock")
uds, _ := net.DialUnix("unix", nil, raddr)
// 通過UDS,發送ListenFD到新版本sidecar容器
uds.WriteMsgUnix(nil, data, nil)
// 停止接收新的request,並且開始排水階段,例如:http2 GOAWAY
tcpLn.Close()
/* 新版本sidecar接收ListenFD,並且開始對外服務 */
// 監聽 UDS
addr, _ := net.ResolveUnixAddr("unix", "/dev/shm/migrate.sock")
unixLn, _ := net.ListenUnix("unix", addr)
conn, _ := unixLn.AcceptUnix()
buf := make([]byte, 32)
oob := make([]byte, 32)
// 接收 ListenFD
_, oobn, _, _, _ := conn.ReadMsgUnix(buf, oob)
scms, _ := syscall.ParseSocketControlMessage(oob[:oobn])
if len(scms) > 0 {
// 解析FD,並轉化為 net.TCPListener
fds, _ := syscall.ParseUnixRights(&(scms[0]))
f := os.NewFile(uintptr(fds[0]), "")
ln, _ := net.FileListener(f)
tcpLn, _ := ln.(net.TCPListener)
// 基於接收到的Listener開始對外提供服務,以http服務為例
http.Serve(tcpLn, serveMux)
}
已知 Mesh 容器熱升級案例
阿里雲服務網格( Alibaba Cloud Service Mesh,簡稱 ASM)提供了一個全托管式的服務網格平台,兼容社區 Istio 開源服務網格。當前,基於 OpenKruise SidecarSet 的熱升級能力,ASM 實現了數據平面 Sidecar 熱升級能力( Beta ),用戶可以在應用無感的情況下完成服務網格的數據平面版本升級,正式版也將於近期上線。除熱升級能力外,ASM 還支持配置診斷、操作審計、訪問日志、監控、服務注冊接入等能力,全方位提升服務網格使用體驗,歡迎您前往試用。
總結
雲原生中 Mesh 容器的熱升級一直都是迫切卻又棘手的問題,本文中的方案也只是阿里巴巴集團在此問題上的一次探索,在反饋社區的同時也希望能夠拋磚引玉,引發各位對此中場景的思考。同時,我們也歡迎更多的同學參與到 OpenKruise 社區來,共同建設一個場景更加豐富、完善的 K8s 應用管理、交付擴展能力,能夠面向更加規模化、復雜化、極致性能的場景。
- 熱升級Migration Demo:https://github.com/openkruise/samples
- Github:https://github.com/openkruise/kruise
- Official:https://openkruise.io/
- 釘釘交流群: