OCI 與容器映象構建

張晉濤發表於2021-11-08

大家好,我是張晉濤。

這篇文章中我將介紹 OCI 及 Docker 映象相關的內容,歡迎留言討論。

OCI 的前世今生

2013 年 3 月 dotCloud 公司在 PyCon 上進行了 Docker 的首次展示,隨後宣佈開源。自此 Docker 開始被眾人知曉,隨後掀起了一股容器化的熱潮。

在 2014 年 6 月 Docker 1.0 正式釋出,有近 460 位貢獻者和超過 8700 次提交,這也標誌著 Docker 達到了生產可用的狀態。

在當時,提到容器化第一想法就是用 Docker 。而當時 Docker 的實現或者說發展方向主要是由 Docker Inc. 公司控制的,並沒有一個統一的工業標準。這對於一些頭部公司而言,顯然是不能接受的,沒有統一的工業標準意味著如果選擇了使用 Docker 的容器化技術,便會被 Docker Inc. 公司所繫結;加上隨著 Docker 軟體的升級,某些功能或者特性必然會進行變動,沒人能保證不發生破壞性變更。

所以,為了推進容器化技術的工業標準化,2015 年 6 月在 DockerCon 上 Linux 基金會與 Google,華為,惠普,IBM,Docker,Red Hat,VMware 等公司共同宣佈成立開放容器專案(OCP),後更名為 OCI。它的主要目標便是 建立容器格式和執行時的工業開放通用標準

發展至今, OCI 制定的主要標準有三個分別是 runtime-specimage-specdistribution-spec 這三個標準分別定義了容器執行時,容器映象還有分發的規範,後面會展開介紹。

為了支援 OCI 容器執行時標準的推進,Docker 公司起草了映象格式和執行時規範的草案,並將 Docker 專案的相關實現捐獻給了 OCI 作為容器執行時的基礎實現,現在專案名為 runc

後來 Docker 將其容器執行時獨立成了一個專案,名為 containerd 並將此專案捐獻給了 CNCF ,現在已經是 CNCF 畢業專案了。

OCI image vs Docker image

OCI 的建立推動了容器技術的工業標準化,但是否此標準就是唯一呢?其實不然。在成立 OCI 並制定 image-spec 標準的時候 Docker 已經空前繁榮,並得到了廣泛的應用。

由於標準只定義了最基本的內容,想要將 Docker 的實現全部按照標準進行改造的話,會對 Docker 造成破壞性變更,也不利於 Docker 功能的迭代。

所以,Docker 為了支援 OCI 標準的普及,已經推進了 registry 對 OCI 映象的支援,現在也正在給 Docker 自身增加適配中,目標是讓 Docker 支援兩種映象格式,分別是符合 Docker 標準的映象和符合 OCI 標準的映象。

那這兩者有什麼異同呢? 我們來逐步看下。

Docker Image 和 OCI Image 的區別和聯絡

在我以前的文章中我們已經詳細的從根本上介紹了 Docker image 是什麼,這裡我們就快速的介紹下。

每個 Docker 映象都是由一系列的配置清單和相應的層進行組織的。每個層一般都是 tar 格式的歸檔,配置清單中描述了對應的層應該按何種順序進行組織,以及映象的一些元屬性。比如映象所支援的架構,例如 amd64 之類的,還有 ENV 等提前配置好的一些引數等。

當然,在 Docker Image 中也包含著構建映象時候所用的 Docker 版本 docker_version 以及構建映象的歷史記錄 history 等資訊。所以你在 DockerHub 或者其他的映象倉庫上可以看到構建映象所用的 Docker 版本, 或者可通過 docker history <IMAGE> 的方式來檢視構建歷史。

那麼 OCI Image 是什麼呢? 首先我們需要有一個 OCI Image 才好探究它到底是什麼。

這裡介紹一個工具 skopeo 可以很方便的從映象倉庫或者本地 Docker daemon 甚至是通過 docker save 儲存的 Docker Image tar 檔案轉換為 OCI Image 。

