使用 docker manifest 構建跨平臺鏡像

在當今的軟件開發領域中,構建跨平臺應用程序已經成爲了一個普遍存在的需求。不同的操作系統、硬件架構需要不同的鏡像環境來支持它們。Docker 作爲一個廣泛應用的容器化技術,必然需要能夠支持構建跨平臺鏡像,本文將介紹如何使用 docker manifest 來實現構建跨平臺鏡像。

簡介

docker manifest 是 Docker 的一個命令,它提供了一種方便的方式來管理不同操作系統和硬件架構的 Docker 鏡像。通過 docker manifest,用戶可以創建一個虛擬的 Docker 鏡像,其中包含了多個實際的 Docker 鏡像,每個實際的 Docker 鏡像對應一個不同的操作系統和硬件架構。

docker manifest 命令本身並不執行任何操作。爲了操作一個 manifestmanifest list,必須使用其中一個子命令。

manifest 可以理解爲是一個 JSON 文件,單個 manifest 包含有關鏡像的信息,例如層(layers)、大小(size)和摘要(digest)等。

manifest list 是通過指定一個或多個(理想情況下是多個)鏡像名稱創建的鏡像列表(即上面所說的虛擬 Docker 鏡像)。可以像普通鏡像一樣使用 docker pulldocker run 等命令來操作它。manifest list 通常被稱爲「多架構鏡像」。

注意:docker manifest 命令是實驗性的,還未轉正。旨在用於測試和反饋,因此其功能和用法可能會在不同版本之間發生變化。

準備工作

工欲善其事,必先利其器,如果想使用 docker manifest 構建多架構鏡像,需要具備以下條件。

爲不同平臺構建鏡像

本文中演示程序所使用的環境是 Apple M2 芯片平臺。本地的 Docker 版本如下:

$ docker version
Client:
 Cloud integration: v1.0.29
 Version:           20.10.21
 API version:       1.41
 Go version:        go1.18.7
 Git commit:        baeda1f
 Built:             Tue Oct 25 18:01:18 2022
 OS/Arch:           darwin/arm64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.15.0 (93002)
 Engine:
  Version:          20.10.21
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.7
  Git commit:       3056208
  Built:            Tue Oct 25 17:59:41 2022
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.6.10
  GitCommit:        770bd0108c32f3fb5c73ae1264f7e503fe7b2661
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

準備 Dockerfile

首先準備如下 Dockerfile 文件,用來構建鏡像。

FROM alpine

RUN uname -a > /os.txt

CMD cat /os.txt

這個鏡像非常簡單,構建時將 uname -a 命令輸出信息(即當前操作系統的相關信息)寫入 /os.txt,運行時將 /os.txt 內容輸出。

構建 arm64 平臺鏡像

因爲本機爲 Apple M2 芯片,所以使用 docker build 命令構建鏡像默認爲 arm64 平臺鏡像。構建命令如下:

$ docker build -t jianghushinian/echo-platform-arm64 .
[+] Building 15.6s (6/6) FINISHED
 =[internal] load build definition from Dockerfile                                             0.0s
 ==> transferring dockerfile: 94B                                                              0.0s
 =[internal] load .dockerignore                                                                0.0s
 ==> transferring context: 2B                                                                  0.0s
 =[internal] load metadata for docker.io/library/alpine:latest                                15.5s
 =[1/2] FROM docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc01  0.0s
 => CACHED [2/2] RUN uname -a > /os.txt                                                          0.0s
 => exporting to image                                                                           0.0s
 ==> exporting layers                                                                          0.0s
 ==> writing image sha256:f017783a39920aa4646f87d7e5a2d67ab51aab479147d60e5372f8749c3742bb     0.0s
 ==> naming to docker.io/jianghushinian/echo-platform-arm64                                    0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

注意:jianghushinian 是我的 Docker Hub 用戶名,你在構建鏡像時應該使用自己的 Docker Hub 用戶名。

如果得到如上類似輸出,表明構建成功。

使用 docker run 運行容器進行測試:

