在 Dockerfile 中传递变量主要有两种形式 ARG 和 ENV。在 Dockerfile 中虽然两者功能几乎相同,但是在实际容器中的作用域却是完全不同的。
下面会结合 SpringBoot 项目 docker-web-demo.jar 构建 Docker 镜像,并在启动时设置 JVM 相关参数来进行详细说明,相信看完本章之后就完全明白两种的区别以及在实际中该怎么使用了。
构建一个镜像
下面是一个简单的 Dockerfile 文件:
FROM openjdk:8WORKDIR appADD docker-web-demo.jar app.jarENTRYPOINT ["java", "-jar", "app.jar"]
Dockerfile 中的内容相信瞟一眼就明白了,就不做任何说明。现在要做的事先构建并运行该服务:
docker build -t web:v1 .
构建完成之后使用 docker run 命令后台运行容器(命令执行完成后会在控制台输出当前容器的ID):
$ docker run -d web:v1
d7c383dc605205b79ee5a348aa5139832a86d56a997f6fbb60b159477b7edfb2
关于容器ID:上面输出的ID特别长,实际上可以使用 docker container ls 命令查看容器列表,在列表中显示的是省略版更简短的ID,所以在后面的示例中会看到有时使用长ID有时使用短ID,其实都是一样的。
现在,有个容器的ID,我们就使用 docker exec 查看当前容器的环境变量。
查看运行状态的容器的环境变量语法是:
docker exec $container_id env
既然 docker 都提供了该命令,我们也就这么使用。这样,就省去了进入容器中查看环境变量的麻烦:
$ docker exec d7c383dc605205b79ee5a348aa5139832a86d56a997f6fbb60b159477b7edfb2 env
输出的环境变量如下:
PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d7c383dc6052
JAVA_HOME=/usr/local/openjdk-8
LANG=C.UTF-8
JAVA_VERSION=8u292
HOME=/root
走到这一步之后我们就来看下 ARG 和 ENV 的用法。
ARG 传递环境变量
相信各位都阅读过 docker 官网文档,在官网文档中有对 ARG 指令的具体使用和说明。所以这里就不再赘述如何使用,直接来看示例。
修改上面的 Dockerfile 文件内容,增加一个 ARG 指令,传递一个 JVM 启动参数:
FROM openjdk:8
WORKDIR app
ADD docker-web-demo.jar app.jar
ARG JAVA_OPS "-Xms64m -Xmx256m"
ENTRYPOINT ["/bin/bash", "-c", "java $JAVA_OPS -jar app.jar"]
这里需要注意,在 ENTRYPOINT 指令中使用了 "/bin/bash" 和 "-c"。这是为什么?如果不知道的话可以使用 man bash 或直接看下 https://man.cx/bash 对 -c 参数的说明,这里不再赘述。
之后开始构建并运行镜像:
$ docker build -t web:v2 .
$ docker run -d web:v2
拿到 web:v2 的容器ID 之后再来看下当前容器的环境变量:
$ docker exec 43467648bdb5 env
# 输出
PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=43467648bdb5
JAVA_HOME=/usr/local/openjdk-8
LANG=C.UTF-8
JAVA_VERSION=8u292
HOME=/root
你会发现输出的环境变量与之前的没有任何区别,这是为什么?不行,一定要使用 docker exec 交互式命令进入容器确认下 java 进程有没有使用定义的环境变量:
$ docker exec -it 43467648bdb5 /bin/bash
root@43467648bdb5:/app# ps -ef | grep java
root 1 0 8 14:09 ? 00:00:06 java -jar app.jar
root 47 40 0 14:10 pts/0 00:00:00 grep java
好吧,你会发现在容器中的 java 进程居然没有将我们在 Dockerfile 中设置的 JVM 参数设置上去!!!!
这说明一个很重要的问题:**ARG** 指令设置的环境变量不会在容器中生效!!!!
先别慌,我们先回过头来看下 ENV 指令。
ENV 传递环境变量
将上面使用 ARG 传递的环境变量修改为如下:
FROM openjdk:8
WORKDIR app
ADD docker-web-demo.jar app.jar
ARG OPS="-Xms64m -Xmx256m"
ENV JAVA_OPS=$OPS
ENTRYPOINT ["/bin/bash", "-c", "java $JAVA_OPS -jar app.jar"]
可以明显的看出区别,仅仅是将 ARG 指令定义的环境变量传递给 ENV 指令,先别问为什么,直接构建镜像并运行:
$ docker build -t web:v3 .
$ docker run -d web:v3
之后查看环境变量输出如下:
PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=999e902bde65
JAVA_HOME=/usr/local/openjdk-8
LANG=C.UTF-8
JAVA_VERSION=8u292
JAVA_OPS=-Xms64m -Xmx256m
HOME=/root
现在你会惊奇的发现环境变量 JAVA_OPS 生效了!!!!
这充分说明了 ENV 指令与 ARG 指令的区别:**ENV** 指令设置的环境变量作用于为容器,而 **ARG** 指令设置的环境变量仅仅作用于 Dockerfile 构建镜像阶段。
而且你会发现,ENV 设置的环境变量其实就相当于我们在 Linux 的 Profile 配置文件中使用 export 定义的环境变量。
现在你可能会有一个问题,明明有了 ENV 为什么还要有 ARG 指令呢?
现在就需要回到 Dockerfile 的构建阶段了。
镜像构建参数: —build-arg
我们使用 Dockerfile 构建镜像时使用的命令还记得吗?
docker build ...
其实在 build 命令后面还可以增加一个 --build-arg 参数,该参数接收一个 KEY=VALUE 键值对集合,多个使用空格进行分隔。看下 docker 对该参数的解释:
--build-arg list Set build-time variables
而使用 --build-arg 参数的目的就是在 Dockerfile 构建阶段传递一些默认配置,在 Dockerfile 想要获取传递的变量就需要使用 ARG 指令才行。换句话说,ARG 指令就是为了 --build-arg 而设计的。现在看下我们继续使用下面的 Dockerfile 文件,不做任何修改:
FROM openjdk:8
WORKDIR app
ADD docker-web-demo.jar app.jar
ARG OPS="-Xms64m -Xmx256m"
ENV JAVA_OPS=$OPS
ENTRYPOINT ["/bin/bash", "-c", "java $JAVA_OPS -jar app.jar"]
现在要做的就是在构建时传递一个 OPS 变量,命令如下所示:
$ docker build --build-arg OPS="-Xms64m -Xmx256m -Dspring.profiles.active=i18n" -t web:v4 .
构建完成后运行该镜像,并查看环境变量:
$ docker run -d web:v4
$ docker exec 44baee0ac90c env
# 环境变量如下
PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=44baee0ac90c
JAVA_HOME=/usr/local/openjdk-8
LANG=C.UTF-8
JAVA_VERSION=8u292
JAVA_OPS=-Xms64m -Xmx256m -Dspring.profiles.active=i18n
HOME=/root
注意看环境变量,你会发现 JAVA_OPS 成功改变了!!!!
到此,相信对 ARG 和 ENV 指令有了全面的了解。但是各位可能还会有一个问题,ENV 指令的目的仅仅就是一个桥接功能将 --build-arg 定义地动态变量进行一次传递?
哈哈,肯定不是的。笔者刚开始学习的时候也对这里特别的迷惑,之后仔细看 docker run 指令之后……
现在就来看下 ENV 指令的高级使用特性。
ENV 的高级特性
ENV 的高级特性主要体现在容器运行时。我们都知道镜像和容器的区别,在 docker 应用层面上来说,容器其实就是在镜像之上增加了一个读写层,镜像是无法修改的,因为它是只读层。
也就是说,我们在运行一个镜像成为容器阶段就可以相应的做些变量的定义。那这是哪个阶段呢?其实就是 docker run 阶段!
在运行一个镜像(docker run)时,其实有一个可选参数 -e。看下 docker 对该参数的解释:
-e, --env list Set environment variables
设置一个环境变量!并且支持多个,多个直线需要使用空格做分隔。
还记得之前 ENV 指令定义的环境变量的 KEY 是什么吗?是 JAVA_OPS!也就是说,我们在 run 一个镜像时可以使用 -e 参数修改环境变量 JAVA_OPS 的值!看下示例:
docker run -d -e "JAVA_OPS=-Dspring.profiles.active=local" web:v4
看下上面的命令,之前构建 web:v4 镜像时设置的 JAVA_OPS 变量的值是 -Xms64m -Xmx256m -Dspring.profiles.active=i18n 对吗?现在使用上面的命令运行之后再看看环境变量:
PATH=/usr/local/openjdk-8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=9633cfa762d4
JAVA_OPS=-Dspring.profiles.active=local
JAVA_HOME=/usr/local/openjdk-8
LANG=C.UTF-8
JAVA_VERSION=8u292
HOME=/root
看下 JAVA_OPS,有没有惊呆了~
到此相信也就不需要多说什么了,剩下的,痴儿,自悟去吧~
总结
最后来一个总结,ARG 指令的主要影响的是镜像构建阶段,在实际应用中更多的可能是根据不同环境做默认的环境配置,比如测试和开发环境默认激活的配置文件问题。
而 ENV 更多的是针对运行态的镜像,即容器。我们可以根据该容器要运行的物理机问题做些相应的优化,比如可以某个物理机的内存和CPU比较猛,所以就相应的将 JVM 参数开大点。如果遇到物理机性能比较差劲的情况,可能默认设置的 JVM 无法使用。这样我们也能够利用 ENV 指令在运行时根据物理机的实际性能做相应的修改。
好了,关于 ARG 和 ENV 指令就说这么多。
完结,撒花~
