Docker 多阶段构建最佳实践

Java技术迷

共 4470字,需浏览 9分钟

 · 2021-06-07



Docker目前在容器市场可以说是占领了大部分市场,Docker掀起了容器革命,同时也改变了现代化云平台的构建方式。尽管Docker很强大,但使用过程当中也遇到了一些问题。比如说我想要构建一个编译型语言镜像,需要先在一个Dockerfile中编译,然后再使用另外一个Dockerfile把编译好的文件放到镜像中。这样无形当中就增大了CI/CD的复杂度。
Docker多阶段构建是17.05以后引入的新特性,旨在解决编译和构建复杂的问题。减小镜像大小。因此要使用多阶段构建特性必须使用高于或等于17.05的Docker。

多阶段构建出现之前

构建镜像最具挑战性的一点是使镜像大小尽可能的小。Dockerfile中的每条指令都为图像添加了一个图层,您需要记住在移动到下一层之前清理任何不需要的工件。
为了编写一个真正高效的Dockerfile,传统上需要使用shell技巧和其他逻辑来保持层尽可能小,并确保每个层都具有前一层所需的工件而不是其他任何东西。
实际上,有一个Dockerfile用于开发(包含构建应用程序所需的所有内容),以及用于生产环境的精简版Dockerfile,它只包含您的应用程序以及运行它所需的内容。这被称为“建造者模式”。维护两个Dockerfiles并不理想。
这是一个Dockerfile.build和Dockerfile的例子,它遵循上面的模式:
Dockerfile.build:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
请注意,此示例使用Bash &&运算符人为压缩两个RUN命令,以避免在image中创建其他层。这很容易出错并且难以维护。
Dockerfile:
FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
build.sh:
#!/bin/sh
echo Building alexellis2/href-counter:build
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \  
-t alexellis2/href-counter:build . -f Dockerfile.build
docker container create --name extract alexellis2/href-counter:build  
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app  
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app
当您运行build.sh脚本时,它需要构建第一个image,从中创建容器以复制工件,然后构建第二个image。
多阶段构建大大简化了这种情况!

使用多阶段构建

对于多阶段构建,您可以在Dockerfile中使用多个FROM语句。每个FROM指令可以使用不同的基础,并且每个指令都开始一个新的构建。您可以选择性地将工件从一个阶段复制到另一个阶段,从而在最终image中只留下您想要的内容。
为了说明这是如何工作的,让我们调整上述示例的Dockerfile以使用多阶段构建。
Dockerfile:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]  

您只需要单个Dockerfile。您也不需要单独的构建脚本。只需运行docker build:


$ docker build -t app:latest .

最终结果是产生与之前相同大小的image,复杂性显著降低。您不需要创建任何中间image,也不需要将任何artifacts提取到本地系统。
它是如何工作的?第二个FROM指令以alpine:latest image为基础开始一个新的构建阶段。
COPY –from = 0行仅将前一阶段的构建文件复制到此新阶段。Go SDK和任何中间层都被遗忘,而不是保存在最终image中。

为多构建阶段命名

默认情况下,阶段未命名,您可以通过整数来引用它们,从第0个FROM指令开始。
但是,您可以通过向FROM指令添加as NAME来命名您的阶段。此示例通过命名阶段并使用COPY指令中的名称来改进前一个示例。
这意味着即使稍后重新排序Dockerfile中的指令,COPY也不会中断。
FROM golang:1.7.3 as builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go    .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"] 

停在特定的构建阶段

构建镜像时,不一定需要构建整个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

链接: http://dockone.io/article/8179

1、Intellij IDEA这样 配置注释模板,让你瞬间高出一个逼格!
2、吊炸天的 Docker 图形化工具 Portainer,必须推荐给你!
3、最牛逼的 Java 日志框架,性能无敌,横扫所有对手!
4、把Redis当作队列来用,真的合适吗?
5、惊呆了,Spring Boot居然这么耗内存!你知道吗?
6、全网最全 Java 日志框架适配方案!还有谁不会?
7、Spring中毒太深,离开Spring我居然连最基本的接口都不会写了

点分享

点收藏

点点赞

点在看

浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报