1. 程式人生 > >深入解析阿裏 PouchContainer 如何實現容器原地升級

深入解析阿裏 PouchContainer 如何實現容器原地升級

其他 狀態 只讀 mars 4.0 什麽 encode 再次 api

PouchContainer 是阿裏巴巴集團開源的高效、輕量級企業級富容器引擎技術,擁有隔離性強、可移植性高、資源占用少等特性。可以幫助企業快速實現存量業務容器化,同時提高超大規模下數據中心的物理資源利用率。已助力阿裏巴巴集團實現在線業務 100% 容器化,雙 11 容器規模達到百萬級。

背景
阿裏巴巴集團內部,容器使用方式有很大一部分是富容器模式,像這種基於傳統虛擬機運維模式下的富容器,其中也有一定數量容器仍然是有狀態的。有狀態服務的更新和升級是企業內部頻率很高的一個日常操作,對於以鏡像為交付的容器技術來說,服務的更新和升級,對應的容器操作實際上是兩步:舊鏡像容器的刪除,以及新鏡像容器的創建。而有狀態服務的升級,則要求保證新容器必須繼承舊容器所有的資源,比如網絡、存儲等信息。下面給出兩個實際的業務案例來直觀闡述富容器業務發布場景需求:

客戶案例一:某數據庫業務,在第一次創建容器服務時,會將遠程的數據下載到本地,作為數據庫的初始數據。因為數據庫初始化過程會比較長,所以在之後可能存在的服務升級過程中,新容器需要繼承舊容器的存儲數據,來降低業務發布的時間;

客戶案例二:某中間件服務,業務采取服務註冊的模式,即所有新擴容的容器 IP 必須首先註冊到服務器列表中,否則新擴容業務容器不可用。在業務容器每次升級發布時,需要保證新容器繼承舊容器 IP,否則會導致新發布的服務不可用。

現在很多企業都是使用 Moby(2017 年 Docker 更名為 Moby) 作為容器引擎,但 Moby 的所有 API 中並沒有一個接口來對標容器升級這一操作。而組合 API 的方式,必然會增加很多 API 請求次數,比如需要請求容器的增刪 API,需要請求 IP 保留的 API 等等,還可能增加升級操作失敗的風險。

基於以上背景,PouchContainer 在容器引擎層面提供了一個 upgrade 接口,用於實現容器的原地升級功能。將容器升級功能下沈到容器引擎這一層來做,對於操作容器相關資源更加方便,並且減少很多 API 請求,讓容器升級操作變得更加高效。

Upgrade 功能具體實現
容器底層存儲介紹
PouchContainer 底層對接的是 Containerd v1.0.3 ,對比 Moby,在容器存儲架構上有很大的差別,所以在介紹 PouchContainer 如何實現容器原地升級功能之前,有必要先簡單介紹一下在 PouchContainer 中一個容器的存儲架構:

image.png | center | 600x336.3525091799266

對比 Moby 中容器存儲架構,PouchContainer 主要不一樣的地方:

PouchContainer 中沒有了 GraphDriver 和 Layer 的概念,新的存儲架構裏引入了 Snapshotter 和 Snapshot,從而更加擁抱 CNCF 項目 containerd 的架構設計。Snapshotter 可以理解為存儲驅動,比如 overlay、devicemapper、btrfs 等。Snapshot 為鏡像快照,分為兩種:一種只讀的,即容器鏡像的每一層只讀數據;一種為可讀寫的,即容器可讀寫層,所有容器增量數據都會存儲在可讀寫 Snapshot 中;

Containerd 中容器和鏡像元數據都存儲在 boltdb 中,這樣的好處是每次服務重啟不需要通過讀取宿主機文件目錄信息來初始化容器和鏡像數據,而是只需要初始化 boltdb。

Upgrade 功能需求
每一個系統和功能設計之初,都需要詳細調研該系統或功能需要為用戶解決什麽疼點。經過調研阿裏內部使用容器原地升級功能的具體業務場景,我們對 upgrade 功能設計總結了三點要求:

數據一致性

靈活性

魯棒性

數據一致性指 upgrade 前後需要保證一些數據不變:

網絡:升級前後,容器網絡配置要保持不變;

存儲:新容器需要繼承舊容器的所有 volume ;

Config:新容器需要繼承舊容器的某一些配置信息,比如 Env, Labels 等信息;

靈活性指 upgrade 操作在舊容器的基礎上,允許引入新的配置:

允許修改新容器的 cpu、memory 等信息;

對新的鏡像,即要支持指定新的 Entrypoint ,也要允許繼承舊容器的 Entrypoint ;

支持給容器增加新的 volume,新的鏡像中可能會包含新的 volume 信息,在新建容器時,需要對這部分 volume 信息進行解析,並創建新的 volume。

魯棒性是指在進行容器原地升級操作期間,需要對可能出現的異常情況進行處理,支持回滾策略,升級失敗可以回滾到舊容器。