關於 skopeo 的安裝過程就不再贅述了,參考專案主頁的文件說明即可。這裡直接開始使用。

我們使用 debian 的映象為例。

(MoeLove) ➜  skopeo copy docker://debian:stretch oci:debian:stretch    
Getting image source signatures
Copying blob a4d8138d0f6b done
Copying config 45f82268e3 done
Writing manifest to image destination
Storing signatures

通過上面的命令便會得到一個 OCI Image 了, 我們看下它的目錄結構。

(MoeLove) ➜  tree debian 
debian
├── blobs
│   └── sha256
│       ├── 0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155
│       ├── 45f82268e32180cb1839f90467d9b8a8258953d68b7221199976653308d92ef5
│       └── a4d8138d0f6b5a441aaa533faf5fe0c3996a6ca42643c46f4402c7e8bda53742
├── index.json
└── oci-layout

2 directories, 5 files

是不是有種似曾相識的感覺?沒錯 OCI Image 的規範是在 Docker Image 的基礎上建立的,所以大致看起來差異不是特別大。我們看看其中具體的內容。

oci-layout

這個檔案是 OCI Image 的佈局檔案,也是用於說明它所使用或者遵循的映象規範。

(MoeLove) ➜  debian cat oci-layout| jq
{
  "imageLayoutVersion": "1.0.0"
}

可以看到此處的內容寫的是 1.0.0 這便說明該映象遵循 OCI 1.0.0 版本的佈局規範。

index.json

index.json 檔案中的 manifest 欄位類似於 Docker Image 中的 manifest.json 作為 OCI Image 的頂級配置, 也是映象的一個入口配置。

(MoeLove) ➜  debian cat index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155",
      "size": 349,
      "annotations": {
        "org.opencontainers.image.ref.name": "stretch"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

從它的內容可以看到它其中的 mediaType 欄位與 Docker Image 中的型別形式相同,但是將 docker 都換成了 oci。從這個配置檔案,我們可以找到第一個 blob 是 sha256:0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155 我們看看它的內容。

(MoeLove) ➜  debian cat blobs/sha256/0043cd2a654fe86258f43f5b1dbbb4e6c582cc4bb6e505e9c5171c124150d155 | jq
{
  "schemaVersion": 2,
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:45f82268e32180cb1839f90467d9b8a8258953d68b7221199976653308d92ef5",
    "size": 579
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:a4d8138d0f6b5a441aaa533faf5fe0c3996a6ca42643c46f4402c7e8bda53742",
      "size": 45337510
    }
  ]
}

這個入口檔案描述了 OCI 映象的實際配置和其中的 Layer 配置。如果有多層那 layers 也會相應增加。

注意:layers 中 mediaType 使用了 application/vnd.oci.image.layer.v1.tar+gzip 說明資料內容是經過 gzip 壓縮的 如果有興趣你可以將它用 tar 解壓一下,你會發現很有趣的內容。

這裡先將結果說出來,解壓後你會得到一個 rootfs 這與 Docker Image 是類似的。

小結

我們通過 skopeo 工具,從本地的 Docker daemon 中由 debian 的 Docker Image 得到了 OCI Image,並分析了它其中的內容。

最主要的區別在於它們的目錄結構不完全相同,配置資訊尤其是 mediaType 的規範是不相同的。

而它們的聯絡也在於此,OCI Image 的規範是由 Docker Image 的規範修改而來的,所以類似它們的 blob 的組織形式大致是相同的,配置檔案中很多的引數也相似。

另外,我們也可以很容易的得到另一個結論,那便是我們可以很方便的將 Docker Image 轉換為 OCI Image 。

OCI image 和 Docker image 的轉換

上面我們已經看到,使用 skopeo 工具,可以將 Docker Image 轉換為 OCI Image ,當然它也可以將 OCI Image 轉換為 Docker Image 。下面給出了方法:

