[Docker轻量化] 在多阶段构建中,阶段间的COPY最好使用绝对路径

当我打开书准备学习k8s时,却意外地遇到了关于Docker镜像轻量化的内容,所以我会把它记录下来作为备忘录。

多阶段构建是指

简而言之,可以通过一个Dockerfile逐步地使用多个基础镜像。

举个例子,如果要创建一个用于 golang 应用程序的镜像,如果是我,我会这样写…

# 通常(シングルステージビルド?)の書き方
FROM golang:1.14.1-alpine3.11

COPY ./main.go ./

RUN go build -o ./app ./main.go

ENTRYPOINT ["./app"]

我是一个业余工程师,所以我选择使用Alpine镜像轻量便携!(自豪)哈哈

# イメージビルド
$ docker build -t goapp1 .

# イメージサイズの確認
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
goapp1              latest              e44201621bca        6 seconds ago       377MB
alpine              3.11                e389ae589224        4 months ago        5.62MB
golang              1.14.1-alpine3.11   760fdda71c8f        17 months ago       370MB

但是,据说从这里开始可以进一步减轻重量。

对每个处理进行基础图像分割,并只保留必要的部分在最终图像中进行轻量化。

利用多阶段构建,可以将构建图像和执行图像分开。

# マルチステージビルド

# 1つ目のベースイメージにbuilderという名前を付ける
FROM golang:1.14.1-alpine3.11 as builder
# ローカルファイルをbuilderイメージ内にコピー
COPY ./main.go ./
# ビルド先を指定
RUN go build -o /app ./main.go

# 計量イメージを2つ目のベースイメージとして指定
FROM alpine:3.11
# builderイメージからファイルをコピー
COPY --from=builder /app .
# 2つ目のイメージだけコマンド実行
ENTRYPOINT ["./app"]

处理内容如下,build和run的各个步骤在两个图像之间协同合作并分担。

使用 golang:1.14.1-alpine3.11 镜像构建 main.go 文件。

将使用builder镜像构建的app二进制文件复制到alpine:3.11镜像中。

在Alpine 3.11镜像上运行应用程序。

因此,最终只需使用第二个alpine:3.11镜像,以减轻镜像的负担。

# マルチステージビルドを使用したDockerfileからイメージビルド
$ docker build -t goapp2 .

# イメージサイズの確認
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
goapp2              latest              8fb96a4a5138        3 seconds ago       13.1MB
<none>              <none>              a8884f1d05c5        4 seconds ago       377MB
goapp1              latest              e44201621bca        13 minutes ago      377MB
alpine              3.11                e389ae589224        4 months ago        5.62MB
golang              1.14.1-alpine3.11   760fdda71c8f        17 months ago       370MB

可以从goapp1(377MB)的大小降低到goapp2(13.1MB)这一点看出,图像已经被轻量化了。

如果第一个构建器的图像还呈现为,如果不需要的话可以安全删除。

# IMAGE IDを指定して, <none>イメージ(builderイメージ)を削除
$ docker image rmi a8884f1d05c5

# イメージ一覧を表示
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
goapp2              latest              8fb96a4a5138        3 minutes ago       13.1MB
goapp1              latest              e44201621bca        16 minutes ago      377MB
alpine              3.11                e389ae589224        4 months ago        5.62MB
golang              1.14.1-alpine3.11   760fdda71c8f        17 months ago       370MB

在进行舞台之间的复制时需要注意的事项

在尝试使用多阶段构建时,遇到了一个问题,即在拷贝阶段时使用相对路径时会出现错误。

# [NG例!!!]

FROM golang:1.14.1-alpine3.11 as builder
COPY ./main.go ./
# "相対パス"でアウトプット先を指定
RUN go build -o ./app ./main.go

FROM alpine:3.11
# builderイメージから"相対パス"でファイルをコピー
COPY --from=builder ./app .
ENTRYPOINT ["./app"]

建映像结果

$ docker build -t ng-goapp .
Sending build context to Docker daemon    6.4MB
Step 1/6 : FROM golang:1.14.1-alpine3.11 as builder
 ---> 760fdda71c8f
Step 2/6 : COPY ./main.go ./
 ---> Using cache
 ---> 422b5182bade
Step 3/6 : RUN go build -o ./app ./main.go
 ---> Using cache
 ---> 524167b98ef9
Step 4/6 : FROM alpine:3.11
 ---> e389ae589224
Step 5/6 : COPY --from=builder ./app .
COPY failed: stat /var/lib/docker/overlay2/68c7926017fabca29e5aa3a12024d52b2e6e2f000dd08f046a8f698fc2079bf1/merged/app: no such file or directory
# ファイルが見つからず, エラーになる!

我想要解决上述的错误。

不同镜像的默认当前目录未必相同。

在进行舞台之间的复制时,使用相对路径会导致错误的原因是,每个基础镜像的默认当前目录不同。

FROM golang:1.14.1-alpine3.11
# カレントディレクトリを表示
ENTRYPOINT ["pwd"]

在第一个(builder)图像中,默认的当前目录似乎是/go。

$ docker image build -t test1 .
$ docker run test1
/go

以相同的步骤,检查第二个应用程序执行图像中的默认当前目录。

FROM alpine:3.11
# カレントディレクトリを表示
ENTRYPOINT ["pwd"]

这似乎是。

$ docker image build -t test2 .
$ docker run test2
/

因此,由于/main.go在第一个图像中在/go/app中构建,而在第二个图像中试图从/app复制,所以导致了错误。

[结论] 在两个阶段之间进行复制时,最好使用绝对路径。

总的来说,由于根据基础映像的不同,相对路径的解释可能会有差异,因此在使用多阶段构建时,使用绝对路径来进行COPY操作可以减少额外的错误。

此外,可以说,为了安全起见,需要使用绝对路径来指定main.go的构建位置。

# マルチステージビルド

FROM golang:1.14.1-alpine3.11 as builder
COPY ./main.go ./
# "絶対パス"でビルド先を指定
RUN go build -o /app ./main.go

FROM alpine:3.11
# builderイメージから"絶対パス"でファイルをコピー
COPY --from=builder /app .
ENTRYPOINT ["./app"]

Docker镜像在构建过程中很难进行调试,所以在Dockerfile中明确处理是关键。我受益匪浅。

请提供参考文献

《Kubernetes完全指南 第2版》是由青山真也撰写,出版于2020年的一本书籍,出版社为印象社。

网站
Docker文档“编写最佳实践的Dockerfile – WORKDIR
Web工程师的杂记“关于Dockerfile的COPY命令”(2019年12月20日)

bannerAds