点击上方蓝字⭐️关注“DevOps云学堂”,接收最新技术实践
今天是「DevOps云学堂」与你共同进步的第 39 天
第⑦期DevOps实战训练营·招新中
在运行任何 docker 镜像或 Kubernetes pod 时,您是否在服务器上看到过exec /docker-entrypoint.sh: exec format error
错误消息?这很可能是因为您正在服务器上运行一些其他 CPU 架构的容器镜像,或者您是否曾经 在 Apple Silicon M1
、M2
MacBook 上使用过--platform linux/x86_64
选项?如果是,那么您无法获得 Apple 芯片的本机性能,并且可能会耗尽 MacBook 的电池电量。为了避免这种错误和性能问题,我们需要运行正确的多架构容器镜像,或者我们可能需要构建自己的镜像,因为所有容器公共镜像都没有可用的多架构镜像。
在这篇博文中,我们将了解什么是多架构容器镜像?怎么运行的?如何建设和推广?我们将编写一个示例代码,用于在 CI/CD 管道中构建多架构镜像。
什么是多架构容器镜像?
多架构 Docker 镜像是一个镜像列表,其中引用了为多个 CPU 架构编译的二进制文件和库。当我们需要在不同的 CPU 架构(ARM、x86、RISC-V 等)上运行相同的应用程序而无需为每个架构创建单独的镜像时,这种类型的镜像非常有用。
多架构容器用例
性能和成本优化:容器多架构用于优化不同CPU架构上的性能。通过构建和部署针对特定架构优化的镜像,我们可以获得更好的性能并减少资源使用。
跨平台开发:如果您正在开发需要在多个平台上运行的应用程序,例如ARM和x86,您可以使用buildx构建多架构Docker镜像并在不同架构上测试应用程序。
IoT 设备:许多 IoT/Edge 设备使用 ARM 处理器,这需要与 x86 处理器不同的二进制文件和库。通过多架构映像,您可以创建可在 ARM、x86 和 RISCV 设备上运行的映像,从而更轻松地将应用程序部署到各种 IoT 设备。
使用多架构容器镜像的好处
使用多架构容器镜像的几个优点是:
能够在多个 CPU 架构上运行 Docker 镜像
使我们能够选择环保的CPU架构
从一种架构无缝迁移到另一种架构
使用arm64获得更好的性能并节省成本
能够使用arm64支持每个CPU更多的内核
如何构建多架构容器镜像?
构建多架构容器的方法有多种,但我们将重点关注广泛使用且简单的方法。
传统 Docker 构建命令
使用Docker buildx
使用传统的 Docker 构建命令
在本教程中,我们将在不同 CPU 架构的机器上手动构建两个镜像,并将它们推送到容器注册表(例如 Dockerhub),然后创建包含两个镜像引用的清单文件。清单文件是一个简单的 JSON 文件,其中包含容器映像的索引及其元数据,例如映像大小、sha256 摘要、操作系统等。稍后我们将在本博客中了解有关清单文件的更多信息。
例如。这是我们的基本 Dockerfile。
FROM nginx
RUN echo “Hello multiarch” > /usr/share/nginx/html/index.html
########## on amd64 machine ##########
docker build -t username/custom-nginx:v1-amd64 .
docker push username/custom-nginx:v1-amd64
########## on arm64 machine ##########
docker build -t username/custom-nginx:v1-arm64 .
docker push username/custom-nginx:v1-arm64
########## Create a manifest index file ##########
docker manifest create \
username/custom-nginx:v1 \
username/custom-nginx:v1-amd64 \
username/custom-nginx:v1-arm64
########## Push manifest index file ##########
docker manifest push username/custom-nginx:v1
使用 Docker Buildx
使用 buildx,我们只需要运行一个具有参数化架构的命令。
docker buildx build \
--push \
--platform linux/arm64,linux/amd64 \
-t username/custom-nginx:v1 .
在后台,Docker buildx
命令使用 buildkit
,因此当我们运行上述命令时,它会创建一个带有moby/buildkitd
映像的容器,该容器具有用于多个 CPU 架构的QEMU 二进制文件
,负责模拟 CPU 指令集
。我们可以通过ls /usr/bin/buildkit-qemu-*
在正在运行的buildkit
容器中运行来查看这些 QEMU 二进制文件。
在上面的命令中,我们传递了--platform linux/arm64,linux/amd64
所以它使用/usr/bin/buildkit-qemu-aarch64
QEMU 二进制文件来构建 linux/arm64
映像,并且 linux/amd64
是在主机上本地构建的。构建两个映像后,它会使用该--push
选项创建清单文件,并将两个映像与清单文件一起推送到注册表服务器。
通过检查清单文件,我们可以看到Ref
字段包含实际的镜像链接,当platform[0].architecture
与主机系统架构匹配时将获取该链接。
$ docker manifest inspect -v nginx
[
{
"Ref": "docker.io/library/nginx:latest@sha256:bfb112db4075460ec042ce13e0b9c3ebd982f93ae0be155496d050bb70006750",
"Descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:bfb112db4075460ec042ce13e0b9c3ebd982f93ae0be155496d050bb70006750",
"size": 1570,
"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": 7916,
"digest": "sha256:080ed0ed8312deca92e9a769b518cdfa20f5278359bd156f3469dd8fa532db6b"
},
….
{
"Ref": "docker.io/library/nginx:latest@sha256:3be40d1de9db30fdd9004193c2b3af9d31e4a09f43b88f52f1f67860f7db4cb2",
"Descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:3be40d1de9db30fdd9004193c2b3af9d31e4a09f43b88f52f1f67860f7db4cb2",
"size": 1570,
"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": 7932,
"digest": "sha256:f71a4866129b6332cfd0dddb38f2fec26a5a125ebb0adde99fbaa4cb87149ead"
}
我们还可以使用 buildx imagetools
命令以更易于理解的格式查看相同的输出。
$ docker buildx imagetools inspect sonarqube:10.0.0-community
Name: docker.io/library/sonarqube:10.0.0-community
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:51588fac6153b949af07660decfe20b5754da9fd12c82db5d95a0900b6024196
Manifests:
Name: docker.io/library/sonarqube:10.0.0-community@sha256:8b536568cd64faf15e1e5be916cf21506df70e2177061edfedfd22f255a7b1a0
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
Name: docker.io/library/sonarqube:10.0.0-community@sha256:2163e9563bbba2eba30abef8c25e68da4eb20e6e0bb3e6ecc902a150321fae6b
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64/v8
如果您在构建多架构映像时遇到任何问题,可以运行以下命令来重置/proc/sys/fs/binfmt_misc
条目。
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
我们还可以使用 Buildah构建多架构容器镜像。
多架构容器镜像如何工作?
从图中我们可以看到,主机具有x86/amd64
CPU架构,在此之上,我们安装的操作系统可以是Windows
或Linux
。Windows 需要WSL或LinuxKit
才能运行 Docker。它使用 QEMU 模拟多个 CPU 架构,并在该模拟中运行 Dockerfile 构建。
当我们运行docker pull或build
命令时,它会从注册表服务器获取请求的清单文件
。这些清单文件是 JSON 文件,可以具有一个 Docker 映像引用或包含多个映像列表。它根据主机的 CPU 架构获取正确的图像。
如何将多架构容器构建与 CI/CD 集成?
如果您的工作负载运行在具有不同 CPU 架构的多台计算机上,那么为您的应用程序构建多架构 Docker 映像总是更好。将多架构构建集成到 CI/CD 中可以更轻松地简化映像构建和扫描过程,仅添加一个 Docker 标签,并节省时间。下面我们编写了用于构建多架构镜像的 Jenkins
和 GitHub CI
示例代码。
Jenkins 多架构 CI
目前,Jenkins Docker 插件不支持多架构构建,因此我们可以使用 buildx 来构建多架构镜像。
pipeline
{
agent {
label 'worker1'
}
options{
timestamps()
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
environment {
DOCKER_REGISTRY_PATH = "https://registry.example.com"
DOCKER_TAG = "v1"
}
stages
{
stage('build-and-push')
{
steps{
script{
docker.withRegistry(DOCKER_REGISTRY_PATH, ecrcred_dev){
sh '''
####### check multiarch env ###########
export DOCKER_BUILDKIT=1
if [[ $(docker buildx inspect --bootstrap | head -n 2 | grep Name | awk -F" " '{print $NF}') != "multiarch" ]]
then
docker buildx rm multiarch | exit 0
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
fi
####### Push multiarch ###########
docker buildx build --push --platform linux/arm64,linux/amd64 -t "$DOCKER_REGISTRY_PATH"/username/custom-nginx:"$DOCKER_TAG" .
'''
}
}
}
}
}
}
否则,我们可以在 Jenkins 阶段中使用传统的 Docker 构建命令,如上所示,具有不同的 Jenkins 工作节点集。
用于构建多架构容器映像的 GitHub CI 管道
GitHub Actions还支持多架构容器映像。它还在后台使用 QEMU CPU 模拟。
name: docker-multi-arch-push
on:
push:
branches:
- 'main'
jobs:
docker-build-push:
runs-on: ubuntu-20.04
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to docker.io container registry
uses: docker/login-action@v2
with:
username: $
password: $
- name: Build and push
id: docker_build
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: username/custom-nginx:latest
如何将你的多架构镜像提升到更高的环境?
提升 Docker 多架构需要一些额外的步骤,因为该docker pull
命令仅根据主机的 CPU 架构
提取单个映像。为了推广多架构Docker镜像,我们需要使用以下方法一一拉取所有CPU架构镜像,–plarform=linux/$ARCH
然后创建清单文件并将其推送到新的注册服务器。为了避免这些复杂的步骤,我们可以利用以下工具。Skopeo
或Crane
可用于仅使用一个命令即可将我们的多架构映像从一个帐户提升到另一个帐户。在后台,这些工具的作用是使用Docker API
获取所有多架构映像,然后创建清单并推送所有映像和清单。
$ skopeo login --username $USER docker.io
$ skopeo copy -a docker://dev-account/custom-nginx:v1 docker://prod-account/custom-nginx:v1
如果只想使用 Docker 命令将该镜像提升到更高的环境(Production)怎么办?
####### Pull DEV images ###########
docker pull --platform=amd64 "$DOCKER_IMAGE_NAME_DEV":"$DOCKER_TAG"
docker pull --platform=arm64 "$DOCKER_IMAGE_NAME_DEV":"$DOCKER_TAG"
####### Tag DEV image with STAGE ###########
docker tag "$DOCKER_IMAGE_NAME_DEV":"$DOCKER_TAG" "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-amd64
docker tag "$DOCKER_IMAGE_NAME_DEV":"$DOCKER_TAG" "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-arm64
####### Push amd64 and arm64 image to STAGE ###########
docker push "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-amd64
docker push "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-arm64
####### Create mainfest and push to STAGE ###########
docker manifest create \
"$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG" \
--amend "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-amd64 \
--amend "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"-arm64
docker manifest push "$DOCKER_IMAGE_NAME_STAGE":"$DOCKER_TAG"
如何扫描多架构镜像漏洞?
我们可以使用Trivy、Gryp或Docker scan
等任何工具进行镜像扫描,但我们必须将多架构镜像一一拉取然后扫描它们,因为默认情况下 Docker pull
命令只会获取与主机 CPU 匹配的一个镜像。我们可以利用 Docker pull
命令来--platform={amd64, arm64}
拉取不同的 CPU 架构镜像。我们可以这样做:
####### Pull amd64 image and scan ###########
docker pull --platform=amd64 nginx:latest
trivy image nginx:latest
####### Pull arm64 image and scan ###########
docker pull --platform=arm64 nginx:latest
trivy image nginx:latest
使用多架构容器的一些注意事项
使用多架构容器有显着的好处,但在采取行动之前,您当然应该注意一些注意事项。
存储其他架构镜像需要额外的存储空间。
构建多架构容器映像也需要时间,而在 QEMU 仿真上构建 arm64 会消耗大量时间和资源。
与本机运行二进制文件相比,在不同 CPU 上模拟运行二进制文件的性能明显较低。
buildx 构建arm64 映像仍然存在一些问题,例如基础映像在arm64 中不可用,并且执行sudo 级别访问或构建交叉编译静态链接二进制文件需要额外的步骤。
需要对所有镜像进行容器一一扫描。
Buildx 多架构构建仅在 amd64 CPU 架构上受支持。
结论
在本博客中,我们了解了什么是多架构容器及其用例。我们通过示例代码将多架构构建与 Jenkins 和 Github CI 集成,并为您提供了几种推广和扫描多架构容器映像的方法,最后,我们了解了使用多架构容器的注意事项。
使用多架构镜像使我们能够构建一次并在各处运行。我们可以轻松地从一个 CPU 架构无缝迁移到另一个 CPU。此外,通过部署针对特定架构优化的镜像,我们可以获得更好的性能并降低资源成本。