Dockerfile 多阶段构建实践

时间:2022-01-24 09:57:08

Dockerfile 多阶段构建实践

写在前面

在Docker Engine 17.05 中引入了多阶段构建,以此降低构建复杂度,同时使缩小镜像尺寸更为简单。这篇小作文我们来学习一下如何编写实现多阶段构建的Dockerfile

关于dockerfile基础编写可参考之前docker容器dockerfile详解


一 、不使用多阶段构建

我们知道在Dockerfile中每新增一个指令都会在镜像中生产新的层,一个高效的Dockerfile应该在继续下一层之前清除之前所有不需要的资源。

不使用多阶段构建时,我们通常会创建两dockerfile文件,一个用于开发及编译应用,另一个用于构建精简的生产镜像。这样能比较大限度的减小生产镜像的大小。

我们以一个go应用来看看。我首先会创建一个dockerfile,构建这个镜像的主要目的就是编译我们的应用。

FROM golang:1.16
WORKDIR /go/src
COPY app.go ./
#go编译
RUN go build -o myapp app.go

构建镜像

[root@localhost dockerfiles]# docker build -t builder_app:v1 .
Sending build context to Docker daemon 3.072kB
Step 1/4 : FROM golang:1.16
---> 019c7b2e3cb8
Step 2/4 : WORKDIR /go/src
---> Using cache
---> 15362720e897
Step 3/4 : COPY app.go ./
---> Using cache
---> 8f14ac97a68a
Step 4/4 : RUN go build -o myapp app.go
---> Running in 4368cc4617a7
Removing intermediate container 4368cc4617a7
---> 631f67587803
Successfully built 631f67587803
Successfully tagged builder_app:v1

这样在这个镜像中就包含了我们编译后的应用myapp,现在我们可以创建容器将myapp拷贝到宿主机等待后续使用。

# docker create --name builder builder_app:v1
fafc1cf7ffa42e06d19430b807d24eafe0bf731fc45ff0ecf31ada5a6075f1d5
# docker cp builder:/go/src/myapp ./

我们有了应用,下一步就是构建生产镜像

FROM scratch
WORKDIR /server
COPY myapp ./
CMD ["./myapp"]

由于此时我们不需要其他依赖环境,所以我们采用scratch这个空镜像,不仅可以减小容器尺寸,还可以提高安全性。

构建镜像

#docker build  --no-cache -t server_app:v1 .

我们看一次构建的两个镜像大小

# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
server_app v1 6ebc0833cad0 6 minutes ago 1.94MB
builder_app v1 801f0b615004 23 minutes ago 921MB

显然在不使用多阶段构建时,我们也可以构建出生产镜像,但是我们需要维护两个dockerfile,需要将app遗留到本地,并且带来了更多存储空间开销。在使用多阶段构建时能比较好的解决以上问题。


二、使用多阶段构建

在一个Dockerfile中使用多个FROM指令,每个FROM都可以使用不同的基镜像,并且每条指令都将开始新阶段构建。在多阶段构建中,我们可以将资源从一个阶段复制到另一个阶段,在最终镜像中只保留我们所需要的内容。

我们将上面实例的两个Dockerfile合并为如下:

#阶段1
FROM golang:1.16
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#阶段2
FROM scratch
WORKDIR /server
COPY --from=0 /go/src/myapp ./
CMD ["./myapp"]

构建镜像

# docker build --no-cache  -t server_app:v2 .

查看构建好的镜像

# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
server_app v2 20225cb1ea6b 12 seconds ago 1.94MB

这样我们无需创建额外镜像,以更简单的方式构建出了同样微小的目标镜像。可以看到在多阶段构建dockerfile中最关键的是COPY --from=0 /go/src/myapp ./ 通过 --from=0指定我们资源来源,这里的0即是指第一阶段。

命令构建阶段

默认情况下构建阶段没有名称,我们可以通过整数0~N来引用,即第一个from从0开始。其实我们还可以在FROM指令中添加AS <NAME> 来命名构建阶段,接着在COPY指令中通过<NAME>引用。我们对上面dockerfile修改如下:

#阶段1命名为builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#阶段2
FROM scratch
WORKDIR /server
#通过名称引用
COPY --from=builder /go/src/myapp ./
CMD ["./myapp"]
只构建某个阶段

构建镜像时,您不一定需要构建整个 Dockerfile,我们可以通过--target参数指定某个目标阶段构建,比如我们开发阶段我们只构建builder阶段进行测试。

#docker build --target builder -t builder_app:v2 .
使用外部镜像

使用多阶段构建时,我们局限于从之前在 Dockerfile 中创建的阶段进行复制。还可以使用COPY --from指令从单独的镜像复制,如本地镜像名称、本地或 Dockerhub上可用的标签或标签 ID。Docker 客户端在必要时会拉取需要的镜像到本地。

COPY --from  httpd:latest /usr/local/apache2/conf/httpd.conf ./httpd.conf
从上一阶段创建新的阶段

我们可以通过FROM指令来引用上一阶段作为新阶段的开始

#阶段1命名为builder
FROM golang:1.16 as builder
WORKDIR /go/src
COPY app.go ./
RUN go build app.go -o myapp
#阶段2
FROM builder as builder_ex
ADD dest.tar ./
...

通过上面我们对dockerfile多阶段构建有了一个整体的了解。


NEXT

  • Dockerfile 与Docker容器安全实践

希望小作文对你有些许帮助,如果内容有误请指正。

您可以随意转载、修改、发布本文章,无需经过本人同意。 个人blog:iqsing.github.io