Dockerfile的COPY --link

文章目录

  • 总结
  • 环境
  • 概述
  • “ --link” 是什么
  • 引入“ --link”
  • 使用“COPY --link”
  • 示例
  • 什么情况不适用
  • 总结
  • 参考

注:我做了很多测试,发现不管是否使用 --link ,结果貌似都一样。我在网上搜了半天,最后发现,该功能貌似目前被disable了,参见 https://github.com/docker/buildx/issues/1099 。等什么时候可用了,做完测试,再来完善该文章。

总结

  • 优点
    • 时间:独立的layer,提高了缓存的复用性,节省时间
    • 空间:向registry里push的image里只包含必要的layer,节省空间
    • cache-tocache-from :todo
  • 局限
    • 不能依赖于前面layer的文件系统
    • symlink

环境

  • RHEL 9.3
  • Docker Community 24.0.7

概述

COPY --link 是BuildKit的新功能,用来加速Docker image构建。它把文件复制到独立的image layer,并不需要上一步的layer存在。可以在base image不存在的情况下,为image添加新的内容。

该功能在作为Buildx v0.8的一部分,添加于2022年3月。Docker CLI的20.10.14版本包含了该功能。

“ --link” 是什么

--link 是Dockerfile的 COPY 指令的一个选项。

传统的 COPY 语句把文件复制到前一个layer里,该layer必须已存在,然后把新的内容合进来。

下面是一个传统的Dockerfile:

FROM alpine
COPY my-file /my-file
COPY another-file /another-file
  • FROM 指令之后,image包含了Alpine的内容:
bin/
dev/
etc/
......
  • 第一个 COPY 指令创建了一个image,包含了Alpine的内容以及 my-file 文件:
my-file
bin/
dev/
etc/
......
  • 同理,第二个 COPY 指令在该image之上,添加了 another-file 文件:
another-file
my-file
bin/
dev/
etc/
......

每条指令所创建的layer,包含了之前的所有东西和本指令新加的内容。在构建完成时,Docker使用一个“diff”进程检测出每个layer的变化。最终的image仅包含了在每个快照stage所添加的文件,但这并没有在构建的装配过程中体现出来(注:我理解这句话的意思是说,构建过程有一些冗余的东西)。

引入“ --link”

每次使用 --link 时,Docker会创建一个新的单独的文件系统。新文件不再复制到上一个layer里,而是复制到一个完全不同的位置,形成一个独立的layer。这些layer随后被链接在一起,产生最终的image。

下面是使用了 --link 的Dockerfile:

FROM alpine
COPY --link my-file /my-file
COPY --link another-file /another-file
  • FROM 指令不变,仍然创建了Alpine layer,包含了该image的所有内容:
bin/
dev/
etc/
......
  • 第一条 COPY 指令这次创建了一个独立的layer,它是一个仅包含 my-file 文件的新的文件系统:
my-file
  • 同理,第二条 COPY 指令也创建了另一个新的快照,其中只包含 another-file 文件:
another-file

当构建完成时,Docker把这些独立的快照保存为新的layer archive(tarball)。这些tarball被链接回之前的layer链里,构建出最终的image。它由这三个快照组合在一起。当创建容器时,其文件系统和原先(不用 --link 时)是一致的:

my-file
another-file
bin/
dev/
etc/
......

下图展示了这两种工作方式的不同:

Dockerfile的COPY --link_第1张图片

使用“COPY --link”

COPY --link 只有在使用BuildKit构建image时才可以使用:

  • 使用 docker buildx --create 构建,或者:
  • 构建时设置环境变量 DOCKER_BUILDKIT=1

此外,必须指定Dockerfile v1.4语法。

# syntax=docker/dockerfile:1.4
FROM alpine:latest
COPY --link my-file /my-file
COPY --link another-file /another-file

构建:

DOCKER_BUILDKIT=1 docker build -t my-image:latest .

构建好的image并无差异, --link 只影响构建过程。

示例

使用 --link ,即使复制的内容发生变化,也可以复用build cache。此外,即使base image不存在,也可以完成构建。

回到上面的例子。在添加新内容之前,标准的 COPY 行为要求 alpine image必须已存在于宿主机上。如果之前没有pull过,则在构建时,该image会自动下载。

对于链接复制,Docker则不需要 alpine image里的内容。它pull alpine 的manifest,为复制的文件创建新的独立layer,然后创建一个修订版的manifest,把这些layer链接到alpine的layer里。只有从新的image启动容器,或者导出tar achive时,alpine image的内容才会下载。当你push新image到registry时,registry只存储该image的新layer,并从远端获取alpine的layer。

