Docker镜像多架构构建

Posted 啊码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker镜像多架构构建相关的知识,希望对你有一定的参考价值。

目前arm系统越来越常见,对镜像的多架构需求也越来越大。对于同一个镜像,最简单的办法就是在amd64或arm机器上build后通过不同的tag进行区分,比如 nginx:v1-amd64 、 nginx:v1-arm64 ,但这种方式比较丑陋,而且没有对应架构的机器用来构建怎么办?

目前最新的办法就是使用buildx来进行构建,不过这个特性目前默认是没启用的,需要在docker的配置文件中添加 “experimental”: true 后重启docker服务生效。

首先执行下面的命令让amd64的机器也可以构建arm的镜像:

docker run --rm --privileged tonistiigi/binfmt:latest --install all

然后创建一个新的build实例:

docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master

这样准备工作就全都做好了。

接下来以一个kubebuilder命令创建的operator项目默认的Dockerfile为例:

# Build the manager binary
FROM --platform=$TARGETPLATFORM golang:1.16 as builder

ARG TARGETOS TARGETARCH

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/

# Build
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -a -o manager main.go

# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM  --platform=$TARGETPLATFORM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/manager .
USER 65532:65532

ENTRYPOINT ["/manager"]

修改点有2个:

--platform=$TARGETPLATFORM
GOOS=$TARGETOS GOARCH=$TARGETARCH

这些TARGET开头的变量可以在参考链接2里看到全部含义。

接下来使用这个文件进行构建:

docker buildx build -t hello/namespace/name:v1 -f Dockerfile .  --platform linux/amd64,linux/arm64 --push

注意这里的 buildx 、 --platform 参数后面跟随需要构建的版本、以及 --push ,buildx构建的多架构镜像要么使用这个参数push到镜像仓库,要么使用 --load 加载到本地,不可省略。

构建完成后就会生成相应的多架构镜像了,可以使用 docker manifest inspect 来进行验证,比如:

docker manifest inspect hello/namespace/name:v1

   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1367,
         "digest": "sha256:a7b99854e13939e3beaf21c1da8b4654022018eda9f438155b18ae16eeff49a5",
         "platform": 
            "architecture": "amd64",
            "os": "linux"
         
      ,
      
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 2169,
         "digest": "sha256:844885928813685ffa8c5ea4c6e9e7a09d95ac79e417afae0be7b73086762cfd",
         "platform": 
            "architecture": "arm64",
            "os": "linux"
         
      
   ]

可以看出确实是生成了多个架构的镜像,使用时直接在不同架构的机器上pull就会自动下载对应的架构镜像了。或者也可以使用 docker pull --platform arm64|amd64 xxxxx 来指定拉取镜像的架构。

对于某些没有办法使用buildx的场景,我们可以手动build不同架构的镜像,然后再手动创建manifest,比如:

# 指定拉取amd64架构
docker pull --platform amd64 gcr.io/distroless/static:nonroot
# 重新打tag
docker tag 9ef34  hello/ns/static:nonroot-amd64
# 推送
docker push hello/ns/static:nonroot-amd64

# 指定拉取arm64架构
docker pull --platform arm64 gcr.io/distroless/static:nonroot
# 重新打tag
docker tag 91714  hello/ns/static:nonroot-arm64
# 推送
docker push hello/ns/static:nonroot-arm64

## 制作manifest
docker manifest create hello/ns/static:nonroot  hello/ns/static:nonroot-amd64 hello/ns/static:nonroot-arm64
docker manifest push hello/ns/static:nonroot
docker manifest rm hello/ns/static:nonroot

重点是最后3行,push manifest后使用就和第一种方法一样了。

另外如果想进行多架构构建有几个注意点:

yum|apt install

其他
查看镜像仓库里都有哪些镜像:

curl -u "用户名":"密码"  -X GET http://镜像地址:5000/v2/_catalog?n=2000  | python -m json.tool

查看镜像有哪些tag:

curl -u "用户名":"密码" -X GET http://镜像地址:5000/v2/命名空间/镜像名称/tags/list | python -m json.tool

谈谈 Docker 镜像多阶段构建

文章目录

导读

通常情况下,构建镜像通常会采用两种方式:

  1. 将全部组件及其依赖库的编译、测试、打包等流程封装进一个

    Docker

    镜像中。采用这种方式往往忽略了以下这些问题:

    • Dockefile 特别长,可维护性降低。
    • 镜像的层次多,体积大,部署时间长。
    • 源代码存在泄漏的风险。
  2. 分散到多个 Dockerfile。事先在一个 Dockerfile 将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 Dockerfile 和一些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种方式存在的风险,但明显部署过程较复杂。

为了解决以上这些问题,Docker v17.05 开始支持多镜像阶段构建 (multistage builds)。只需要编写一个 Dockerfile 即可。

对于我这种深度“极简主义者”来说,这简直就是福音。但是我一直没有写这个,就代表了我的看法。等我详细介绍完,再列举例子之后,大家应该会有自己的想法。


