构建docker多架构镜像,混合架构都不是事

以前一直在想,为什么一个镜像只能在一种架构中使用,有什么办法可以解决多架构的kubernetes集群镜像的问题?经过一段时间的调研,还是找到了解决的方案。

什么是多架构集群呢?

在当前形势下,不少公司选择使用国产服务器,但国产服务器cpu基本上都是小众的架构,比如arm、mips等,而最广泛使用的x86架构的服务器。要引入国产服务器,因为各种现实原因,必然面临着一个kubernetes集群中使用多种架构的服务器的情况。

我一直觉得理想的情况是,kubernetes集群能在各种架构的机器上运行,能在编排层面对应用屏蔽掉底层机器的架构。可以一套编排方案,适用于各种架构的集群。会根据当前调度到的机器的架构,拉取对应架构的镜像。也就是说,我管你最终把我的应用pod运行在哪台机器上,这台机器是啥架构,我啥都不关心,把编排好的文件往kubernetes集群中一丢就完事了。

多架构镜像的构建就是实现这个目的的关键一环。

多架构镜像原理


docker宣称目标是"Build and Ship any Application Anywhere",如果不解决多架构镜像问题,这个目标也无从谈起。

多架构的实现——manifests

manifests是什么呢? 咱们从docker的源码入手:

// A ManifestDescriptor references a platform-specific manifest.
type ManifestDescriptor struct {
    distribution.Descriptor

    // Platform specifies which platform the manifest pointed to by the
    // descriptor runs on.
    Platform PlatformSpec `json:"platform"`
}
// Descriptor describes targeted content. Used in conjunction with a blob
// store, a descriptor can be used to fetch, store and target any kind of
// blob. The struct also describes the wire protocol format. Fields should
// only be added but never changed.
type Descriptor struct {
    // MediaType describe the type of the content. All text based formats are
    // encoded as utf-8.
    MediaType string `json:"mediaType,omitempty"`

    // Size in bytes of content.
    Size int64 `json:"size,omitempty"`

    // Digest uniquely identifies the content. A byte stream can be verified
    // against against this digest.
    Digest digest.Digest `json:"digest,omitempty"`

    // URLs contains the source URLs of this content.
    URLs []string `json:"urls,omitempty"`

    // NOTE: Before adding a field here, please ensure that all
    // other options have been exhausted. Much of the type relationships
    // depend on the simplicity of this type.
}
// PlatformSpec specifies a platform where a particular image manifest is
// applicable.
type PlatformSpec struct {
    // Architecture field specifies the CPU architecture, for example
    // `amd64` or `ppc64`.
    Architecture string `json:"architecture"`

    // OS specifies the operating system, for example `linux` or `windows`.
    OS string `json:"os"`

    // OSVersion is an optional field specifying the operating system
    // version, for example `10.0.10586`.
    OSVersion string `json:"os.version,omitempty"`

    // OSFeatures is an optional field specifying an array of strings,
    // each listing a required OS feature (for example on Windows `win32k`).
    OSFeatures []string `json:"os.features,omitempty"`

    // Variant is an optional field specifying a variant of the CPU, for
    // example `ppc64le` to specify a little-endian version of a PowerPC CPU.
    Variant string `json:"variant,omitempty"`

    // Features is an optional field specifying an array of strings, each
    // listing a required CPU feature (for example `sse4` or `aes`).
    Features []string `json:"features,omitempty"`
}

可以看出manifest其实就是记录镜像的能够运行的cpu架构、操作系统类型、操作系统版本等等信息。multi-arch的镜像,可以看做是多个镜像,只是镜像仓库在存储镜像数据的同时,也会存储上面对应的manifest信息。

一个busybox镜像的manifests:

cxwen@cxw:~$ docker manifest inspect busybox
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:2ca5e69e244d2da7368f7088ea3ad0653c3ce7aaccd0b8823d11b0d5de956002",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:55dec6dbd4b329ef2bfc2e104ab6ee57ef1a91f15c8bd324650b34756f43ad61",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v5"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:8bec2de1c91b986218004f65a7ef40989ac9827e80ed02c7ac5cd18058213ba7",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v6"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:2e6dd9846f4022bd771bd7371114d60cf032e15bd160ab6172b776f9fc49812c",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:614f2e7b8fbab8a23bef168e7058739180a7a15d17c583bdfcbdb647d9798079",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:13786684a2d2c684562fd93652fe803fad2ca9fc596ea793ca67b6bbeb2c4730",
         "platform": {
            "architecture": "386",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 527,
         "digest": "sha256:1d67a71f84422fa78f03573a44a419d023682b1c6aa1c380c9d84f69cc79e7f6",
         "platform": {
            "architecture": "mips64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:49c781843a30b2af8fff421e4f2dde9365fb778d5ce11a88991d1ab6056d8f40",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:2ed8bd58e1966fc775f4ba7e97a56ceb1fdd96706779ae8bcb59bc8dbae21be8",
         "platform": {
            "architecture": "s390x",
            "os": "linux"
         }
      }
   ]
}

客户端拉取镜像的流程如下, 这里盗用别人画的一张流程图:

多架构镜像构建方法

docker已经提供了构建多架构镜像的命令行工具:

cxwen@cxw:~$ 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.

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

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

需要开启实验特性才能使用

$vim ~/.docker/config.json
{
    "experimental": "enabled"
}

步骤

1、分别为各架构够建相应的镜像,并推送到镜像仓库

2、创建manifest

docker manifest create {多架构镜像名} {amd64架构镜像名} {arm64架构镜像名} {mips64le架构镜像名} ......

例如:

docker manifest create xwcheng/kube-proxy:v1.15.9 xwcheng/kube-proxy-amd64:v1.15.9 xwcheng/kube-proxy-arm64:v1.15.9 xwcheng/kube-proxy-mips64le:v1.15.9

2、添加对应架构manifest添加annotation属性值

例如:

docker manifest annotate xwcheng/kube-proxy:v1.15.9 xwcheng/kube-proxy-amd64:v1.15.9 --os linux --arch amd64
docker manifest annotate xwcheng/kube-proxy:v1.15.9 xwcheng/kube-proxy-arm64:v1.15.9 --os linux --arch arm64 --variant unknown
docker manifest annotate xwcheng/kube-proxy:v1.15.9 xwcheng/kube-proxy-mips64le:v1.15.9 --os linux --arch mips64le

注意,arm64一定要添加 --variant unknown, 要不会无法拉取。

3、将manifest推送到镜像仓库

docker manifest push xwcheng/kube-proxy:v1.15.9

通过这三步,你就可以只使用 xwcheng/kube-proxy:v1.15.9 这一个镜像名在多种架构的机器上愉快的跑起来了。

2020/07/11 posted in  docker