【翻译】轻松构建多平台的容器图像

在为Kubernetes的镜像管理解决方案Trow做一些工作时,我们发现新的工具可以很容易地为多个架构制作容器镜像。这篇文章将展示我们是如何通过一点点工作就能为ARM和Intel平台创建Trow镜像的。

Docker支持多平台镜像已经有一段时间了,一个镜像可以有多个不同的版本用于不同的架构和平台。拉取一个多平台镜像将只拉取你当前架构的版本。例如, docker pull debian:fresh会在我的笔记本上拉出amd64镜像,在我的Raspberry Pi上拉出armv7镜像。这种方式是通过使用清单(又称 "胖清单")来实现的,其中包含指向不同架构的镜像的指针。

Trow项目拥有多平台构建已经有一段时间了,但实现这种构建的代码复杂得令人沮丧,并且依赖于Phil Estes的清单工具。随着buildx 最近的更新,GitHub 容器注册表现在支持清单,以及从Tõnis Tiigi 的 xx 项目中得到的一些灵感,我意识到有可能将事情简化很多。虽然花了点功夫,但我们现在有了一个新的Trow构建,只需要一个Docker文件

# syntax=docker/dockerfile:1 #Note we build on host platform and cross-compile to target arch FROM --platform=$BUILDPLATFORM rust:new as cross ARG TARGETARCH WORKDIR /usr/src/trow copy docker/platform.sh . RUN ./platform.sh # 应该写上/.platform和/.compiler RUN rustup component add rustfmt RUN rustup target add $(cat /.platform) RUN apt-get update && apt-get install -y unzip $(cat /.compiler) COPY Cargo.toml . COPY Cargo.lock . COPY . cargo/config .cargo/config COPY lib lib COPY src src # 为生成的代码先构建protobuf RUN cd lib/protobuf && cargo build --release --target $(cat /.platform) RUN cargo build --release --target $(cat /.platform) RUN cp /usr/src/trow/target/$(cat /。platform)/release/trow /usr/src/trow/ # 当 build --out 是 stable FROM debian 时,把这个去掉。stable-slim RUN groupadd -r -g 333333 trow && useradd -r -g trow -u 333333 trow # 注意,代理时需要证书 RUN apt-get update \ &&apt-get install -y --no-install-recommends openssl libssl-dev ca-certificates\ && apt-get clean && rm -rf /var/lib/apt/lists/* COPY quick-install/self-cert /install/self-cert COPY start-trow.sh / RUN mkdir --parents /data/layers && mkdir /data/scratch && mkdir /certs # 为了并发,保持这个晚点 COPY --from=cross /usr/src/trow/trow /trow RUN chown -R trow /data /certs /install USER trow ENTRYPOINT ["/start-trow.sh" ] ARG VCS_REF ARG VCS_BRANCH ARG DATE ARG VERSION ARG TAG ENV CREATED=$DATE ENV VCS_REF=$VCS_REF ENV VCS_BRANCH=$VCS_BRANCH ENVERSION=$VERSION LABEL org.opencontainers.image.created=$DATE \ org.opencontainers.image.authors="Container Solutions Labs" org.opencontainers.image.url="https://trow.io" org.opencontainers.image.source="https://github.com/ContainerSolutions/trow" org.opencontainers.image.version=$VERSION  org.opencontainers.image。revision=$VCS_REF \ git.branch=$VCS_BRANCH \ org.opencontainers.image.title="Trow Cluster Registry" \ repository=$REPO \ tag=$TAG

Using Buildx builders

要弄清楚发生了什么,最简单的方法是从末端开始往后推。我们可以运行以下程序来使用Dockerfile:

docker buildx build -f Dockerfile .../

注意,我们正在使用buildx,它是Docker的CLI插件,可以使用Docker的高级buildkit功能。在这篇博客中,我使用了GitHub仓库中的完整版buildkit,而不是Docker内置的版本(通常用 DOCKER_BUILDKIT=1来启用)。

我们也可以使用 --平台 参数,例如:

docker buildx build --平台 linux/arm/v7 -f Dockerfile .../

假设你有一个支持目标平台的构建器实例,Docker会很乐意构建相应的镜像。我们中的大多数人没有目标的构建集群,但我们可以使用Docker的QEMU支持来创建一个可以模拟目标架构的构建实例。还可以指定多个平台,这在后面会很重要。

你可以通过运行docker buildx lse.查看你可以为哪些平台构建

g:

NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS default docker default running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

