[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日)