# 從 DockerHub 將 debian 的 Docker Image 拉取並轉換為 OCI Image
(MoeLove) ➜  skopeo copy docker://debian:stretch oci:debian:stretch    
Getting image source signatures
Copying blob a4d8138d0f6b done
Copying config 45f82268e3 done
Writing manifest to image destination
Storing signatures


# 將當前目錄下的 debian 的 OCI Image 轉換為 Docker Image 並儲存到本地 docker daemon 中
(MoeLove) ➜  skopeo copy  oci:debian:stretch docker-daemon:local/debian:oci
Getting image source signatures
Copying blob 0e350e141713 done
Copying config aae58a37cf done
Writing manifest to image destination
Storing signatures
# 驗證
(MoeLove) ➜  oci docker images local/debian
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
local/debian        oci                 ac6bcf605d82        6 months ago        101MB

映象構建工具

在 CI/CD 環境中,雖然我們可以使用 DinD (Docker in Docker) 的方式啟動一個 docker daemon 或者使用掛載的方式,將外部的 /var/run/docker.sock 掛載進容器內部,亦或者將 Docker API 使用 HTTP 的方式暴露出來,直接使用該地址進行構建。

但這些方式你是否會覺得比較重?是否有考慮安全問題,或者壓力及負載的問題?

這裡的壓力及負載主要是指當所有的任務都共用同一個 docker daemon 提供服務的話,對該 docker daemon 造成的壓力。

這裡我們來介紹一些其他的映象構建工具,使用這些工具可以讓你在無 Docker 的環境下構建出映象並上傳至 Docker 映象倉庫中。

到目前為止,我們可以有很多種選擇:

這些工具側重點各有不同,當然也不僅有上面列到的這些工具,只是這些工具比較典型罷了。

通常情況下,在網路上比較容易見到宣傳為下一代映象構建工具的是 buildah ,最主要原因是因為它可以直接構建 OCI 標準的映象或 Docker 映象,也可以直接使用 Dockerfile 。並且它還可以 pull/push 映象,可以說在映象構建方面與 Docker 是完全相容,甚至可以說它在構建映象方面可以作為 Docker 的替代品了。

並且 buildah 構建映象的時候不需要任何 root 許可權,也不依賴 Docker, 它使用了簡單的 fork-exec 模型,同時它也可以作為一個庫包含在其他的工具中。它的最終目標便是提供一個更低層次的核心工具集,來完成構建映象相關的事情。

說完這個典型的替代品,我們再來說下 BuildKitimg , img 這個工具是構建在 BuildKit 之上的,所以有很多相似性。它們使用非 root 使用者來構建映象。當然 BuildKit 我在之前的文章中詳細介紹過了,它是 Docker 內建的下一代構建工具,獨立使用也是可以的。稱它為“下一代映象構建工具” 也並不為過。

kaniko 是 Google 推出的,它主要的宣傳語為 “在 Kubernetes 中構建容器映象” 實際上無論是在 K8S 叢集中或者在容器中它都是可以工作的。它也可以使用 Dockerfile 構建映象。當然還有很重要的一點,它所有的構建命令都是執行在使用者態的,並且也可以很好的與 Kubernetes 結合,在雲原生時代下,它也佔據了一定的優勢。

以上工具都只是大致介紹了下,如果對它們感興趣,可直接進入專案主頁檢視 README.md 基礎使用都有比較詳細的說明,這裡不再進行贅述了。

總結

本篇為大家介紹了 OCI 的前世今生,以及 OCI Image 的規範和特點,另外也介紹了一個可用於在 OCI Image 和 Docker Image 之間映象轉換的工具 skopeo 。另外介紹了一些可用於在 CI 環境或其他有特定場景環境下替代 Docker build 的工具,請大家按實際需求進行選擇。


歡迎訂閱我的文章公眾號【MoeLove】

TheMoeLove

相關文章