案例

通过一段简单的 C 语言代码的编译、执行来具体演示。demo.c 的内容如下:

# include<stdio.h>
int main()

  printf("%s\\n","This is a demo!");
  return 0;

2.1 只通过一个 Dockerfile 来构建【方案一】

查看对应的 Dockerfile:

FROM centos:7.8.2003

ENV VERSION 1.0

WORKDIR /demo

COPY demo.c .

RUN yum install -y gcc && \\
    gcc -v
RUN gcc demo.c -o demo && \\
    rm -f demo.c && \\
    yum erase -y gcc && \\
    cp demo /usr/local/bin/

CMD ["demo"]

感兴趣的小伙伴可以直接将上面的 Dockerfile 和 docker-entrypoint.sh 在本地构建目录创建,执行 docker build -t redis:6.0.5-buster 进行尝试。

2.2 多个 Dockerfile 实现多阶段构建【方案二】

多阶段构建一般需要多个 Dockerfile 来完成,由于我们只需要源码编译后的产物。所以我们第一个阶段可以直接使用上文中镜像构建后的产物。第二阶段的 Dockerfile 内容如下:

FROM centos:7.8.2003

ENV VERSION 1.0

WORKDIR /demo

COPY demo /usr/local/bin

CMD ["demo"]

执行构建脚本 bash build.sh, build.sh 的内容如下:

#!/bin/bash
cd stage-1
docker create --name bin demo:1.0
cd ../stage-2
docker cp bin:/usr/local/bin/demo .
docker rm -f bin
docker build -t demo:2.0 .

构建后得到的 Docker 容器运行结果:

$ docker run --rm -it demo:1.0
This is a demo!
$ docker run --rm -it demo:2.0
This is a demo!

两个容器的环境变量:

$ docker run --rm -it demo:1.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=a52af1bec0af
TERM=xterm
VERSION=1.0
HOME=/root
$ docker run --rm -it demo:2.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=f6618fd1244b
TERM=xterm
VERSION=2.0
HOME=/root

2.3 一个 Dockerfile 实现多阶段构建【方案三】

FROM centos:7.8.2003

ENV VERSION 1.0

WORKDIR /demo

COPY demo.c .

RUN yum install -y gcc && \\
    gcc -v
RUN gcc demo.c -o demo && \\
    rm -f demo.c && \\
    yum erase -y gcc && \\
    cp demo /usr/local/bin/

FROM centos:7.8.2003
COPY --from=0 /usr/local/bin/demo /usr/local/bin/demo

CMD ["demo"]

这种方式构建的 Docker 容器运行结果:

$ docker run --rm -it demo:3.0
This is a demo!
$ docker run --rm -it demo:3.0 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=7839b3d568db
TERM=xterm
HOME=/root

三个镜像大小对比:

$ docker images|grep demo
demo                3.0                  8766031d380a        39 seconds ago           203MB
demo                2.0                  7d9c479cb421        10 minutes ago           203MB
demo                1.0                  af331209572f        38 minutes ago           350MB

总结

  • 通过观察,方案一构建得到镜像远比方案二和方案三大得多,方案二和方案三的镜像一样大小。
  • 方案三并不会继承第一阶段构建的镜像的环境变量等配置,仅仅是复制了第一阶段的构建成果,需要特别注意。

其实,我的顾虑在这一行:

COPY --from=0 /usr/local/bin/demo /usr/local/bin/demo

一开始我会疑惑,因为运行这个程序需要一些内部库的,直到我研究了 Docker 的分层系统。但是,我只是对这一个小项目不疑惑了,一旦要封装的内容复杂起来,需要对整个镜像的把控到一定程度,才能做这种操作,不然少那么一两个关联文件,嘿嘿。所以,要对整个项目有一定的把控。


其他操作

停在特定的构建阶段

构建镜像时,不一定需要构建整个Dockerfile每个阶段。

您可以指定目标构建阶段。以下命令假定您使用的是以前的Dockerfile,但在名为builder的阶段停止:

$ docker build --target builder -t alexellis2/href-counter:latest .

使用此功能可能的一些非常适合的场景是:

  • 调试特定的构建阶段
  • 在debug阶段,启用所有调试或工具,而在production阶段尽量精简
  • 在testing阶段,您的应用程序将填充测试数据,但在production阶段则使用生产数据

使用外部镜像作为stage

使用多阶段构建时,您不仅可以从Dockerfile中创建的镜像中进行复制。

您还可以使用COPY –from指令从单独的image中复制,使用本地image名称,本地或Docker注册表中可用的标记或标记ID。

如有必要,Docker会提取image并从那里开始复制。

语法是:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

以上是关于Docker镜像多架构构建的主要内容,如果未能解决你的问题,请参考以下文章

Docker镜像多架构构建

如何保存/同步多架构容器 Docker 镜像

Docker镜像多架构构建

Docker 开启 buildx 多CPU架构镜像制作

云原生之Docker实战使用Dockerfile构建docker镜像

Docker