该功能也有利于高效的image rebase。你可能正在使用当时最新的Ubuntu 20.04 LTS做Docker image:

FROM golang AS build
...
RUN go build -o /app .

FROM ubuntu:20.04
COPY --link --from=build /app /bin/app
ENTRYPOINT ["/bin/app"]

下面是一个完整的例子:

当前目录结构如下:

tree
.
├── Dockerfile
└── gotest1
    ├── go.mod
    └── hello.go

1 directory, 3 files
  • go.mod 文件如下:
module test1

go 1.21.6
  • hello.go 文件如下:
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
  • Dockerfile 文件如下:
FROM golang AS build
WORKDIR /myproject
RUN --mount=type=bind,source=gotest1,target=gotest1 cd gotest1 && go build -o /app .

FROM ubuntu:20.04
COPY --link --from=build /app /bin/app
ENTRYPOINT ["/bin/app"]

在建构image时,你可以使用BuildKit的 --cache-to 选项来启用缓存。 inline 缓存在输出image里存储了构建的缓存数据,以便在后续的构建中重用。

docker buildx build --cache-to type=inline -t kaigotest1:20.04 .

注: buildx 可省略。

运行容器:

docker run kaigotest1:20.04
Hello, World!

假设,后来你想要使用Ubuntu 22.04:

FROM ubuntu:22.04

重新构建image时,可使用之前版本里嵌入的缓存数据:

docker buildx build --cache-from kaigotest1:20.04 -t kaigotest1:22.04 .

构建几乎瞬间就完成了。通过使用已有image里的缓存数据,Docker会验证构建 /app 的文件没有发生变化。这就意味着,通过 COPY 指令所创建的独立layer里的缓存仍然有效。由于该layer不依赖于其它layer, ubuntu:22.04 image也不会被pull下来。Docker仅仅在 ubuntu:22.04 layer链里,把包含 /bin/app 的快照layer链接到一个新的manifest里。快照layer被高效的“rebase”于一个新的parent image,而无需对文件系统的操作。

运行容器:

docker run kaigotest1:22.04
Hello, World!

该模型也可以优化multi-stage构建,其中任意stage之间都可能发生变化。

FROM golang AS build
RUN go build -o /app .

FROM config-builder AS config
RUN generate-config --out /config.yaml

FROM ubuntu:latest
COPY --link --from=config /config.yaml build.conf
COPY --link --from=build /app /bin/app

若没有 --link ,则产生的 config.yaml 文件的任何变化,都会导致 ubuntu:latest 被pull以及文件被复制。由于文件系统变化,造成缓存无效,还得重新编译。有了链接复制, config.yaml 文件的变化不会导致pull ubuntu:latest 或者重新编译。包含 build.conf 的快照layer简单的被替换为一个新的版本,这与其它layer是无关的。

什么情况不适用

有些情况下不适用 --link 选项。由于文件复制到一个新的layer,而不是添加到上一个layer里,所以在目标路径里,不能使用模糊引用(注:意指既可能是目录,也可能是文件,参见下面解释):

COPY --link my-file /data

在传统的 COPY 指令里,如果 /data 是image里已存在的一个目录,则 my-file 被复制为 /data/my-file 文件。而如果使用了 --link ,则目标layer的文件系统一定是空的,所以 my-file 一定会被复制到 /data 文件。

symlink(意指Linux的软链接文件)解析也有同样的考虑。标准的 COPY 指令会自动解析symlink的目标路径。而如果使用 --link ,则不支持该行为,因为symlink在copy的独立layer里不存在。

当没有遇到上述问题时,推荐使用 --link 。该功能会加速构建,并使得缓存更强大。由于这些非向后兼容的变化, --link 是一个选项(需显式指定),而不是缺省的功能。

总结

BuildKit的 COPY --link 使得构建更快更高效。使用链接复制的image无需pull之前的layer,仅仅是把文件复制过来。Docker会为每个 COPY 指令创建一个新的独立layer,然后把这些layer链接回layer链里。

参考

  • https://docs.docker.com/engine/reference/builder/#copy---link
  • https://www.howtogeek.com/devops/how-to-accelerate-docker-builds-and-optimize-caching-with-copy-link
  • https://www.docker.com/blog/image-rebase-and-improved-remote-cache-support-in-new-buildkit (讲的非常清楚。另:文中貌似有个typo: COPY --from=build --link /out/myapp /bin 目标路径最后应该加上 /
  • https://github.com/docker/buildx/issues/1099

你可能感兴趣的:(docker,docker)