构建多种系统架构容器镜像
背景目的
构建同一个镜像支持amd和arm平台
方法一
前提条件
# 设置docker 参数
export DOCKER_CLI_EXPERIMENTAL=enabled
登入amd机器上编译和制作镜像
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-amd64
# build 镜像
docker build --build-arg BUILDPLATFORM=linux/amd64 --build-arg TARGETPLATFORM=linux/amd64 -t $OVN_IMAGE -f Dockerfile.fedora .
# push 镜像
docker push $OVN_IMAGE
登入arm机器上编译和制作镜像
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-aarch64
# build 镜像
docker build --build-arg BUILDPLATFORM=linux/aarch64 --build-arg TARGETPLATFORM=linux/aarch64 -t $OVN_IMAGE -f Dockerfile.fedora .
# push 镜像
docker push $OVN_IMAGE
创建镜像manifest
# 创建 manifest 列表
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni
OVN_IMAGE_AMD64=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-amd64
OVN_IMAGE_ARM64=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-aarch64
# 当要修改一个 manifest 列表时,可以加入 -a 或 --amend 参数
docker manifest create $OVN_IMAGE $OVN_IMAGE_AMD64 $OVN_IMAGE_ARM64
# 推送 manifest 列表
docker manifest push $OVN_IMAGE
设置 manifest 列表
# 设置 manifest 列表
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni
OVN_IMAGE_AMD64=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-amd64
OVN_IMAGE_ARM64=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-aarch64
docker manifest annotate $OVN_IMAGE $OVN_IMAGE_AMD64 --os linux --arch x86_64
docker manifest annotate $OVN_IMAGE $OVN_IMAGE_ARM64 --os linux --arch arm64 --variant v8
查看 manifest 列表
# 查看 manifest 列表
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni
docker manifest inspect $OVN_IMAGE
# 输出如下信息
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 4902,
"digest": "sha256:8ed550698e5dabaee8dbafaf9a4c41f7173fe2aaf17437cd7fc4a8b163983e31",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 4899,
"digest": "sha256:4c59e7cb42177d9a7aadc476dbf5bf88448bd3097ad763f8ff70753934ec3b16",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}
方法二
使用amd架构的docker和arm架构的docker 两种docker 整合成一个docker编译平台
docker远程
编辑 /usr/lib/systemd/system/docker.service文件
在如下位置添加 -H tcp://0.0.0.0:2375
重启docker
systemctl daemon-reload
systemctl restart docker
测试连接
curl http://localhost:2375/version
使用 docker buildx 构建跨平台 容器镜像。
默认的 docker build 命令无法完成跨平台构建任务,我们需要为 docker 命令行安装 buildx 插件扩展其功能。buildx 能够使用由 Moby BuildKit 提供的构建镜像额外特性,它能够创建多个 builder 实例,在多个节点并行地执行构建任务,以及跨平台构建。
buildx启用
macOS 或 Windows 系统的 Docker Desktop,以及 Linux 发行版通过 deb 或者 rpm 包所安装的 docker 内置了 buildx,不需要另行安装。
如果 docker 没有 buildx 命令,可以下载二进制包进行安装:
首先从 Docker buildx 项目的 release 页面找到适合自己平台的二进制文件。
下载二进制文件到本地并重命名为 docker-buildx,移动到 docker 的插件目录 ~/.docker/cli-plugins 或者 /usr/libexec/docker/cli-plugins 目录。
向二进制文件授予可执行权限。
如果本地的 docker 版本高于 19.03,可以通过以下命令直接在本地构建并安装,这种方式更为方便:
DOCKER_BUILDKIT=1 docker build --platform=local -o . "https://github.com/docker/buildx.git"
mkdir -p ~/.docker/cli-plugins
mv buildx ~/.docker/cli-plugins/docker-buildx
使用 buildx 进行构建的方法如下:
# docker 20.X 以下版本需要开启环境变量
export DOCKER_CLI_EXPERIMENTAL=enabled
# 查看docker 是否支持buildx
docker info | grep buildx
buildx: Docker Buildx (Docker Inc., v0.10.4)
# buildx 进行构建的方法
cdocker buildx build .
buildx 和 docker build 命令的使用体验基本一致,还支持 build 常用的选项如 -t、-f等。
buildx实例
docker buildx 通过 builder 实例对象来管理构建配置和节点,命令行将构建任务发送至 builder 实例,再由 builder 指派给符合条件的节点执行。我们可以基于同一个 docker 服务程序创建多个 builder 实例,提供给不同的项目使用以隔离各个项目的配置,也可以为一组远程 docker 节点创建一个 builder 实例组成构建阵列,并在不同阵列之间快速切换。
使用 docker buildx create 命令可以创建 builder 实例,这将以当前使用的 docker 服务为节点创建一个新的 builder 实例。要使用一个远程节点,可以在创建示例时通过 DOCKER_HOST 环境变量指定远程端口或提前切换到远程节点的 docker context。
下面首先以当前节点创建一个新的 builder 实例,并通过命令行选项指定实例名称、驱动以及当前节点的目标平台:
# docker 20.X 以下版本需要开启环境变量
export DOCKER_CLI_EXPERIMENTAL=enabled
# 创建新builder 实例 multi-builder
docker buildx create --driver docker-container --platform linux/amd64 --name multi-builder
multi-builder
# 添加远程arm平台的buidler
export DOCKER_HOST=tcp://192.168.100.2:2375 #(前提:远程docker开启远程访问)
docker buildx create --name multi-builder --append --node remote-builder
#启动新buidler实例 multi-builder
[root@yusur-53 test]#docker buildx inspect --bootstrap multi-builder
Name: multi-builder
Driver: docker-container
Last Activity: 2023-04-14 09:59:15 +0000 UTC
Nodes:
Name: multi-builder0
Endpoint: unix:///var/run/docker.sock
Status: running
Buildkit: v0.9.3
Platforms: linux/amd64, linux/386
Name: remote-builder
Endpoint: tcp://192.168.100.2:2375
Status: running
Buildkit: v0.11.5
Platforms: linux/arm64, linux/arm/v7, linux/arm/v6
# 查看现有builder所有实例
[root@yusur-53 test]#docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
multi-builder * docker-container
multi-builder0 unix:///var/run/docker.sock running v0.9.3 linux/amd64, linux/386
remote-builder tcp://192.168.100.2:2375 running v0.11.5 linux/arm64, linux/arm/v7, linux/arm/v6
default docker
default default running 20.10.12 linux/arm64, linux/arm/v7, linux/arm/v6
启用新builder 实例
# 启用后才能使用 multi-builder 多架构编译镜像
docker buildx use multi-builder
buildx构建
构建单个arm镜像
# 构建arm的镜像到本地
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-aarch64
docker buildx build --build-arg BUILDPLATFORM=linux/aarch64 --build-arg TARGETPLATFORM=linux/aarch64 --platform=linux/arm64 -t $OVN_IMAGE -o type=docker -f Dockerfile.fedora .
构建单个amd镜像
# 构建amd的镜像到本地
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni-amd64
docker buildx build --build-arg BUILDPLATFORM=linux/amd64 --build-arg TARGETPLATFORM=linux/amd64 --platform=linux/amd64 -t $OVN_IMAGE -o type=docker -f Dockerfile.fedora .
构建多种arm, amd镜像
#编译完成后就直接push到hub
OVN_IMAGE=harbor.yusur.tech/yusur_ovn/ovn-daemonset-f:second-cni
docker buildx build --push --platform linux/arm64,linux/amd64 -t $OVN_IMAGE -f Dockerfile.fedora .
go代码例子
cat <<EOF > demo.go
package main
import (
"fmt"
"log"
"net/http"
)
func listenServer() {
http.Handle("/", http.HandlerFunc(lServe))
http.ListenAndServe(":8080", nil)
}
func lServe(w http.ResponseWriter, r *http.Request) {
log.Println("response success")
fmt.Fprintln(w, "hello world")
}
func main() {
listenServer()
}
EOF
# 编写Dockerfile
cat <<EOF > Dockerfile
# Build the manager binary
FROM golang:1.19 as builder
MAINTAINER leid
WORKDIR /workspace
ADD . ./
ENV GO111MODULE=on
ENV GOPROXY="https://goproxy.io"
RUN go build -o demo demo.go
FROM alpine:latest
WORKDIR /
COPY --from=builder /workspace/demo .
EXPOSE 8080
ENTRYPOINT ["/demo"]
EOF
编译该代码
# 编译镜像
docker buildx build --push --platform linux/arm64,linux/amd64 -t harbor.yusur.tech/leid/demo:latest .
# 查看镜像digests
docker buildx imagetools inspect harbor.yusur.tech/leid/demo:latest
Name: docker.io/flftuu/demo:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:d2bfa8696da0f3e8e4bfe8d4f1b23c1e10dbb7e1dbea7dcfa59a851ae7a8fb8c
Manifests:
Name: docker.io/flftuu/demo:latest@sha256:58c51a62aa94b9d138d39fdb79759d41a7b04f8c4d8c4901ebbf7ed16363e6a2
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: docker.io/flftuu/demo:latest@sha256:00ec9b7455915a9b70c9ffa316326147c1a2225b5bd014367a4cdf97984f0916
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
方法三
binfmt_misc启用
如果你使用的是 Docker 桌面版(MacOS 和 Windows),默认已经启用了 binfmt_misc,可以跳过这一步。
如果你使用的是 Linux,需要手动启用 binfmt_misc。大多数 Linux 发行版都很容易启用,不过还有一个更容易的办法,直接运行一个特权容器,容器里面写好了设置脚本:
$ docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
验证是 binfmt_misc 否开启:
[root@centos8~]$ ls -al /proc/sys/fs/binfmt_misc/
total 0
drwxr-xr-x 2 root root 0 Jun 16 18:46 .
dr-xr-xr-x 1 root root 0 Jun 5 21:42 ..
-rw-r--r-- 1 root root 0 Jun 16 19:22 qemu-aarch64
-rw-r--r-- 1 root root 0 Jun 16 19:22 qemu-arm
-rw-r--r-- 1 root root 0 Jun 16 19:22 qemu-ppc64le
-rw-r--r-- 1 root root 0 Jun 16 19:22 qemu-riscv64
-rw-r--r-- 1 root root 0 Jun 16 19:22 qemu-s390x
--w------- 1 root root 0 Jun 16 18:46 register
-rw-r--r-- 1 root root 0 Jun 16 18:46 status
验证是否启用了相应的处理器:
[root@centos8 ~]$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b7
mask ffffffffffffff00fffffffffffffffffeffff
注意
请将 Linux 内核版本升级到 4.x 以上,特别是 CentOS7 用户。
centos7 用户可以用以下命令开启
$ docker run --rm --privileged multiarch/qemu-user-static:register --reset
$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64-static
flags:
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
$ docker create -it --name dummy multiarch/qemu-user-static:x86_64-aarch64 bash
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ab622a76dfa multiarch/qemu-user-static:x86_64-aarch64 "bash" 3 minutes ago Created dummy
$ docker cp dummy:/usr/bin/qemu-aarch64-static qemu-aarch64-static
$ ls qemu-aarch64-static
qemu-aarch64-static*
$ docker rm -f dummy
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker run --rm -t -v $(pwd)/qemu-aarch64-static:/usr/bin/qemu-aarch64-static arm64v8/ubuntu uname -m
aarch64docker run --rm --privileged multiarch/qemu-user-static:register --reset
切换多平台构建器
由于 Docker 默认的 builder 实例不支持同时指定多个 --platform,我们必须首先创建一个新的 builder 实例手动替换
docker buildx create --use --name multi-builder
启动构建器:
root@master01:/tmp# docker buildx inspect multi-builder --bootstrap
[+] Building 2.7s (1/1) FINISHED
=> [internal] booting buildkit 2.7s
=> => pulling image moby/buildkit:buildx-stable-1 2.3s
=> => creating container buildx_buildkit_multi-builder0 0.4s
Name: multi-builder
Driver: docker-container
Last Activity: 2023-04-17 03:08:42 +0000 UTC
Nodes:
Name: multi-builder0
Endpoint: unix:///var/run/docker.sock
Status: running
Buildkit: v0.11.5
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
查看当前使用的构建器及构建器支持的 CPU 架构,可以看到支持很多 CPU 架构:
root@master01:/tmp# docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
multi-builder * docker-container
multi-builder0 unix:///var/run/docker.sock running v0.11.5 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
default docker
default default running 23.0.3 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6
构建多平台镜像
新建 Dockerfile 文件,将该应用容器化
FROM --platform=$TARGETPLATFORM alpine
RUN uname -a > /os.txt
CMD cat /os.txt
现在就可以使用 buildx 构建一个支持 arm、arm64 和 amd64 多架构的 Docker 镜像了,同时将其推送到 Docker Hub:
--push 参数表示将构建好的镜像推送到 Docker 仓库。
提前使用docker login 命令登录认证 Docker Hub
docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t flftuu/demo . --push
# 查看镜像信息
docker buildx imagetools inspect flftuu/demo
背后的原理也很简单,之前已经提到过了,buildx 会通过
QEMU 和 binfmt_misc 分别为 3 个不同的 CPU 架构(arm,arm64 和 amd64)构建 3 个不同的镜像。构建完成后,就会创建一个 manifest list,其中包含了指向这 3 个镜像的指针。
如果想将构建好的镜像保存在本地,可以将
type 指定为 docker,但必须分别为不同的 CPU 架构构建不同的镜像,不能合并成一个镜像,即:
docker buildx build -t hello-arch:arm --platform=linux/arm -o type=docker .
docker buildx build -t hello-arch:arm64 --platform=linux/arm64 -o type=docker .
docker buildx build -t hello-arch:amd64 --platform=linux/amd64 -o type=docker .
测试多平台镜像
由于之前已经启用了 binfmt_misc,现在我们就可以运行任何 CPU 架构的 Docker 镜像了,因此可以在本地系统上测试之前生成的 3 个镜像是否有问题。
# arm
$ docker run -it --rm hello-arch:arm
Linux buildkitsandbox 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 armv7l Linux
# arm64
$ docker run -it --rm hello-arch:arm64
Linux buildkitsandbox 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 aarch64 Linux
# amd64
$ docker run -it --rm hello-arch:amd64
Linux buildkitsandbox 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 x86_64 Linux