$ docker run --rm jianghushinian/echo-platform-arm64
Linux buildkitsandbox 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 Linux

輸出內容中的 aarch64 就表示 ARMv8 架構。

現在我們需要將鏡像推送到 Docker Hub,確保在命令行中已經使用 docker login 登錄過 Docker Hub 的情況下,使用 docker push 命令推送鏡像:

$ docker push jianghushinian/echo-platform-arm64
Using default tag: latest
The push refers to repository [docker.io/jianghushinian/echo-platform-arm64]
dd0468cb6cb1: Pushed
07d3c46c9599: Mounted from jianghushinian/demo-arm64
latest: digest: sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583 size: 735

瀏覽器中登錄 Docker Hub 查看推送成功的鏡像:

echo-platform-arm64

構建 amd64 平臺鏡像

無需切換設備,在 Apple M2 芯片的機器上我們可以直接構建 amd64 也就是 Linux 平臺鏡像,docker build 命令提供了 --platform 參數可以構建跨平臺鏡像。

$ docker build --platform=linux/amd64 -t jianghushinian/echo-platform-amd64 .
[+] Building 15.7s (6/6) FINISHED
 =[internal] load build definition from Dockerfile                                                                                                                      0.0s
 ==> transferring dockerfile: 36B                                                                                                                                       0.0s
 =[internal] load .dockerignore                                                                                                                                         0.0s
 ==> transferring context: 2B                                                                                                                                           0.0s
 =[internal] load metadata for docker.io/library/alpine:latest                                                                                                         15.3s
 => CACHED [1/2] FROM docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300                                                    0.0s
 =[2/2] RUN uname -a > /os.txt                                                                                                                                          0.2s
 => exporting to image                                                                                                                                                    0.0s
 ==> exporting layers                                                                                                                                                   0.0s
 ==> writing image sha256:5c48af5176402727627cc18136d78f87f0793ccf61e3e3fb4df98391a69e9f70                                                                              0.0s
 ==> naming to docker.io/jianghushinian/echo-platform-amd64                                                                                                             0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

鏡像構建成功後,同樣使用 docker push 命令推送鏡像到 Docker Hub:

$ docker push jianghushinian/echo-platform-amd64
Using default tag: latest
The push refers to repository [docker.io/jianghushinian/echo-platform-amd64]
9499dee27c9f: Pushed
8d3ac3489996: Mounted from jianghushinian/demo-amd64
latest: digest: sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359 size: 735

瀏覽器中登錄 Docker Hub 查看推送成功的鏡像:

echo-platform-amd64

你也許會好奇,在 Apple M2 芯片的主機設備上運行 amd64 平臺鏡像會怎樣。目前咱們構建的這個簡單鏡像其實是能夠運行的,只不過會得到一條警告信息:

$ docker run --rm jianghushinian/echo-platform-amd64
WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
Linux buildkitsandbox 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 Linux

輸出內容中的 x86_64 就表示 AMD64 架構。

注意:雖然這個簡單的鏡像能夠運行成功,但如果容器內部程序不支持跨平臺,amd64 平臺鏡像無法在 arm64 平臺運行成功。

同樣的,如果我們登錄到一臺 amd64 架構的設備上運行 arm64 平臺鏡像,也會得到一條警告信息:

# docker run --rm jianghushinian/echo-platform-arm64
WARNING: The requested image's platform (linux/arm64/v8) does not match the detected host platform (linux/amd64) and no specific platform was requested
Linux buildkitsandbox 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 Linux

amd64 架構的設備上運行 amd64 平臺鏡像則不會遇到警告問題:

# docker run --rm jianghushinian/echo-platform-amd64
Linux buildkitsandbox 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 x86_64 Linux

使用 manifest 合併多平臺鏡像

我們可以使用 docker manifest 的子命令 create 創建一個 manifest list,即將多個平臺的鏡像合併爲一個鏡像。

create 命令用法很簡單,後面跟的第一個參數 jianghushinian/echo-platform 即爲合併後的鏡像,從第二個參數開始可以指定一個或多個不同平臺的鏡像。

