2022-05-08_混合CPU架构下的docker镜像构建
目录
背景:众所周知,从CPU发明到现在,有非常多种架构,从我们熟悉的X86、ARM,到不太熟悉的MIPS,IA64,它们之间的差距都非常大。但是如果从最基本的逻辑角度来分类的话,它们可以被分为两大类,即所谓的“复杂指令集”与“精简指令集”系统,也就是经常看到的“CISC”与“RISC”。 Intel和ARM处理器的第一个区别是,前者使用复杂指令集(CISC),而后者使用精简指令集(RISC)。属于这两种类中的各种架构之间最大的区别,在于它们的设计者考虑问题方式的不同。
由于支持指令集的不同,因此相同的程序代码编译到不同架构的CPU上去执行,它们的二进制是不相同的,因此docker镜像也是不相同的
(PS:由于美帝制裁,国内部分公司开始实行科创计划,简而言之从硬件到软件都是国产的,这里面的CPU就有X86和ARM,因此需要在这种混合硬件架构下进行docker镜像构建)
简单想一下,既然和CPU架构相关,那么直接在不同CPU架构的平台下分别构建镜像就可以了,各自的镜像各自使用,这是没问题的,但是如果是混合架构呢?
假设一个K8S集群下面有ARM的计算节点也有X86的计算节点,那么假设ARM的镜像调度到了X86上肯定是不能运行的(当然可以通过打标签的方式指定调度节点类型,这不是本文重点),有没有一种办法,能让一个镜像既能调度到ARM架构的CPU上运行,也能调度到X86的CPU上运行呢?答案是有的,这就是docker manifest
docker manifest
manifest包含了镜像的一些信息,如镜像层、大小、签名信息等,同样它还包含了如
操作系统和架构
等信息,一个manifest可以通过一个或者多个镜像名称生成,manifest的命名和镜像名字一样,也适用于
docker pull
或者
docker run
命令
manifest一般包含了多个操作系统和架构信息的组合,因此,manifest通常用于混合架构镜像构建。
下面演示一下如何使用manifest做混合架构镜像构建
构建并推送镜像
此处需要分别构建X86和ARM的镜像,需要在X86主机和ARM主机上分别构建。 只有该步骤需要在X86和ARM主机上分别执行 ,后续步骤在X86或者ARM执行都可以
Dockerfile文件如下
FROM alpine
RUN echo 'I am building...'
CMD [ "/bin/sh", "-c", "uname -a" ]
- 构建X86镜像
docker build -t kimoqi/alpine-x86:1.0 .
docker push kimoqi/alpine-x86:1.0
- 构建ARM镜像
docker build -t kimoqi/alpine-arm:1.0 .
docker push kimoqi/alpine-arm:1.0
创建并推送manifest
创建manifest,
kimoqi/alpine:1.0
该名字即为manifest的名字,可以当成混合架构的镜像使用,在X86主机上拉取的是X86的镜像,在ARM主机上拉取的则是ARM的镜像
注意创建的时候镜像都要已经推送到仓库了才行 ,manifest本身不是镜像,只是元数据。
docker manifest create kimoqi/alpine:1.0 \
kimoqi/alpine-x86:1.0 \
kimoqi/alpine-arm:1.0
分别为不同的镜像指定os和架构
docker manifest annotate kimoqi/alpine:1.0 \
kimoqi/alpine-x86:1.0 \
--os linux --arch x86_64
docker manifest annotate kimoqi/alpine:1.0 \
kimoqi/alpine-arm:1.0 \
--os linux --arch arm64 --variant v8
查看,可以看到里面包含了两个镜像
docker manifest inspect kimoqi/alpine:1.0
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:XXX",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 528,
"digest": "sha256:XXX",
"platform": {
"architecture": "amd64",
"os": "linux"
}
最后推送之
docker manifest push kimoqi/alpine:1.0
测试
最后在X86上和ARM上分别进行测试,可以看到打印出了不同CPU的架构信息, 而镜像名都是引用的桶一个镜像名 !这就是manifest的作用!
- 在X86上运行
docker run --rm kimoqi/alpine:1.0
# 打印出了X86的信息
Linux a8f5e6f8c2e3 5.15.11-1.el7.elrepo.x86_64 #1 SMP Tue Dec 21 13:17:11 EST 2021 x86_64 Linux
- 在ARM上运行
docker run --rm kimoqi/alpine:1.0
# 打印出了ARM的信息
Linux 4e000ef21145 5.4.150 #0 SMP PREEMPT Wed Oct 6 17:27:03 2021 aarch64 Linux
整个过程大概如下图所示
原理
其实原理很简单,docker在拉取镜像的时候会根据当前系统和CPU架构和manifest做匹配,如果匹配到则再去拉取目标镜像,如下图所示
当然上述过程还是比较繁琐,为了构建不同CPU架构的镜像得需要不同的主机才行,假设我有一个C程序,需要构建镜像发布到各种平台运行,包括X86服务器、ARM主机、安卓手机、MACOS等平台,那岂不是每个设备都得买一个?但事实并不是这样,可以在X86上构建各种平台的镜像,这就是buildx
docker buildx
buildx是专门为了构建混合架构镜像而存在的,它会自动构建指定平台镜像然后使用manifest将它们合并,其实就是将docker manifest的过程自动化了,而且关键是可以在一个平台上构建多个平台的镜像。
注意,使用buildx需要docker版本19.03+
创建builder
- 创建builder并使用
docker buildx create --name mybuilder --use
- 初始化builder
docker buildx inspect --bootstrap
[+] Building 3.6s (1/1) FINISHED
=> [internal] booting buildkit
=> => pulling image moby/buildkit:buildx-stable-1
=> => creating container buildx_buildkit_mybuilder0
Name: mybuilder
Driver: docker-container
Nodes:
Name: mybuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
可以看到docker会
去docker hub拉取
一个叫做
moby/buildkit
的镜像,然后启动了一个
buildx_buildkit_mybuilder0
容器,此时这个该builder才算构建完成
混合架构镜像一键构建
- 准备一个dockerfile
FROM alpine
RUN echo 'I am building...'
CMD [ "/bin/sh", "-c", "uname -a" ]
- 下面就是见证奇迹的时刻,使用一条命令即可构建混合建构镜像
docker buildx build --platform linux/amd64,linux/arm64/v8 -t kimoqi/alpine:1.0 --push .
其中
--platform linux/amd64,linux/arm64/v8
分别指定了需要构建X86和ARM的镜像
测试
分别在X86和ARM的主机上测试,结果符合预期
- X86
docker run --rm kimoqi/alpine:1.0
# 打印出了X86的信息
Linux a8f5e6f8c2e3 5.15.11-1.el7.elrepo.x86_64 #1 SMP Tue Dec 21 13:17:11 EST 2021 x86_64 Linux
- ARM
docker run --rm kimoqi/alpine:1.0
# 打印出了ARM的信息
Linux 4e000ef21145 5.4.150 #0 SMP PREEMPT Wed Oct 6 17:27:03 2021 aarch64 Linux
docker buildx使用私有仓库
前面的案列都是使用docker hub作为仓库,一般在企业中都有自己的私有镜像仓库,私有镜像仓库稍等不同,下面模拟一下在私有仓库下如果使用docker buildx
部署私有镜像仓库
一键部署,
/var/lib/registry
为容器中存储镜像二进制的目录,可选择性挂载到宿主机,假设仓库域名为
vcentos7
docker run -d \
--name docker-registry \
--restart=always \
-p 5000:5000 \
-v /root/private-registry:/var/lib/registry \
registry
将本机Docker添加非安全仓库
cat > /etc/docker/daemon.json <<EOF
"insecure-registries": [
"vcentos7:5000"
EOF
重启docker进程启用
systemctl restart docker
因为后续都使用私有仓库,因此需要将docker hub拉取的buildkit镜像推送到私仓
docker tag moby/buildkit:buildx-stable-1 vcentos7:5000/moby/buildkit:buildx-stable-1
docker push vcentos7:5000/moby/buildkit:buildx-stable-1
创建builder
-
新增私仓配置,不配置推送会失败,因为默认要求是HTTPS的,buildkit默认读取配置文件的路径是
/etc/buildkit/buildkitd.toml,如果是该路径则后续无需指定
cat > ./buildkitd.toml << EOF
debug = true
[registry."vcentos7:5000"]
http = true
insecure = true