做者 | 張翼飛 阿里雲技術專家
來源|阿里巴巴雲原生公衆號node
導讀:衆所周知,Kubernetes 是雲原生領域的基石,做爲容器編排的基礎設施,被普遍應用在 Serverless 領域。彈性能力是 Serverless 領域的核心競爭力,本次分享將重點介紹基於 Kubernetes 的 Serverless 服務中,如何優化 Pod 建立效率,提高彈性效率。golang
在進入主題以前,先簡單回顧下 Serverless 計算的定義。docker
從維基百科能夠了解到,Serverless 計算是雲計算的一種形態,由雲廠商管理服務器,向用戶動態分配機器資源,基於實際使用的資源量計費。緩存
用戶構建和運行服務時,不用考慮服務器,下降了用戶管理服務器的負擔。在業務高峯期經過平臺的彈性能力自動擴容實例,在業務低峯期自動縮容實例,下降資源成本。安全
下述是當前常見的 Serverless 計算產品的架構。
服務器
整個產品架構一般會有管控平面和數據平面兩層,管控平面服務開發者,管理應用生命週期,知足開發者對應用管理的需求,數據平面服務應用的訪問方,如開發者業務的用戶,知足應用的流量管理和訪問訴求。網絡
管控平面一般採用 Kubernetes 作資源管理和調度,master 一般是 3 節點,知足對高可用的需求,節點經過內網 SLB 訪問 K8s master。多線程
在節點層面,一般會有兩種類型的節點:架構
一種是運行 kubelet 的節點,如裸金屬服務器、虛擬機等,這類節點上會運行安全容器做爲 Pod 運行時,每一個 Pod 擁有獨立的 kernel,下降共享宿主機 kernel 帶來的安全風險。同時會經過雲產品 VPC 網絡或其餘網絡技術,在數據鏈路層隔離租戶的網絡訪問。經過 安全容器+二層網絡隔離,單個節點上能夠提供可靠的多租運行環境。併發
Serverless 產品會提供基於 K8s 的 PaaS 層,負責向開發者提供部署、開發等相關的服務,屏蔽 K8s 相關的概念,下降開發者開發、運維應用的成本。
在數據平面,用戶可經過 SLB 實現對應用實例的訪問。PaaS 層也一般會在該平面提供諸如流量灰度、A/B 測試等流量管理服務,知足開發者對流量管理的需求。
彈性能力是 Serverless 計算平臺的核心競爭力,須要知足開發者對 Pod 規模 的訴求,提供相似無限資源池的能力,同時還要知足建立 Pod 效率的訴求,及時響應請求。
Pod 規模可經過增長 IaaS 層資源來知足,接下來重點介紹提高 Pod 建立效率的技術。
先了解下 Pod 建立相關的場景,這樣能夠更有效經過技術知足業務訴求。
業務中會有兩種場景涉及到 Pod 建立:
Serverless 服務中,開發者關心的重點在於應用的生命週期,尤爲是建立和升級階段,Pod 建立效率會影響這兩個階段的總體耗時,進而影響開發者的體驗。面對突發流量時,建立效率的高低會對開發者服務的響應速度產生重要影響,嚴重者會使開發者的業務受損。
面對上述業務場景,接下來重點分析如何提高 Pod 建立效率。
總體分析下 Pod 建立的階段,按照影響 Pod 建立效率的優先級來依次解決。
這是簡化後的建立 Pod 流程:
當有 Pod 建立請求時,先進行調度,爲 Pod 選取最合適的節點。在節點上,先進行拉取鏡像的操做,鏡像在本地準備好後,再進行建立容器組的操做。在拉取鏡像階段,又依次分爲下載鏡像和解壓鏡像兩個步驟。
咱們針對兩種類型的鏡像進行了測試,結果以下:
從測試結果可看到,解壓鏡像耗時在整個拉取鏡像過程當中的佔比不容忽視,對於解壓前 248MB 左右的 golang:1.10 鏡像,解壓鏡像耗時居然佔到了拉取鏡像耗時的 77.02%,對於節解壓前 506MB 左右的 hadoop namenode 鏡像,解壓鏡像耗時和下載鏡像耗時各佔 40% 和 60% 左右,即對於拉取鏡像過程的總耗時也不容忽視。
接下來就分別針對上述過程的不一樣節點進行優化處理,分別從上述整個流程、解壓鏡像、下載鏡像等方面進行探討。
能夠快速想到的方法是進行鏡像預熱,在 Pod 調度到節點前預先在節點上準備好鏡像,將拉取鏡像從建立 Pod 的主鏈路中移除,以下圖:
能夠在調度前進行全局預熱,在全部節點上行提早拉取鏡像。也能夠在調度過程當中進行預熱,在肯定調度到的
節點後,在目標節點上拉取鏡像。
兩種方式無可厚非,可根據集羣實際狀況進行選擇。
社區裏 OpenKruise 項目即將推出鏡像預熱服務,能夠關注下。下述是該服務的使用方式:
經過 ImagePullJob CRD 下發鏡像預熱任務,指定目標鏡像和節點,可配置拉取的併發度、Job 處理的超時時間以及 Job Object 自動回收的時間。如果私有鏡像,可指定拉取鏡像時的 secret 配置。ImagePullJob 的 Events 會提鏡任務的狀態信息,可考慮適當增大 Job Object 自動回收的時間,便於經過 ImagePullJob Events 查看任務的處理狀態。
從剛纔看到的拉取鏡像的數據來看,解壓鏡像耗時會佔拉取鏡像總耗時很大的比例,測試的例子最大佔比到了 77%,因此須要考慮如何提高解壓效率。
先回顧下 docker pull 的技術細節:
在 docker pull 時,總體會進行兩個階段:
在解壓 image 層時,默認採用的 gunzip。
再簡單瞭解下 docker push 的過程:
gzip/gunzip 是單線程的壓縮/解壓工具,可考慮採用 pigz/unpigz 進行多線程的壓縮/解壓,充分利用多核優點。
containerd 從 1.2 版本開始支持 pigz,節點上安裝 unpigz 工具後,會優先用其進行解壓。經過這種方法,可經過節點多核能力提高鏡像解壓效率。
這個過程也須要關注 下載/上傳 的併發度問題,docker daemon 提供了兩個參數來控制併發度,控制並行處理的鏡像層的數量,--max-concurrent-downloads 和 --max-concurrent-uploads。默認狀況下,下載的併發度是 3,上傳的併發度是 5,可根據測試結果調整到合適的值。
使用 unpigz 後的解壓鏡像效率:
在相同環境下,golang:1.10 鏡像解壓效率提高了 35.88%,hadoop namenode 鏡像解壓效率提高了 16.41%。
一般內網的帶寬足夠大,是否有可能省去 解壓縮/壓縮 的邏輯,將拉取鏡像的耗時集中在下載鏡像方面?即適量增大下載耗時,縮短解壓耗時。
再回顧下 docker pull/push 的流程,在 unpack/pack 階段,能夠考慮將 gunzip 和 gzip 的邏輯去掉:
對於 docker 鏡像,若 docker push 時的鏡像是非壓縮的,則 docker pull 時是無需進行解壓縮操做,故要實現上述目標,就須要在 docker push 時去掉壓縮邏輯。
docker daemon 暫時不支持上述操做,咱們對 docker 進行了一番修改,在上傳鏡像時不進行壓縮操做,測試結果以下:
這裏重點關注解壓鏡像耗時,可看到 golang:1.10 鏡像解壓效率提高了 50% 左右,hadoop namenode 鏡像解壓效率替身掛了 28% 左右。在拉取鏡像總耗時方面,該方案有必定的效果。
小規模集羣中,提高拉取鏡像效率的重點須要放在提高解壓效率方面,下載鏡像一般不是瓶頸。而在大規模集羣中,因爲節點數衆多,中心式的 Image Registry 的帶寬和穩定性也會影響拉取鏡像的效率,以下圖:
下載鏡像的壓力集中在中心式的 Image Registry 上。
這裏介紹一種基於 P2P 的鏡像分發系統來解決上述問題,以 CNCF 的 DragonFly 項目爲例:
這裏有幾個核心組件:
它本質上是一箇中心式的 SuperNode,在 P2P 網絡中做爲 tracker 和 scheduler 協調節點的下載任務。同時它仍是一個緩存服務,緩存從 Image Registry 中下載的鏡像,下降節點的增長對 Image Registry 帶來的壓力。
它既是節點上下載鏡像的客戶端,同時又充當向其餘節點提供數據的能力,能夠將本地已有的鏡像數據按需提供給其餘節點。
在每一個節點上有個 Dfdaemon 組件,它本質上是一個 proxy,對 docker daemon 的拉取鏡像的請求實現透明代理服務,使用 Dfget 下載鏡像。
經過 P2P 網絡,中心式的 Image Registry 數據被緩存到 ClusterManager 中,ClusterManager 協調節點對鏡像的下載需求,將下載鏡像的壓力分攤到集羣節點上,集羣節點既是鏡像數據的拉取方,又是鏡像數據的提供方,充分利用內網帶寬的能力進行鏡像分發。
除了上述介紹到的方法,是否還有其餘優化方法?
當前節點上建立容器時,是須要先把鏡像所有數據拉取到本地,而後才能啓動容器。再考慮下啓動虛擬機的過程,即便是幾百 GB 的虛擬機鏡像,啓動虛擬機也一般是在秒級別,幾乎感覺不到虛擬機鏡像大小帶來的影響。
那麼容器領域是否也能夠用到相似的技術?
再看一篇發表在 usenix 上的題爲《Slacker: Fast Distribution with Lazy Docker Containers》 的 paper 描述:
Our analysis shows that pulling packages accounts for 76% of container start time, but only 6.4% of
that data is read.
該 paper 分析,在鏡像啓動耗時中,拉取鏡像佔比 76%,可是在啓動時,僅有 6.4% 的數據被使用到,即鏡像啓動時須要的鏡像數據量不多,須要考慮在鏡像啓動階段按需加載鏡像,改變對鏡像的使用方式。
對於「Image 全部 layers 下載完後才能啓動鏡像」,須要改成啓動容器時按需加載鏡像,相似啓動虛擬機的方式,僅對啓動階段須要的數據進行網絡傳輸。
但當前鏡像格式一般是 tar.gz 或 tar,而 tar 文件沒有索引,gzip 文件不能從任意位置讀取數據,這樣就不能知足按需拉取時拉取指定文件的需求,鏡像格式須要改成可索引的文件格式。
Google 提出了一種新的鏡像格式,stargz,全稱是 seeable tar.gz。它兼容當前的鏡像格式,但提供了文件索引,可從指定位置讀取數據。
傳統的 .tar.gz 文件是這樣生成的: Gzip(TarF(file1) + TarF(file2) + TarF(file3) + TarFooter))。分別對每一個文件進行打包,而後對文件組進行壓縮操做。
stargz 文件作了這樣的創新:Gzip(TarF(file1)) + Gzip(TarF(file2)) + Gzip(TarF(file3_chunk1)) + Gzip(F(file3_chunk2)) + Gzip(F(index of earlier files in magic file), TarFooter)。針對每一個文件進行打包和壓縮操做,同時造成一個索引文件,和 TarFooter 一塊兒進行壓縮。
這樣就能夠經過索引文件快速定位要拉取的文件的位置,而後從指定位置拉取文件。
而後在 containerd 拉取鏡像環節,對 containerd 提供一種 remote snapshotter,在建立容器 rootfs 層時,不經過先下載鏡像層再構建的方式,而是直接 mount 遠程存儲層,以下圖所示:
要實現這樣的能力,一方面須要修改 containerd 當前的邏輯,在 filter 階段識別遠程鏡像層,對於這樣的鏡像層不進行 download 操做,一方面須要實現一個 remote snapshotter,來支持對於遠程層的管理。
當 containerd 經過 remote snapshotter 建立容器時,省去了拉取鏡像的階段,對於啓動過程當中須要的文件,可對 stargz 格式的鏡像數據發起 HTTP Range GET 請求,拉取目標數據。
阿里雲實現了名爲 DADI 的加速器,相似上述的思想,目前應用在了阿里雲容器服務,實現了 3.01s 啓動
10000 個容器,完美杜絕了冷啓動的漫長等待。感興趣的讀者也參考該文章:https://developer.aliyun.com/article/742103
上述都是針對建立 Pod 過程提供的技術方案,對於升級場景,在現有的技術下,是否有效率提高的可能性?是否能夠達到下述效果,即免去建立 Pod 的過程,實現 Pod 原地升級?
在升級場景中,佔比較大的場景是僅升級鏡像。針對這種場景,可以使用 K8s 自身的 patch 能力。經過 patch image,Pod 不會重建,僅目標 container 重建,這樣就不用完整通過 調度+新建 Pod 流程,僅對須要升級的容器進行原地升級。
在原地升級過程當中,藉助 K8s readinessGates 能力,能夠控制 Pod 優雅下線,由 K8s Endpoint Controller 主動摘除即將升級的 Pod,在 Pod 原地升級後加入升級後的 Pod,實現升級過程當中流量無損。
OpenKruise 項目中的 CloneSet Controller 提供了上述能力:
開發者使用 CloneSet 聲明應用,用法相似 Deployment。在升級鏡像時,由 CloneSet Controller 負責執行 patch 操做,同時確保升級過程當中業務流量無損。
從業務場景出發,咱們瞭解了提高 Pod 建立效率帶來收益的場景。而後經過分析 Pod 建立的流程,針對不一樣的階段作相應的優化,有的放矢。
經過這樣的分析處理流程,使得能夠有效經過技術知足業務需求。
張翼飛,就任於阿里雲容器服務團隊,主要專一 Serverless 領域的產品研發。