$ docker manifest create jianghushinian/echo-platform jianghushinian/echo-platform-arm64 jianghushinian/echo-platform-amd64
Created manifest list docker.io/jianghushinian/echo-platform:latest

如上輸出,表明多架構鏡像構建成功。

注意:在使用 docker manifest create 命令時,確保待合併鏡像都已經被推送到 Docker Hub 鏡像倉庫,不然報錯 no such manifest。這也是爲什麼前文在構建鏡像時,都會將鏡像推送到 Docker Hub。

此時在 Apple M2 芯片設備上使用 docker run 啓動構建好的跨平臺鏡像 jianghushinian/echo-platform

$ docker run --rm jianghushinian/echo-platform
Linux buildkitsandbox 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 aarch64 Linux

沒有任何問題,就像在啓動 jianghushinian/echo-platform-arm64 鏡像一樣。

現在我們可以將這個跨平臺鏡像推送到 Docker Hub,不過,這回我們需要使用的命令不再是 docker push 而是 manifest 的子命令 docker manifest push

$ docker manifest push jianghushinian/echo-platform
Pushed ref docker.io/jianghushinian/echo-platform@sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359 with digest: sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359
Pushed ref docker.io/jianghushinian/echo-platform@sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583 with digest: sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583
sha256:87b51c1835f13bb722bbb4279fcf50a6da0ecb852433a8f1c04e2f5fe93ac055

瀏覽器中登錄 Docker Hub 查看推送成功的鏡像:

echo-platform

進入鏡像信息詳情頁面的 Tags 標籤,能夠看到鏡像支持 amd64arm64/v8 這兩個平臺。

multi-arch

現在,我們可以在 amd64 架構的設備上同樣使用 docker run 命令啓動構建好的跨平臺鏡像 jianghushinian/echo-platform

# docker run --rm jianghushinian/echo-platform
Linux buildkitsandbox 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 x86_64 Linux

輸出結果沒有任何問題。可以發現,無論是 arm64 設備還是 amd64 設備,雖然同樣使用 docker run --rm jianghushinian/echo-platform 命令啓動鏡像,但它們的輸出結果都表明啓動的是當前平臺的鏡像,沒有再次出現警告。

manifest 功能清單

docker manifest 不止有 create 一個子命令,可以通過 --help/-h 參數查看使用幫助:

$ docker manifest --help

Usage:  docker manifest COMMAND

The **docker manifest** command has subcommands for managing image manifests and
manifest lists. A manifest list allows you to use one name to refer to the same image
built for multiple architectures.

To see help for a subcommand, use:

    docker manifest CMD --help

For full details on using docker manifest lists, see the registry v2 specification.

EXPERIMENTAL:
  docker manifest is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

Commands:
  annotate    Add additional information to a local image manifest
  create      Create a local manifest list for annotating and pushing to a registry
  inspect     Display an image manifest, or manifest list
  push        Push a manifest list to a repository
  rm          Delete one or more manifest lists from local storage

Run 'docker manifest COMMAND --help' for more information on a command.

可以發現,docker manifest 共提供了 annotatecreateinspectpushrm 這 5 個子命。

接下來我們分別看下這幾個子命令的功能。

create

先從最熟悉的 create 子命令看起,來看下它都支持哪些功能。

$ docker manifest create -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...]

Create a local manifest list for annotating and pushing to a registry

EXPERIMENTAL:
  docker manifest create is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

Options:
  -a, --amend      Amend an existing manifest list
      --insecure   Allow communication with an insecure registry

筆記:可以看到輸出結果第一行的提示,短標誌 -h 已經被棄用,推薦使用 --help 查看子命令幫助信息。

可以發現,create 子命令支持兩個可選參數 -a/--amend 用來修訂已存在的多架構鏡像。

指定 --insecure 參數則允許使用不安全的(非 https)鏡像倉庫。

push

push 子命令我們也見過了,使用 push 可以將多架構鏡像推送到鏡像倉庫。

來看下 push 還支持設置哪些可選參數。

$ docker manifest push -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker manifest push [OPTIONS] MANIFEST_LIST

