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

HitTwice發表於2018-07-25

作者:仔仁  文章來源:雲棲社群

原文連結:https://yq.aliyun.com/articles/615113?spm=a2c4e.11153940.bloghomeflow.256.603b291aakVqfZ

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

背景

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

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

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

現在很多企業都是使用 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                                            Runtimetest   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 toptest$ pouch ps

Name   ID       Status         Created          Image                                            Runtimetest   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開源地址

PouchContainer 容器技術開源 GitHub 地址為:

PouchContainer 團隊誠邀廣大技術愛好者參與開源貢獻,打造國內第一容器品牌。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31473948/viewspace-2158720/,如需轉載,請註明出處,否則將追究法律責任。

相關文章