只有搞懂 Dockerfile 中的 ARG 作用域, 才能算 Build 镜像 CI 刚入门

之前我们讨论了 面试问 Dockerfile 的优化, 千万不要只会说减少层数 , 详细说明 Dockerfile 的优化原理和操作方法, 给大家概括了 简单易记 的口诀。

今天, 我们继续来探讨一下, Dockerfile 中的另外一个利器 – ARG

如果说掌握 Dockerfile 的优化, 勉强算作读完秘籍的目录。 那只有 熟练 掌握了 ARG 的用法, 我们才是打出了神功的 第一拳

先来说说 ARG

官网文档中有介绍 Build time variables - build-arg , 从名字可以可以看到, ARG 是在构建时生效的。

通过 --build-arg=KEY=VALUE 这样的参数形式, 我们可以在构建时传入 实际 值, 而非在 Dockerfile 中 预设 值。 优势就是使用 ARG 可以有效的复用 Dockerfile。

上面的的文字比较拗口, 换句话说, 我们能完成更 通用 的构建 CI 模版, 兼容更多场景, 早点下班

简单的 Dockerfile ARG 案例

下面是一个 Dockerfile, 通过 ARG VERSION 字段传入需要使用的 alpine 版本

1
2
3
4
# 1.Dockerfile

ARG VERSION
FROM alpine:${VERSION}

通过 Makefile 管理了两个编译命令

1
2
3
4
5
6
7
8
9

version ?= latest

build:
	docker build -f 1.Dockerfile -t example.com/demo/arg:$(version) . --no-cache --build-arg=VERSION=$(version)

build.1:
	version=3.11 make build
	version=3.12 make build

执行结果, 注意看 version 部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ make build
    docker build -f 1.Dockerfile -t example.com/demo/arg:latest . --no-cache --build-arg=VERSION=latest
    => [internal] load metadata for docker.io/library/alpine:latest


$ make build.1
    version=3.11 make build
    docker build -f 1.Dockerfile -t example.com/demo/arg:3.11 . --no-cache --build-arg=VERSION=3.11
    => [internal] load metadata for docker.io/library/alpine:3.11

    version=3.12 make build
    docker build -f 1.Dockerfile -t example.com/demo/arg:3.12 . --no-cache --build-arg=VERSION=3.12
    => [internal] load metadata for docker.io/library/alpine:3.12 

ARG 有没有作用域?

但是 ARG 是可以定义在在 Dockerfile 中的任意位置的, 那么 ARG 变量有没有 作用域 呢? 或者说 ARG 变量的 生效范围 是什么。

接下来我们通过实验确认

实验过程

  1. 创建 Dockerfile 如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

## 在第一个 FROM 之前的所有 ARG , 在所有 FROM 中生效, 仅在 FROM 中生效
ARG image

FROM $image as stage1
RUN echo "stage1 -> base from image is : $image "
    # result: stage1 -> base from image is :

FROM $image as stage11
RUN echo "stage11 -> base from image is : $image "
    # result: stage11 -> base from image is :

FROM alpine:3.12 as stage2
## 在 FROM 后的 ARG, 仅在当前 FROM 作用域生效。 即尽在当前 阶段 (stage) 生效
ARG image
RUN echo "stage2 -> base from image is : $image "
    # stage2 -> base from image is : alpine:3.12

FROM alpine:3.12 as stage21
RUN echo "stage21 -> base from image is : $image "
    # stage21 -> base from image is :
  1. 执行 docker build 命令
1
# docker build --build-arg image=alpine:3.12 --no-cache .
  • build 结果展示
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Sending build context to Docker daemon  3.072kB
Step 1/10 : ARG image
Step 2/10 : FROM $image as stage1
 ---> d6e46aa2470d
Step 3/10 : RUN echo "stage1 -> base from image is : $image "
 ---> Running in ecb7be5dd9cc
stage1 -> base from image is :  ### image 结果未输出
Removing intermediate container ecb7be5dd9cc
 ---> 04807c8d53be
Step 4/10 : FROM $image as stage11
 ---> d6e46aa2470d
Step 5/10 : RUN echo "stage11 -> base from image is : $image "
 ---> Running in a90e45076345
stage11 -> base from image is :       ### image 结果未输出
Removing intermediate container a90e45076345
 ---> f2dbce837a1b
Step 6/10 : FROM alpine:3.12 as stage2
 ---> d6e46aa2470d
Step 7/10 : ARG image
 ---> Running in 5c8cec4c2f22
Removing intermediate container 5c8cec4c2f22
 ---> 999d9990bd91
Step 8/10 : RUN echo "stage2 -> base from image is : $image "
 ---> Running in 4407dcb0e0bb
stage2 -> base from image is : alpine:3.12     ### image 结果输出
Removing intermediate container 4407dcb0e0bb
 ---> e5ddd7a84f81
Step 9/10 : FROM alpine:3.12 as stage21
 ---> d6e46aa2470d
Step 10/10 : RUN echo "stage21 -> base from image is : $image "
 ---> Running in 64a0a3bb090c
stage21 -> base from image is :      ### image 结果未输出
Removing intermediate container 64a0a3bb090c
 ---> 82665f9a1037
Successfully built 82665f9a1037

对照组解析

在随后的 Dockerfile 中, 只定义了一个变量 image , 并在 FROMstage 中重复使用

  1. 对照组1: stage1stage11 均在 FROM 中使用了变量 $image: **作用域在所有 FROM

    • 成功拉取 FROM $image 并完成 layer 构建
    • 但是在 RUN 中无法正确输出结果,即 image 的值 alpine:3.12
  2. 对照组2: stage1 vs stage2: 作用域在 FROM stage 内部

  • stage2 的作用域中声明了 ARG image,且能正确输出结果。
  1. 对照组3: stage2 vs stage21: 作用域仅在当前 FROM stage 内部
    • 虽然 stage2stage21 上方且声明了 ARG image, 但 stage21 仍然不能不能正确输出结果。

结论

在 Docker 官网文档 Understand how ARG and FROM interact , 也有提到 ARG 的作用域范围

  1. FROM instructions support variables that are declared by any ARG instructions that occur before the first FROM.
  2. An ARG declared before a FROM is outside of a build stage, so it can’t be used in any instruction after a FROM. To use the default value of an ARG declared before the first FROM use an ARG instruction without a value inside of a build stage:
  1. 在第一个 FROM 之前的所有 ARG , 在所有 FROM 中生效, 仅在 FROM 中生效
  2. FROM 后的 ARG, 仅在当前 FROM 作用域生效。 即尽在当前 阶段 (stage) 生效

这么说比较抽象, 用一个案例概括,一张图可以很好的展示

  1. VERSION 在 FROM 前, 全局仅在 FROM 中生效, 即 3,5,15 行。
  2. DATE 在 FROM 后, 在 stage1 内, 仅在 stage1 中生效, 即 7-14 行。
  3. TIME 在 FROM 后, 在 stage1 内, 仅在 stage1 中生效, 即 11-14 行。

build-arg-scope