Push a manifest list to a repository

EXPERIMENTAL:
  docker manifest push is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

Options:
      --insecure   Allow push to an insecure registry
  -p, --purge      Remove the local manifest list after push

同樣的,push 也有一個 --insecure 參數允許使用不安全的(非 https)鏡像倉庫。

-p/--purge 選項的作用是推送本地鏡像到遠程倉庫後,刪除本地 manifest list

inspect

inspect 用來查看 manifest/manifest list 所包含的鏡像信息。

其使用幫助如下:

$ docker manifest inspect -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker manifest inspect [OPTIONS] [MANIFEST_LIST] MANIFEST

Display an image manifest, or manifest list

EXPERIMENTAL:
  docker manifest inspect is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

Options:
      --insecure   Allow communication with an insecure registry
  -v, --verbose    Output additional info including layers and platform

--insecure 參數允許使用不安全的(非 https)鏡像倉庫。這已經是我們第三次看見這個參數了,這也驗證了 docker manifest 命令需要聯網才能使用的說法,因爲這些子命令基本都涉及到和遠程鏡像倉庫的交互。

指定 -v/--verbose 參數可以輸出更多信息,包括鏡像的 layersplatform 信息。

使用示例如下:

$ docker manifest inspect jianghushinian/echo-platform
{
   "schemaVersion": 2,
   "mediaType""application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests"[
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359",
         "platform"{
            "architecture""amd64",
            "os""linux"
         }
      },
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583",
         "platform"{
            "architecture""arm64",
            "os""linux",
            "variant""v8"
         }
      }
   ]
}

從輸出信息中可以發現,我們構建的多架構鏡像 jianghushinian/echo-platform 包含兩個 manifest,可以支持 amd64/arm64 架構,並且都爲 linux 系統下的鏡像。

指定 -v 參數輸出更詳細信息:

$ docker manifest inspect -v jianghushinian/echo-platform
[
 {
  "Ref""docker.io/jianghushinian/echo-platform:latest@sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359",
  "Descriptor"{
   "mediaType""application/vnd.docker.distribution.manifest.v2+json",
   "digest""sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359",
   "size": 735,
   "platform"{
    "architecture""amd64",
    "os""linux"
   }
  },
  "SchemaV2Manifest"{
   "schemaVersion": 2,
   "mediaType""application/vnd.docker.distribution.manifest.v2+json",
   "config"{
    "mediaType""application/vnd.docker.container.image.v1+json",
    "size": 1012,
    "digest""sha256:5c48af5176402727627cc18136d78f87f0793ccf61e3e3fb4df98391a69e9f70"
   },
   "layers"[
    {
     "mediaType""application/vnd.docker.image.rootfs.diff.tar.gzip",
     "size": 2818413,
     "digest""sha256:59bf1c3509f33515622619af21ed55bbe26d24913cedbca106468a5fb37a50c3"
    },
    {
     "mediaType""application/vnd.docker.image.rootfs.diff.tar.gzip",
     "size": 211,
     "digest""sha256:1e5897976ad1d3969268a18f4f0356a05875baf0225e39768a9066f43e950ebd"
    }
   ]
  }
 },
 {
  "Ref""docker.io/jianghushinian/echo-platform:latest@sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583",
  "Descriptor"{
   "mediaType""application/vnd.docker.distribution.manifest.v2+json",
   "digest""sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583",
   "size": 735,
   "platform"{
    "architecture""arm64",
    "os""linux",
    "variant""v8"
   }
  },
  "SchemaV2Manifest"{
   "schemaVersion": 2,
   "mediaType""application/vnd.docker.distribution.manifest.v2+json",
   "config"{
    "mediaType""application/vnd.docker.container.image.v1+json",
    "size": 1027,
    "digest""sha256:f017783a39920aa4646f87d7e5a2d67ab51aab479147d60e5372f8749c3742bb"
   },
   "layers"[
    {
     "mediaType""application/vnd.docker.image.rootfs.diff.tar.gzip",
     "size": 2715434,
     "digest""sha256:9b3977197b4f2147bdd31e1271f811319dcd5c2fc595f14e81f5351ab6275b99"
    },
    {
     "mediaType""application/vnd.docker.image.rootfs.diff.tar.gzip",
     "size": 212,
     "digest""sha256:edf2b8e1db64e4f46a2190a3dfcb74ae131ae13ad43fcfedde4c3f304c451f7d"
    }
   ]
  }
 }
]

