作者 | 楊育兵(沈陵) 阿裡巴巴進階技術專家
我們從 2016 年開始在集團推廣全面的鏡像化容器化,今年是集團全面鏡像化容器化後的第 4 個 雙11,PouchContainer 容器技術已經成為集團所有線上應用運作的運作時底座和運維載體,每年 雙11 都有超過百萬的 PouchContainer 容器同時線上,提供電商和所有相關的線上應用平穩運作的載體,保障大促購物體驗的順滑。
我們通過 PouchContainer 容器運作時這一層标準建構了應用開發和基礎設施團隊的标準界面,每年應用都有新的需求、新的變化,同時基礎設施也有上雲/混部/神龍/存儲計算分離/網絡變革這些更新,兩邊平行演進,互不幹擾。技術設施和 PouchContainer 自身都做了很大的架構演進,這些很多的架構和技術演進對應用開發者都是無感覺的。
在容器技術加持的雲原生形成趨勢的今天,PouchContainer 容器技術支援的業務方也不再隻有集團電商業務和線上業務了,我們通過标準化的演進,把所有定制功能做了插件化,适配了不同場景的需要。除了集團線上應用,還有運作在離線排程器上面的離線 job 類任務、跑在搜尋排程器上面的搜尋廣告應用、跑在 SAE/CSE 上面的 Serverless 應用、專有雲産品及公有雲(ACK+CDN)等場景,都使用了 PouchContainer 提供的能力。
運作時的演進
2015 年之前,我們用的運作時是 LXC,PouchContainer 為了在鏡像化後能夠平滑接管原來的 T4 容器,在 LXC 中支援新的鏡像組裝方式,并支援互動式的 exec 和内置的網絡模式。
随着雲原生的程序,我們在使用者無感覺的情況下對運作時做了 containerd+runc 的支援,用标準化的方式加内部功能插件,實作了内部功能特性的支援和被各種标準化運維系統無縫內建的目标。
無論是 LXC 還是 runc 都是讓所有容器共享 Linux 核心,利用 cgroup 和 namespace 來做隔離,對于強安全場景和強隔離場景是不适用的。為了容器這種開發和運維友好的傳遞形式能給更多場景帶來收益,我們很早就開始探索這方面的技術,和集團 os 創新團隊以及螞蟻 os 虛拟化團隊合作共建了 kata 安全容器和 gvisor 安全容器技術,在容器生态嫁接,磁盤、網絡和系統調用性能優化等方面都做了很多的優化。在相容性要求高的場景我們優先推廣 kata 安全容器,已經支援了 SAE 和 ACK 安全容器場景。在語言和運維習慣确定的場景,我們也在 618 大促時上線了一些合适的電商使用了 gvisor 的運作時隔離技術,穩定性和性能都得到了驗證。
為了一部分專有雲場景的實施,我們今年還首次支援了 Windows 容器運作時,在容器依賴相關的部署、運維方面做了一些探索,幫助靈活版專有雲拿下了一些客戶。
除了安全性和隔離性,我們的運作時演進還保證了标準性,今年最新版本的 PouchContainer 把 diskquota、lxcfs、dragonfly、DADI 這些特性都做成了可插拔的插件,不需要這些功能的場景可以完全不受這些功能代碼的影響。甚至我們還對一些場景做了 containerd 發行版,支援純粹的标準 CRI 接口和豐富的運作時。
鏡像技術的演進
鏡像化以後必然會引入鏡像分發的效率方面的困難,一個是速度另一個是穩定性,讓釋出擴容流程不增加太多時間的情況下,還要保證中心節點不被壓垮。
PouchContainer 在一開始就支援了使用 Dragonfly 來做 P2P 的鏡像分發,就是為了應對這種問題,這是我們的第一代鏡像分發方案。在研發域我們也對鏡像分層的最佳實踐做了推廣,這樣能最大程度的保證基礎環境不變時每次下載下傳的鏡像層最小。鏡像加速要解決的問題有:build 效率、push 效率、pull 效率、解壓效率以及組裝效率。第一代鏡像加速方案,結合 Dockerfile 的最佳實踐解決了 build 效率和 pull 效率和中心壓力。
第一代鏡像分發的缺點是無論使用者啟動過程中用了多少鏡像資料,在啟動容器之前就需要把所有的鏡像檔案都拉到本地,在很多場景下都是浪費的,特别影響的是擴容場景。是以第二代的鏡像加速方案,我們調研了阿裡雲的盤古,盤古的打快照、mount、再打快照這種使用方式完美比對打鏡像和分發的流程;能做到秒級鏡像 pull,因為 pull 鏡像時隻需要鑒權,下載下傳鏡像 manifest,然後 mount 盤古,也能做到鏡像内容按需讀取。
2018 年 雙11,我們小規模上線了盤古遠端鏡像,也驗證了我們的設計思路,這一代的鏡像加速方案結合新的 overlay2 技術在第一代的基礎上又解決了PouchContainer 效率/pull 效率/解壓效率群組裝效率。
但是也存在一些問題。首先鏡像資料沒有存儲在中心鏡像倉庫中,隻有 manifest 資訊,這樣鏡像的分發範圍就受限,在哪個盤古叢集做的鏡像,就必須在那個盤古叢集所在的阿裡雲叢集中使用這個鏡像;其次沒有 P2P 的能力,在大規模使用時對盤古後端的壓力會很大,特别是離線場景下由于記憶體壓力導緻很多程序的可執行檔案的 page cache 被清理,然後需要重新 load 這種場景,會給盤古後端帶來更大的壓力。基于這兩個原因,我們和 ContainerFS 團隊合作共建了第三代鏡像分發方案:DADI(基于塊裝置的按需 P2P 加載技術,後面也有計劃開源這個鏡像技術)。
DADI 在建構階段保留了鏡像的多層結構,保證了鏡像在多次建構過程中的可重用性,并索引了每個檔案在每層的offset 和 length,推送階段還是把鏡像推送到中心鏡像倉庫中,保證在每個機房都能拉取到這個鏡像。在每個機房都設定了超級節點做緩存,每一塊内容在特定的時間段内,都隻從鏡像倉庫下載下傳一次。如果有時間做鏡像預熱,像 雙11 這種場景,預熱階段就是從中心倉庫中把鏡像預熱到本地機房的超級節點,後面的同機房的資料傳輸會非常快。鏡像 pull 階段隻需要下載下傳鏡像的 manifest 檔案(通常隻有幾 K大小),速度非常快,啟動階段 DADI 會給每個容器生成一個塊裝置,這個塊裝置的 chunk 讀取是按需從超級節點或臨近節點 P2P 讀取的内容,這樣就保證了容器啟動階段節點上隻讀取了需要的部分内容。為了防止容器運作過程中出現 iohang,我們在容器啟動後會在背景把整個鏡像的内容全部拉到 node 節點,享受超快速啟動的同時最大程度地避免後續可能出現的 iohang。
使用 DADI 鏡像技術後的今年 雙11 高峰期,每次有人在群裡面說有擴容任務,我們值班的同學去看工單時,基本都已經擴容完成了,擴容體驗做到了秒級。
網絡技術演進
PouchContainer 一開始的網絡功能是揉合在 PouchContainer 自己的代碼中的,用內建代碼的方式支援了集團各個時期的網絡架構,為了向标準化和雲原生轉型,在應用無感覺的情況下,我們在 Sigma-2.0 時代使用 libnetwork 把集團現存的各種網絡機架構都統一做了 CNM 标準的網絡插件,沉澱了集團和專有雲都在用的阿裡巴巴自己的網絡插件。在線上排程系統推廣期間,CNM 的網絡插件已經不再适用,為了不需要把所有的網絡插件再重新實作一遍,我們對原來的網絡插件做了包裝,沉澱了 CNI 的網絡插件,把 CNM 的接口轉換為 CNI 的接口标準。
内部的網絡插件支援的主流單機網絡拓撲演進過程如下圖所示:
從單機拓撲能看出來使用神龍 eni 網絡模式可以避免容器再做網橋轉接,但是用神龍的彈性網卡和CNI網絡插件時也有坑需要避免,特别是 eni 彈性網卡是擴容容器時才熱插上來的情況時。建立 eni 網卡時,udevd 服務會配置設定一個唯一的 id N,比如 ethN,然後容器 N 啟動時會把 ethN 移動到容器 N 的 netns,并從裡面改名為 eth0。容器 N 停止時,eth0 會改名為 ethN 并從容器 N 的 netns 中移動到主控端的 netns 中。
這個過程中,如果容器 N 沒有停止時又配置設定了一個容器和 eni 到這台主控端上,udevd 由于看不到 ethN 了,它有可能會配置設定這個新的 eni 的名字為 ethN。容器 N 停止時,把 eth0 改名為 ethN 這一步能成功,但是移動到主控端根 netns 中這一步由于名字沖突會失敗,導緻 eni 網卡洩漏,下一次容器 N 啟動時找不到它的 eni 了。可以通過修改 udevd 的網卡名字生成規則來避免這個坑。
運維能力演進
PouchContainer 容器技術支援了百萬級的線上容器同時運作,經常會有一些問題需要我們排查,很多都是已知的問題,為了解決這個困擾,我還寫了 PouchContainer 的一千個細節以備使用者查詢,或者重複問題問過來時直接交給使用者。但是 PouchContainer 和相關鍊路本身穩定性和運維能力的提升才是最優的方法。今年我們建設了 container-debugger 和 NodeOps 中心系統,把一些容器被使用者問的問題做自動檢測和修複,任何修複都做了灰階篩選和灰階部署能力,把一些經常需要答疑的問題做了使用者友好的提示和修複,也減輕了我們自身的運維壓力。
- 内部的中心化日志采集和即時分析
- 自帶各子產品的健康和保活邏輯
- 所有子產品提供 Prometheus 接口,暴露接口成功率和耗時
- 提供常見問題自動巡檢修複的工具
- 運維經驗積累,對使用者問題提供修複建議
- 提供灰階工具,任何變更通過金絲雀逐漸灰階
- 剖析工具,流程中插入代碼的能力
- Pouch 具備一鍵釋出能力,快速修複
容器使用方式演進
提供容器平台給應用使用,在容器啟動之前必然有很多平台相關的邏輯需要處理,這也是我們以前用富容器的原因。
- 安全相關:安全路由生成、安全腳本配置
- cpushare 化相關配置:tsar 和 nginx 配置
- 運維agent 更新相關:運維agent 更新相對頻繁,基礎鏡像更新特别慢,不能依賴于基礎鏡像更新來更新運維agent
- 配置相關邏輯:同步頁頭頁尾,隔離環境支援, 強弱依賴插件部署
- SN 相關:模拟寫 SN 到/dev/mem,保證 dmidecode 能讀到正确的 SN
- 運維相關的的 agent 拉起,很多運維系統都依賴于在節點上面有一個 agent,不管這個節點是容器/ecs 還是實體機
-
隔離相關的配置:比如 nproc 這個限制是在使用者上面的,用統一個鏡像的容器不能使用統一 uid 不然無法隔離 nproc
現在随着基于 K8s 編排排程系統的推廣,我們有了 Pod 能力,可以把一些預置邏輯放到前置 hook 中去執行,當然富容器可以瘦下來,還要依賴于運維 agent 可以從主容器中拆出來,那些隻依賴于 volume 共享就能跑起來的 agent 可以先移動到 sidecar 裡面去,這樣就可以把運維容器和業務主容器分到不同的容器裡面去,一個 Pod 多個容器在資源隔離上面分開,主容器是 Guaranteed 的 QOS,運維容器是 Burstable 的 QOS。同時在 kubelet 上支援 Pod 級别的資源管控,保證這個 Pod 整體是 Guaranteed 的同時,限制了整個 pod 的資源使用量不超過應用單執行個體的申請資源。
還有一些 agent 不是隻做 volume 共享就可以放到 sidecar 的運維容器中的,比如 arthas 需要能 attach 到主容器的程序上去,還要能 load 主容器中非 volume 路徑上面的 jar 檔案才能正常工作。對于這種場景 PouchContainer 容器也提供了能讓同 Pod 多容器做一些 ns 共享的能力,同時配合 ns 穿越來讓這些 agent 可以在部署方式和資源隔離上是和主容器分離的,但是在運作過程中還可以做它原來可以做的事情。
容器技術繼續演進的方向
可插拔的插件化的架構和更精簡的調用鍊路在容器生态裡面還是主流方向,kubelet 可以直接去調用 pouch-containerd 的 CRI 接口,可以減少中間一個元件的遠端調用,不過 CRI 接口現在還不夠完善,很多運維相關的指令都沒有,logs 接口也要依賴于 container API 來實作,還有運作環境和建構環境分離,這樣使用者就不需要到主控端上面執行 build。所有的運維系統也不再依賴于 container API。在這些限制下我們可以做到減少對一個中間元件的系統調用,直接用 kubelet 去調用 pouch-containerd 的 CRI 接口。
現在每個應用都有很多的 Dockerifle,怎麼讓 Dockerfile 更有表達能力,減少 Dockerfile 數量。建構的時候并發建構也是一個優化方向,buildkit 在這方面是可選的方案,Dockerfile 表達能力的欠缺也需要新的解決方案,buildkit 中間态的 LLB 是 go 代碼,是不是可以用 go 代碼來代替 Dockerfile,定義更強表達能力的 Dockerfile 替代品。
容器化是雲原生的關鍵路徑,容器技術在運作時和鏡像技術逐漸趨于穩定的情況下,熱點和開發者的目光開始向上層轉移,K8s 和基于其上的生态成為容器技術未來能産生更多創新的領域,PouchContainer 技術也在向着更雲原生、更好适配 K8s 生态的方向發展,網絡、diskquota、試圖隔離等 PouchContainer 的插件,在 K8s 生态系統中适配和優化也我們後面的方向之一。
本書亮點
- 雙11 超大規模 K8s 叢集實踐中,遇到的問題及解決方法詳述
- 雲原生化最佳組合:Kubernetes+容器+神龍,實作核心系統 100% 上雲的技術細節
- 雙 11 Service Mesh 超大規模落地解決方案
“ 阿裡巴巴雲原生 關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術圈。”