如果指定的平台与主机平台不同,buildx可以使用QEMU来模拟目标平台(你也可以为不同平台指定独立的构建实例)。

交叉编译的拯救

但是,尽管这很酷,却有一个大问题。它很慢。不仅仅是去喝咖啡的速度慢,而是如果你在做像编译这样的CPU密集型工作时,可能会有三道菜--实际上是明天才回来--的速度。值得庆幸的是,有一个解决方法--很多编译器,包括Go和Rust的编译器,都允许你进行交叉编译,即在主机架构上执行编译步骤,但针对不同的架构。在我们的Docker文件中,这正是这一行发生的事情:

 RUN cargo build --release --target $(cat /.platform)

确切的平台(例如gcc-arm-linux-gnueabihf)是在.platform文件的内容中指定的。

明白了

这一点,让我们回到,呃,结束。如果我们看一下Docker文件中

第二个FROM命令,你会发现它非常标准:

FROM debian:stable-slim

没有指定平台,所以它将使用buildx build命令中指定的同一平台。如果这个平台与主机不同,它将使用 QEMU,但这没关系,因为唯一的 RUN 命令是琐碎的 shell 操作(mkdir 和 chown),不会对 CPU 造成压力。

与第一个FROM行相比:

FROM --platform=$BUILDPLATFORM rust:new as cross

这里我们说 "cross "构建镜像将在BUILDPLATFORM上构建,buildx将这个变量设置为主机构建平台(所以对于我的笔记本,就是amd64),与构建命令中指定的平台无关。我们的想法是,在这个非模拟的构建阶段,我们进行昂贵的CPU编译,然后将结果复制到构建最终图像的第二阶段。

稍微有点棘手的

是,我们如何告诉编译器要为哪个平台构建?这就是我们需要聪明一点的地方。

看看

这几行:

COPY docker/platform.sh . RUN ./platform.sh # 应该写/.platform和/.compiler

这里我们运行一个简短的脚本,用从buildx设置的TARGETARCH变量中提取的值填充文件/.platform/.compiler。该脚本实际上非常简单:

#!/bin/bash # 在Docker构建中用于设置与平台相关的变量 case $TARGETARCH in
"
amd64") echo "x86_64-unknown-linux-gnu" > /.platform echo "" > /.compiler;; "arm64") echo "arch64-unknown-linux-gnu" > /.platform echo "gcc-aarch64-linux-gnu" > /.compiler ;; "arm") echo "armv7-unknown-linux-gnueabihf" > /.platform echo "gcc-arm-linux-gnueabihf" > /.compiler;; esac

然后我们可以通过以下方式在RUN步骤中参考这些值:

RUN rustup target add $(cat /.platform) RUN apt-get update && apt-get install -y unzip $(cat /.compiler)
and:
RUN cargo build --release --target $(cat /.platform)

有一个问题是,你必须在Docker文件中声明一个ARG来暴露TARGETARCH环境变量,例如:

ARG TARGETARCH

注意,"cat "是需要的,因为我们使用的是sh,而不是bash。在bash脚本中,我们可以用$(< /.compiler)来代替。

但就是这样,Docker文件的其余部分只是设置标签和复制文件。

这样做的真正好处是我们现在可以一次为多种架构进行构建,例如g:

docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 \ -t myrepo/multi-image \ --push \ -f Dockerfile .../

这将为上述所有平台构建我们的Dockerfile,创建一个清单列表并将图像推送到Docker Hub的myrepo/multi-image 上。拉动该标签将为你当前的架构下载正确的镜像。

(

如果你对清单的工作方式感到好奇,可以尝试使用buildx中的imagetools命令来查看远程清单,例如docker buildx imagetools inspect containersol/trow:latest)。

这个解决方案省去了很多以前的麻烦;使用buildx的变量意味着我们现在只需要一个Docker文件,我不必做任何手工工作来拼接清单,因为它是一个Docker文件,buildx会处理它。

如果你喜欢这种方法,可以看看Tõnis Tiigi的xx项目,他也是buildx背后的主要开发者。 xx提供了一个Docker镜像和帮助脚本,以抽象出许多繁琐的细节,例如,你可以使用xx-apt-get来安装适当版本的库,而不必为不同的平台找出正确的名称。

希望这能帮助你们中的一些人创建你们的第一个多架构构建,还有一些人简化了现有构建。如果你觉得这篇文章有用,请告诉我们。

猜你喜欢

转载自blog.csdn.net/community_717/article/details/128699874