1 前言
前两章,我们大致介绍了Docker的基础操作和常见命令,实际生产开发过程中,企业或组织都有自己的一套产品和应用,类似以前Windows出现过很多定制化的系统,如Ghost xxxx,深度等。那对于Docker构建的镜像,是怎么实现定制化或个性化或一致性环境的镜像呢?本章将给予你答案。
2 命令
Dockerfile 由一行行命令语句
组成,并且支持以 # 开头的注释行。使用docker build
即可执行脚本构建镜像,自动地去做一些事(同类似于travis-ci
中的.travis.yml
)。
2.1 格式
Dockerfile 的格式统一为:
# Comment
INSTRUCTION arguments
注意:必须以
FROM BASE_IMAGE
开头指定基础镜像!!!除开解析器指令
、注释
和具有全局作用域的ARG
。
2.2 语法
Dockerfile 基本的语法如下:
- 使用 # 来注释
MAINTAINER
:镜像作者信息,新版本已废弃。推荐使用LABEL
指令;LABEL
:添加镜像的元数据,使用键值对
的形式;FROM
:指令告诉 Docker 使用哪个镜像作为基础;RUN
:开头的指令会在创建中运行,比如安装一个软件包,在这里使用 yum 来安装了一些软件;COPY
:从 Docker 宿主机复制文件至创建的新镜像文件;ADD
:类似于 COPY 指令,ADD 支持 tar 文件和 URL 路径;WORKDIR
:用于为 Dockerfile 中所有的 RUN、CMD、ENTRYPOINT、COPY、ADD 指定设定工作目录;VOLUME
:数据卷,用于在 image 中创建挂载点目录,以挂载 Docker host 上的卷或者其他容器上的卷;EXPOSE
:为容器打开指定的监听端口以实现与外部通信;ENV
:用于为镜像定义所需的环境变量,可以被 Dockerfile 文件中其他命令调用(ENV、ADD、COPY、RUN、CMD);ARG
:定义在构建过程中传递给构建器的变量,可使用docker build
命令设置;CMD
:启动容器指定默认要运行的程序或命令,默认”/bin/sh -c”运行;ENTRYPOINT
:类型 CMD 指令的功能,用于为容器指定默认运行程序或命令;USER
:指定当前用户。
下面介绍几个常用的命令,其他命令的具体用法,可参考官方文档,见文章末尾参考文献。
2.2.1 FROM
指定基础镜像。
2.2.1.1 格式
FROM BASE_IMAGE
# 或者
FROM BASE_IMAGE:Tag
第一条指令必须为
FROM
指令(参见2.1节说明)。并且,如果在同一个Dockerfile
中创建多个镜像时,可以使用多个 FROM 指令
(每个镜像一次)。
2.2.1.2 举例
FROM centos:7
2.2.2 RUN
构建镜像时执行的命令。
2.2.2.1 格式
shell 格式:
#shell 格式: RUN <命令行命令> # <命令行命令> 等同于,在终端操作的 shell 命令。 # 例如: # RUN apt-get update # RUN apt-get install -y curl
exec 格式:
RUN ["可执行文件", "参数1", "参数2"] # 例如: # RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
shell格式
是最常用的,它允许您更轻松地将较长的指令分解为多行,可以使用换行符转义符
或使用heredocs
,例如:
RUN <<EOF
apt-get update
apt-get install -y curl
EOF
注意:Dockerfile 的指令每执行一次都会在 docker 上新建一层。所以过多无意义的层,会造成镜像膨胀过大。例如:
FROM centos
RUN yum -y install wget
RUN wget -O redis.tar.gz “http://download.redis.io/releases/redis-5.0.3.tar.gz“
RUN tar -xvf redis.tar.gz以上执行会创建 3 层镜像。可简化为以下格式:
FROM centos
RUN yum -y install wget
&& wget -O redis.tar.gz “http://download.redis.io/releases/redis-5.0.3.tar.gz“
&& tar -xvf redis.tar.gz
如上,以 && 符号连接命令,这样执行后,只会创建 1 层镜像。
2.2.3 ENV
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
2.2.3.1 格式
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
2.2.3.2 举例
以下示例设置NODE_VERSION = 7.2.0
, 在后续的指令中可以通过$NODE_VERSION
引用:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
2.2.4 ARG
构建参数,与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有docker build
的过程中有效,构建好的镜像内不存在此环境变量。也称为构建时变量
。
构建命令docker build
中可以用--build-arg <参数名>=<值>
来覆盖。
2.2.4.1 格式
ARG <参数名>[=<默认值>]
如果在Dockerfile中期望使用各种ARG变量(无默认值),但在运行build命令时未提供任何变量,则会出现错误消息。
[Warning] One or more build-args [foo] were not consumed.但是,在构建镜像后,可以通过查看镜像
docker history
来轻松检查ARG值。因此,对于敏感数据而言,它们是一个糟糕的选择。
2.2.4.2 举例
ARG some_variable_name
# or with a hard-coded default:
#ARG some_variable_name=default_value
RUN echo "Oh dang look at that $some_variable_name"
# you could also use braces - ${some_variable_name}
从命令行构建Docker镜像时,可以使用--build-arg
来设置ARG值:
$ docker build --build-arg some_variable_name=a_value
使用上述Dockerfile运行该命令将导致打印以下行(以及其他内容):
Oh dang look at that a_value
更多关于ARG的用法和注意事项,参考ARG官方文档
ARG与ENV可用性与重写样例如下图所示:
2.2.5 CMD
构建镜像后调用,也就是在容器启动时
才进行调用。
类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
- CMD 在
docker run
时运行。- RUN 是在
docker build
。
2.2.5.1 格式
CMD <shell 命令>
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
推荐使用第二种格式
,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh。
2.2.5.2 举例
CMD echo "This is a test." | wc -l
CMD ["/usr/bin/wc","--help"]
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
2.2.6 EXPOSE
指定于外界交互的端口
。
2.2.6.1 格式
# 格式:
EXPOSE <port> [<port>...]
2.2.6.2 举例
EXPOSE 80 443
EXPOSE 8080
EXPOSE 11211/tcp 11214/udp
注:
EXPOSE并不会让容器的端口访问到主机。要使其可访问,需要在docker run
运行容器时通过-p
来发布这些端口,或通过-P
参数来发布EXPOSE
指定的所有端口。如果没有暴露端口,后期也可以通过
-p 8080:80
方式映射端口,但是不能通过-P
形式映射.
2.2.7 ADD
将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
2.2.7.1 格式
ADD <src>... <dest>
ADD ["<src>",... "<dest>"] 用于支持包含空格的路径
2.2.7.2 举例
# 添加所有以"hom"开头的文件
ADD hom* /mydir/
# ? 替代一个单字符,例如:"home.txt"
ADD hom?.txt /mydir/
# 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test relativeDir/
# 添加 "test" 到 /absoluteDir/
ADD test /absoluteDir/
2.2.8 COPY
类似ADD,但是是不会自动解压文件,也不能访问网络资源。
2.2.8.1 格式
# <dest> 是绝对路径,或相对于 WORKDIR 的路径
COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
# 这种形式对于包含空格的路径是必需的
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
注意:
--chown
和--chmod
功能仅在用于生成 Linux 容器的Dockerfile上受支持,在 Windows 容器上不起作用。
2.2.8.2 举例
# 要添加所有以“hom”开头的文件:
COPY hom* /mydir/
# ?替换为任何单个字符,例如“home.txt”
COPY hom?.txt /mydir/
# 下面的示例使用相对路径,并将“test.txt”添加到 <WORKDIR>/relativeDir/
COPY test.txt relativeDir/
# 此示例使用绝对路径,并将“test.txt”添加到 /absoluteDir/
COPY test.txt /absoluteDir/
2.2.9 VOLUME
指定持久化目录(指定此目录可以被挂载出去)。数据卷的使用,类似于 Linux 下对目录或文件进行mount
。
2.2.9.1 格式
VOLUME ["/path/to/to"]
2.2.9.2 举例
VOLUME ["/data"]
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache/user"]
注:一个卷可以存在于一个或多个容器的指定目录,该目录可以绕过联合文件系统,并具有以下功能:
- 卷可以
容器间
共享和重用 - 容器并不一定要和其它容器共享卷
- 修改卷后会立即生效
- 对卷的修改不会对
镜像
产生影响 - 卷会一直存在,直到没有任何容器在使用它
2.2.10 WORKDIR
工作目录,类似于cd命令
2.2.10.1 格式
WORKDIR /path/to/workdir
2.2.10.2 举例
# 这时工作目录为/a
WORKDIR /a
# 这时工作目录为/a/b
WORKDIR b
# 这时工作目录为/a/b/c
WORKDIR c
也就是说,如果有多个WORKDIR
,那最终的路径就是从上到下,如:/a/b/c,类似这样。
注:
- 通过WORKDIR设置工作目录后,Dockerfile中其后的命令
RUN
、CMD
、ENTRYPOINT
、ADD
、COPY
等命令都会在该目录下执行。- 在使用docker run运行容器时,可以通过
-w
参数覆盖构建时所设置的工作目录。
2.2.11 ENTRYPOINT
配置容器,使其可执行化。
2.2.11.1 格式
# 可执行文件, 优先
ENTRYPOINT ["executable", "param1", "param2"]
# shell内部命令
ENTRYPOINT command param1 param2
2.2.11.2 举例
FROM centos:7
ENTRYPOINT ["ls", "/usr/local"]
CMD ["/usr/local/tomcat"]
之后,docker run
传递的参数,都会先覆盖cmd
,然后由cmd传递给entrypoint
,做到灵活应用。
注:
ENTRYPOINT
与CMD
非常类似,不同的是通过docker run
执行的命令不会覆盖ENTRYPOINT,而docker run命令中指定的任何参数,都会被当做参数再次传递给CMD
。Dockerfile中只允许有一个ENTRYPOINT命令,多个指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
通常情况下,ENTRYPOINT
与CMD
一起使用,ENTRYPOINT
写默认命令,当需要参数时候使用CMD
传参。
3 实践
3.1 dockerfile编写
以我自己开发的聊天软件中的一个项目为例,实质上为一个Spring Cloud Gateway网关。以下为其dockerfile:
# 拉取JDK
FROM openjdk:8-jdk-alpine
# 挂在/tmp卷下
VOLUME /tmp
# 将taeyeonim-gateway.jar添加进容器
ADD taeyeonim-gateway.jar taeyeonim-gateway.jar
# java -jar启动
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/taeyeonim-gateway.jar"]
这里ADD的时候,很容易出错,因为一般dockerfile在项目中不会与源文件,如*.jar放在一起,所以容易出现Dockerfile ADD failed : No Source files were specified
,比如下面这种错误:
Deploying '<unknown> Dockerfile: taeyeonim-gateway/src/main/docker/dockerfile'…
Building image…
Preparing build context archive…
[==================================================>]1/1 files
Done
Sending build context to Docker daemon…
[==================================================>] 288.0B
Done
Step 1/4 : FROM openjdk:8-jdk-alpine
---> a3562aa0b991
Step 2/4 : VOLUME /tmp
---> Using cache
---> 0b0f08654eb9
Step 3/4 : ADD taeyeonim-gateway.jar taeyeonim-gateway.jar
Error response from daemon: ADD failed: file not found in build context or excluded by .dockerignore: stat taeyeonim-gateway.jar: file does not exist
Failed to deploy '<unknown> Dockerfile: taeyeonim-gateway/src/main/docker/dockerfile': Can't retrieve image ID from build stream
即使配置全路径也不能解决。为啥呢?
原因呢很简单,ADD 命令用来从context上下文
复制新文件、目录或远程文件url,并将它们添加到位于指定路径的映像文件系统中。所以如果dockerfile和源文件在同一个目录下就不会出现上述错误。
在IDEA中,对于两文件分别在不同目录的情况,可以通过指定context folder
,使其指向target解决此问题。如下图所示(IDEA版本:IntelliJ IDEA 2021.3 (Ultimate Edition)):
3.2 构建镜像
上图中,我们可以看到已经配置了镜像的Tag,则构建镜像的命令行形式如下:
docker build -f dockerfile -t im-gateway:v2 .
-t
:用于指定镜像的名称为im-gateway:v2;.
:表示使用当前目录下的Dockerfile进行构建。
名称可以以name:version
的形式指定,name
和version
之间用冒号
分隔。如果没有指定版本,则默认为latest
。
构建日志如下所示:
Deploying 'im-gateway:v2 Dockerfile: taeyeonim-gateway/src/main/docker/dockerfile'…
Building image…
Preparing build context archive…
[==================================================>]33/33 files
Done
Sending build context to Docker daemon…
[==================================================>] 82.20MB
Done
Step 1/4 : FROM openjdk:8-jdk-alpine
---> a3562aa0b991
Step 2/4 : VOLUME /tmp
---> Using cache
---> 3c8fa2e5827c
Step 3/4 : ADD taeyeonim-gateway.jar taeyeonim-gateway.jar
---> Using cache
---> cb2f85399674
Step 4/4 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/taeyeonim-gateway.jar"]
---> Using cache
---> 655e3800a65c
Successfully built 655e3800a65c
Successfully tagged im-gateway:v2
'im-gateway:v2 Dockerfile: taeyeonim-gateway/src/main/docker/dockerfile' has been deployed successfully.
通过docker客户端或者IDEA自带的工具都可以看到构建镜像成功后的镜像列表。
IDEA镜像列表:
3.3 创建容器
两种方式创建容器,一种是通过docker客户端进行创建,一种是通过IDEA创建。
3.3.1 docker client方式
找到image列表中之前构建的Gateway镜像,点击Run
按钮,弹出菜单。
填写好容器名称,这里我命名为Im-Gw
。
点击Run
,开始启动容器,成功后可以在容器列表看到。
3.3.2 IDEA方式
找到IDEA services窗口,查看image列表,找到之前构建的镜像im-gateway
,右键弹出菜单,选中create container
。
填写容器名称,这里为IM-Gateway
. 点击run
运行容器。
同样可以在docker客户端看到启动成功后的docker,如3.3.1节中的容器列表图,也可以在IDEA中查看,只是没有那么好看。哈哈哈哈