传送门
docker系列1:docker安装
docker系列2:阿里云镜像加速器
docker系列3:docker镜像基本命令
docker系列4:docker容器基本命令
docker系列5:docker安装nginx
docker系列6:docker安装redis
docker系列7:docker安装ES
docker系列8:容器卷挂载(上)
docker系列9:容器卷挂载(下)
docker系列10:Dockerfile挂载容器卷
docker系列11:Dockerfile入门
Dockerfile文件回顾
在前面一节讨论过Dockerfile的定义,从官网的定义来看看:
Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.
Docker可以通过读取Dockerfile中的指令自动构建镜像。Dockerfile是一个文本文档,其中包含用户可以在命令行上调用的所有命令来组装镜像。
从上面的流程可以看出,大致为以下几个步骤:
- 编写Dockerfile文件,里面通过指令定义各种步骤
- 通过Docker读取Dockerfile文件,构建镜像
- 镜像里面进行分层,每一条指令按顺序都会生成对应的一个层
Docker这种镜像分层的技术,使得把重复的部分抽取出来形成公共层达到复用的目的。这样的好处显而易见:
- 相同的层可以在镜像之间共享,不用重复编写,效率得到了极大的提高,尤其是一些基础镜像
This is beneficial because it allows layers to be reused between images. For example, imagine you wanted to create another Python application. Due to layering, you can leverage the same Python base. This will make builds faster and reduce the amount of storage and bandwidth required to distribute the images.
这是有益的,因为它允许在镜像之间重用图层。例如,假设您想创建另一个Python应用程序。由于分层,您可以利用相同的Python基础。这将使构建更快,并减少分发镜像所需的存储量和带宽
- 由于上个原因,可以对镜像做缓存,性能也得到提升
而这种镜像分层技术,再加上一种叫"union filesystem 联合文件系统"的技术把它们合并在一起,就形成了容器最终看到的文件系统,而它的底层实现依赖于OverlayFS
打包自己的SpringBoot微服务镜像
前面讨论了这么多关于Dockerfile的定义、指令,也通过自定义Dockerfile来了演示Dockerfile文件的编写、构建及运行,但最终的目的还是要通过Docker来运行我们自己的应用,典型就是通过SpringBoot开发微服务。以原来的auth服务为例!
编写Dockerfile文件
先将微服务打包个包出来:
然后上传到服务器,以前面的test-docker目录为例:
创建Dockerfile文件,命名为Dockerfile_auth:
# 选择jdk8做为基础镜像
FROM java:8
# 将本机微服务jar包复制到镜像中
ADD auth-0.0.1-SNAPSHOT.jar /usr/local/bin
# 设置环境变量:jar包的路径
ENV AUTH_PATH=/usr/local/bin/auth-0.0.1-SNAPSHOT.jar
# for test
RUN echo ${AUTH_PATH}
# 容器启动时,执行微服务启动命令:java -jar auth-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java","-jar", "/usr/local/bin/auth-0.0.1-SNAPSHOT.jar"]
# 暴露端口8083
EXPOSE 8083
运行镜像
运行上面的Dockerfile文件,生成镜像:
运行镜像auth:
然后进入容器:
- 通过docker启动镜像:docker run -d --name auth1 auth -p 8083:8083
- 进入容器:docker exec -it 4eca1df4d1a6 /bin/bash
执行curl请求之后,服务端进行了响应,表示服务运行成功了,通过docker运行的SpringBoot微服务成功!
Dockerfile指令详解
FROM基础镜像的选择
前面在讨论FROM指令的时候说过:有效的Dockerfile必须以FROM指令开头,必须要选择一个基础镜像!那对于java应用来说,jdk肯定是刚需(比买房还刚)了。在dockerhub上搜索结果如下:
官方镜像中,只有这一个选项那就是叫java的镜像了!所以只要指定对应的版本即可,在上面的Dockerfile中选择了java8! 这个也可以其它开源项目,比如前面讨论的xxl-job源码 里面编写的Dockerfile来对比:
在第一行中通过FROM指令指定了jdk的版本:openjdk:8-jre-slim
ADD与COPY的区别
在前面讨论过COPY指令,其实ADD指令跟COPY是类似的,命令格式也差不多,区别在于:
ADD
andCOPY
are functionally similar.COPY
supports basic copying of files into the container, from the build context or from a stage in a multi-stage build.ADD
supports features for fetching files from remote HTTPS and Git URLs, and extracting tar files automatically when adding files from the build context.ADD和COPY功能相似。COPY支持将文件从构建上下文或多阶段构建中的阶段基本复制到容器中。ADD支持从远程安全超文本传输协议和Git URL获取文件,以及从构建上下文添加文件时自动提取tar文件的功能。
简而言之:
- ADD支持从远程获取资源,COPY不行
- ADD会自动解压tar包,COPY不会
- 更多的可以查看ADD or COPY
可以通过一个简单的例子来验证一下上面的tar包自动解压功能:
分别创建copy、add的Dockerfile文件:Dockerfile_copy、Dockerfile_add:
FROM java:8
COPY a.txt b.tar /usr/local/test_copy/
WORKDIR /usr/local/test_copy
FROM java:8
ADD a.txt b.tar /usr/local/test_add/
WORKDIR /usr/local/test_add
再创建a.txt、b.tar文件:
分别构建Dockerfile_copy、Dockerfile_add:
然后分别运行对应的镜像,并进入对应的目录查看文件:
- docker run -d --name dc5 dc ping 8.8.8.8 /bin/bash
- docker exec -it bf6ea6082e51 /bin/bash
会发现通过ADD的文件已经自动解压了,而COPY的里面还是tar包
队此以外,COPY还有不少用法:
- 比如上面的例子中,同时拷贝多个源文件:COPY file1.txt file2.txt /usr/src/things/
- 支持正则表达式:COPY *.png /dest/,拷贝所有png结尾的文件;COPY index.?s /dest/,?是单字符通配符,匹配例如index. js和index.ts
- 排除指定文件:COPY --exclude=*.txt hom* /mydir/,添加所有以“hom”开头的文件,不包括扩展名为. txt的文件
- 指定文件的权限:COPY [--chown=<user>:<group>] [--chmod=<perms> ...] <src> ... <dest>:COPY --chown=55:mygroup files* /somedir/
COPY --chown=bin files* /somedir/
COPY --chown=1 files* /somedir/
COPY --chown=10:11 files* /somedir/
COPY --chown=myuser:mygroup --chmod=644 files* /somedir/
ENTRYPOINT
容器启动后,要启动微服务。一般执行微服务启动命令:java -jar auth-0.0.1-SNAPSHOT.jar
在上面的例子Dockerfile和XXL-job的Docker镜像Dockerfile中,发现启动命令都是用的ENTRYPOINT指令:
ENTRYPOINT ["executable", "param1", "param2"]
比如上面的指令:ENTRYPOINT ["java","-jar", "/usr/local/bin/auth-0.0.1-SNAPSHOT.jar"]
两种可能的执行形式
exec
这种形式称之为exec形式!比如前面的卷挂载的Dockerfile中:CMD ["ls", "-al"],也是exec形式。关于exec形式的说明在官网上有如下解释:
The exec form makes it possible to avoid shell string munging, and to invoke commands using a specific command shell, or any other executable. It uses a JSON array syntax, where each element in the array is a command, flag, or argument.
exec形式可以避免shell字符串修改,并使用特定的命令shell或任何其他可执行文件来调起命令。它使用JSON数组语法,其中数组中的每个元素都是命令、标志或参数。
而且官网上也推荐这种写法:
The exec form is best used to specify an
ENTRYPOINT
instruction, combined withCMD
for setting default arguments that can be overridden at runtime.exec形式最好用于指定ENTRYPOINT指令,结合CMD设置可以在运行时覆盖的默认参数。
在这种形式下,对于shell变量是不会自动替换的:比如RUN [ "echo", "$HOME" ]指令,运行之后并不自动替换$HOME.这个原因在于:
Using the exec form doesn't automatically invoke a command shell.
使用exec表单不会自动调用命令shell。
If you want shell processing then either use the shell form or execute a shell directly with the exec form, for example:
RUN [ "sh", "-c", "echo $HOME" ]
如果要使用shell生效,可以如下执行:RUN [ "sh", "-c", "echo $HOME" ]
shell
而另一种执行形式,就是所谓的shell形式:
RUN source $HOME/.bashrc && echo $HOME
这种形式就是在shell环境中执行的:
Unlike the exec form, instructions using the shell form always use a command shell.
与exec形式不同,使用shell形式的指令始终使用命令shell。
比如将上面的Dockerfile修改一下,最后的应用启动命令从exec:
- ENTRYPOINT ["java","-jar", "/usr/local/bin/auth-0.0.1-SNAPSHOT.jar"]
改为shell形式来执行:
- ENTRYPOINT java -jar ${AUTH_PATH}
即修改为如下的Dockerfile文件:
# 选择jdk8做为基础镜像
FROM java:8
# 将本机微服务jar包复制到镜像中
ADD auth-0.0.1-SNAPSHOT.jar /usr/local/bin
# 设置环境变量:jar包的路径
ENV AUTH_PATH=/usr/local/bin/auth-0.0.1-SNAPSHOT.jar
# for test
RUN echo ${AUTH_PATH}
# 容器启动时,执行微服务启动命令:java -jar auth-0.0.1-SNAPSHOT.jar
ENTRYPOINT java -jar ${AUTH_PATH}
# 暴露端口8083
EXPOSE 8083
运行上面的Dockerfile文件,生成镜像,然后运行,效果同上!这个时候通过docker ps 来查看运行的容器,会发现一个现象:
从这里可以看出来,通过shell形式执行的命令,会默认在["/bin/bash", "-c"]下执行的(这里可以通过--no-trunc参数查看完整的启动命令):
而["/bin/sh", "-c"] 则是默认的shell,如果要更改,docker也提供了对应的SHELL指令:
SHELL ["/bin/bash", "-c"]
RUN echo hello
具体可以看Use a different shell
关于CMD与ENTRYPOIT的区别
CMD与ENTRYPOIT的区别,网上也有很多解释:其中比较多的说法是在Docker容器中,CMD和ENTRYPOINT是两种不同的指令,用于定义容器启动时要执行的命令。CMD适用于设置默认命令和参数,而ENTRYPOINT适用于定义容器的主要入口点。其中CMD指令会被Docker run启动命令中参数被覆盖,而ENTRYPOINT会把Docker run启动命令参数进行追加!
在官网中也对两者的区别做了说明:
Both
CMD
andENTRYPOINT
instructions define what command gets executed when running a container. There are few rules that describe their co-operation.
Dockerfile should specify at least one of
CMD
orENTRYPOINT
commands.
ENTRYPOINT
should be defined when using the container as an executable.
CMD
should be used as a way of defining default arguments for anENTRYPOINT
command or for executing an ad-hoc command in a container.
CMD
will be overridden when running the container with alternative arguments.
大致意思就是:
- Dockerfile应至少指定一个CMD或ENTRYPOINT命令。
- 将容器用作可执行文件时应定义ENTRYPOINT。
- CMD应该用作定义ENTRYPOINT命令或在容器中执行ad-hoc命令的默认参数的一种方式。
- 使用替代参数运行容器时,CMD将被覆盖。