annotate

annotate 子命令可以給一個本地鏡像 manifest 添加附加的信息。這有點像 K8s Annotations 的意思。

其使用幫助如下:

$ docker manifest annotate -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST

Add additional information to a local image manifest

EXPERIMENTAL:
  docker manifest annotate is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

Options:
      --arch string           Set architecture
      --os string             Set operating system
      --os-features strings   Set operating system feature
      --os-version string     Set operating system version
      --variant string        Set architecture variant

可選參數列表如下:

| 選項 | 描述 | | --- | --- | | --arch | 設置 CPU 架構信息。 | | --os | 設置操作系統信息。 | | --os-features | 設置操作系統功能信息。 | | --os-version | 設置操作系統版本信息。 | | --variant | 設置 CPU 架構的 variant 信息(翻譯過來是 “變種” 的意思),如 ARM 架構的 v7、v8 等。 |

例如設置操作系統版本信息,可以使用如下命令:

$ docker manifest annotate --os-version macOS jianghushinian/echo-platform jianghushinian/echo-platform-arm64

現在使用 inspect 查看鏡像信息已經發生變化:

{
   "schemaVersion": 2,
   "mediaType""application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests"[
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359",
         "platform"{
            "architecture""amd64",
            "os""linux"
         }
      },
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583",
         "platform"{
            "architecture""arm64",
            "os""linux",
            "os.version""macOS",
            "variant""v8"
         }
      }
   ]
}

rm

最後要介紹的子命令是 rm,使用 rm 可以刪除本地一個或多個多架構鏡像(manifest lists)。

$ docker manifest rm -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker manifest rm MANIFEST_LIST [MANIFEST_LIST...]

Delete one or more manifest lists from local storage

EXPERIMENTAL:
  docker manifest rm is an experimental feature.
  Experimental features provide early access to product functionality. These
  features may change between releases without warning, or can be removed from a
  future release. Learn more about experimental features in our documentation:
  https://docs.docker.com/go/experimental/

使用示例如下:

$ docker manifest rm jianghushinian/echo-platform

現在使用 inspect 查看鏡像信息已經不在有 os.version 信息了,因爲本地鏡像 manifest lists 信息已經被刪除,重新從遠程鏡像倉庫拉下來的多架構鏡像信息並不包含 os.version

$ docker manifest inspect jianghushinian/echo-platform
{
   "schemaVersion": 2,
   "mediaType""application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests"[
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:13cbf21fc8078fb54444992faae9aafca0706a842dfb0ab4f3447a6f14fb1359",
         "platform"{
            "architecture""amd64",
            "os""linux"
         }
      },
      {
         "mediaType""application/vnd.docker.distribution.manifest.v2+json",
         "size": 735,
         "digest""sha256:8eb172234961bf54a01e83d510697f09646c43c297a24f839be846414dfaf583",
         "platform"{
            "architecture""arm64",
            "os""linux",
            "variant""v8"
         }
      }
   ]
}

總結

本文主要介紹瞭如何使用 docker manifest 來實現構建跨平臺鏡像。

首先對 docker manifest 進行了簡單介紹,它是 Docker 的一個子命令,本身並不執行任何操作,爲了操作一個 manifestmanifest list,必須使用它包含的子命令。

接着我們又在 Apple M2 芯片設備上構建了不同平臺的鏡像,然後使用 manifest list 的能力將其合併成跨平臺鏡像。

最後對 docker manifest 支持的所有子命令都進行了講解。

參考

docker manifest 官方文檔:https://docs.docker.com/engine/reference/commandline/manifest/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DsqD_iQTPGu1HjiF0czckg