Upgrade 功能具體實現
Upgrade API 定義
首先說明一下 upgrade API 入口層定義,用於定義升級操作可以對容器的哪些參數進行修改。如下 ContainerUpgradeConfig 的定義,容器升級操作可以對容器 ContainerConfig 和 HostConfig 都可以進行操作,如果在 PouchContainer github 代碼倉庫的 apis/types 目錄下參看這兩個參數的定義,可以發現實際上,upgrade 操作可以修改舊容器的所有相關配置。

// ContainerUpgradeConfig ContainerUpgradeConfig is used for API "POST /containers/upgrade".
// It wraps all kinds of config used in container upgrade.
// It can be used to encode client params in client and unmarshal request body in daemon side.
//
// swagger:model ContainerUpgradeConfig

type ContainerUpgradeConfig struct {
ContainerConfig

// host config
HostConfig *HostConfig `json:"HostConfig,omitempty"`

}
Upgrade 詳細操作流程
容器 upgrade 操作,實際上是在保證網絡配置和原始 volume 配置不變的前提下,進行舊容器的刪除操作,以及使用新鏡像創建新容器的過程,如下給出了 upgrade 操作流程的詳細說明:

首先需要備份原有容器的所有操作,用於升級失敗之後,進行回滾操作;

更新容器配置參數,將請求參數中新的配置參數合並到舊的容器參數中,使新配置生效;

鏡像 Entrypoint 參數特殊處理:如果新的參數中指定了 Entrypoint 參數,則使用新的參數;否則查看舊容器的 Entrypoint ,如果該參數是通過配置參數指定,而不是舊鏡像中自帶的,則使用舊容器的 Entrypoint 作為新容器的 Entrypoint ;如果都不是,最後使用新鏡像中的 Entrypoint 最為新創建容器的 Entrypoint 。對新容器 Entrypoint 這樣處理的原因是為了保持容器服務入口參數的連續性。

判斷容器的狀態,如果是 running 狀態的容器,首先 stop 容器;之後基於新的鏡像創建一個新的 Snapshot 作為新容器讀寫層;

新的 Snapshot 創建成功之後,再次判斷舊容器升級之前的狀態,如果是 running 狀態,則需要啟動新的容器,否則不需要做任何操作;

最後進行容器升級清理工作,刪掉舊的 Snapshot,並將最新配置進行存盤。

Upgrade 操作回滾
upgrade 操作可能會出現一些異常情況,現在的升級策略是在出現異常情況時,會進行回滾操作,恢復到原來舊容器的狀態,在這裏我們需要首先定義一下 升級失敗情況 :

給新容器創建新的資源時失敗,需要執行回滾操作:當給新容器創建新的 Snapshot,Volumes 等資源時,會執行回滾操作;

啟動新容器出現系統錯誤時,需要執行回滾操作:即在調用 containerd API 創建新的容器時如果失敗則會執行回滾操作。如果 API 返回正常,但容器內的程序運行異常導致容器退出的情況,不會執行回滾操作。
如下給出了回滾操作的一個基本操作:

defer func() {
if !needRollback {
return
}

// rollback to old container.
c.meta = &backupContainerMeta

// create a new containerd container.
if err := mgr.createContainerdContainer(ctx, c); err != nil {
    logrus.Errorf("failed to rollback upgrade action: %s", err.Error())
    if err := mgr.markStoppedAndRelease(c, nil); err != nil {
        logrus.Errorf("failed to mark container %s stop status: %s", c.ID(), err.Error())
    }
}

}()
在升級過程中,如果出現異常情況,會將新創建的 Snapshot 等相關資源進行清理操作,在回滾階段,只需要恢復舊容器的配置,然後用恢復後的配置文件啟動一個新容器既可。

Upgrade 功能演示
使用 ubuntu 鏡像創建一個新容器:

$ pouch run --name test -d -t registry.hub.docker.com/library/ubuntu:14.04 top
43b75002b9a20264907441e0fe7d66030fb9acedaa9aa0fef839ccab1f9b7a8f

$ pouch ps
Name ID Status Created Image Runtime
test 43b750 Up 3 seconds 3 seconds ago registry.hub.docker.com/library/ubuntu:14.04 runc
將 test 容器的鏡像升級為 busybox :

$ pouch upgrade --name test registry.hub.docker.com/library/busybox:latest top
test
$ pouch ps
Name ID Status Created Image Runtime
test 43b750 Up 3 seconds 34 seconds ago registry.hub.docker.com/library/busybox:latest runc
如上功能演示,通過 upgrade 接口,直接將容器的鏡像替換為新的鏡像,而其他配置都沒有變化。

總結
在企業生產環境中,容器 upgrade 操作和容器擴容、縮容操作一樣也是的一個高頻操作,但是,不管是在現在的 Moby 社區,還是 Containerd 社區都沒有一個與該操作對標的 API,PouchContainer 率先實現了這個功能,解決了容器技術在企業環境中有狀態服務更新發布的一個痛點問題。PouchContainer 現在也在嘗試與其下遊依賴組件服務如 Containerd 保持緊密的聯系,所以後續也會將 upgrade 功能回饋給 Containerd 社區,增加 Containerd 的功能豐富度。

深入解析阿裏 PouchContainer 如何實現容器原地升級