通过之前章节可以得知,如果要下载一个有其他依赖的镜像,可以在原有镜像容器的基础上手动下载所需的依赖,然后使用 commit 方式打包为一个新镜像。但是这种方式过于繁琐,如果后续需要不断添加依赖,就需要重复 commit ,DockerFile 应运而生
DockerFile 是用于构建 Docker 镜像的文本文件,构建步骤为:编写 Dockerfile 文件 > docker build命令构建镜像 > docker run 运行容器实例。具体说明可参考 官方文档
image.png

DockerFile 执行流程:

  1. 1. docker从基础镜像运行一个容器
  2. 2. 执行一条指令并对容器作出修改
  3. 3. 执行类似docker commit的操作提交一个新的镜像层
  4. 4. docker再基于刚提交的镜像运行一个新容器
  5. 5. 执行dockerfile中的下一条指令直到所有指令都执行完成
  6. 6. 打包成一个新镜像

image_waifu2x_2x_3n_png.png


DockerFile 保留字:

image.png

FROM:

基础镜像来源,说明当前新镜像是基于哪个镜像的进行构建,必须指定一个已经存在的镜像作为模板,且 dockerFile 第一条保留字必须是 from

MAINTAINER:

当前 dockerFile 构建生成的镜像的维护者姓名和邮箱地址

RUN:

容器构建时需要运行的命令,分为 shell 与 exec 两种格式,该保留字在 docker build 时运行
每条RUN命令都会在原有镜像的基础上创建出一个新镜像进行覆盖,因此在编写 Dockefile 时,应该将多条 RUN 命令合并为一个,以此来减少重复生成镜像所花费的时间

  1. shell格式:
  2. 执行语句: RUN 命令行
  3. 例如: RUN yum -y install vim
  4. exec 格式:
  5. 执行语句: RUN ["可执行文件","参数1","参数2"...]
  6. 例如: RUN ["/test.php","dev","offline"] 等价于 RUN ./test.php dev offline

EXPOSE:

当前容器对外暴露出的端口,相当于 docker run-p 的容器端口参数,实际意义是让用户知道该镜像在 docker run 使用-p 参数时需要指定监听的容器端口号是多少

WORKDIR:

指定在创建容器后,终端默认登陆的进来工作目录,如果目录不存在将会自动创建

USER:

指定该镜像只能用什么样的用户去执行,如果不指定默认是root,通常自行编写 dockerFile 时不会使用该参数

ENV:

用来在构建镜像过程中设置环境变量,相当于 Java 中的变量

  1. 写法一:
  2. ENV [变量名]=[变量值]
  3. 写法二:
  4. ENV [变量名] [变量值]

ARG:

变量参数,在构建镜像完成后生效,因此该参数指定的变量值不会存在于最终生成的镜像中
可以在 docker build 时通过 —build-arg 变量名=XXX 改变其原有变量值

  1. 写法一:
  2. ENV [变量名]=[变量值]
  3. 写法二:
  4. ENV [变量名] [变量值]

ENV 与 ARG 的作用范围示意图

VOLUME:

指定需要挂载到宿主机的容器内路径,相当于 docker run -v 参数的容器路径,只不过宿主机的目录是随机的
由于宿主机目录是随机的,因此不能使用该参数做容器数据卷,而是在创建容器时使用 -v 来创建容器数据卷

ADD:

将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包,相当于 COPY + 解压,如果解压目录不存在将会自动创建
ADD 后面的文件必须和 DockerFile 文件在同一路径下
常用操作命令如下所示:

  1. ADD [文件] [解压路径1] [解压路径2] ...

COPY:

拷贝宿主机的文件和目录到镜像中,类似于反过来的 docker cp,如果路径不存在将会自动创建
复制到容器的文件,其读写权限与当时的宿主机源文件一致

  1. COPY [宿主机文件] [容器路径]

CMD:

指定容器启动后的要干的事情,一个 Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效( echo 这类例外 )
CMD 会被 docker run 之后的参数替换,该保留字在 docker run 时执行,案例参考 ENTRYPOINT 演示
RUN 相同,语法也是两种格式

  1. shell格式:
  2. 执行语句: CMD [命令行]
  3. 例如: CMD ["catalina.sh", "run"]
  4. exec 格式:
  5. 通常该格式是作为 ENTRYPOINT 的参数进行使用,可执行文件代表 SHELL 中的可执行文件
  6. 执行语句: CMD ["可执行文件","参数1","参数2"...]
  7. 详情参考 ENTRYPOINT 保留字说明

Tomcat 的参数替换演示:

image.png
image.png

shell 与 exec 写法案例:

exec 格式需要使用可执行文件才能执行后面的参数,这里以输出语句为例进行演示

  1. shell格式写法:
  2. FROM ubuntu:20.04
  3. ENV NAME docker
  4. CMD echo "hello $NAME"
  5. exec格式写法:
  6. FROM ubuntu:20.04
  7. ENV NAME docker
  8. CMD ["sh", "-c", "echo hello $NAME"]

ENTRYPOINT:

用来指定一个容器启动时要运行的命令
类似于 CMD 指令,但是不会被 docker run 后面的命令覆盖, 而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序
可以和 CMD 一起用,组合在一起时 CMD 将会作为变参,此时 CMD 等于是在给 ENTRYPOINT 传参。相当于
ENTRYPOINT 命令格式如下:

  1. ENTRYPOINT ["<executeable>","<param1>","<param2>"...]

与 CMD 一同使用案例说明:
image.png

当不传递参数执行时,将按照 dockerFile 中的命令创建容器;当传递参数执行时,传递的参数将会替换 CMD 中的变参

是否传参 按照dockerfile编写执行 传参运行
Docker命令 docker run nginx docker run nginx -c /etc/nginx/new.conf
衍生出的实际命令 nginx -c /etc/nginx/nginx.conf nginx -c /etc/nginx/new.conf

DockerFile 的使用:

DockerFile 文件名称通常为 Dockerfile,且一般没有后缀名
运行 Dockerfile 构建镜像时需要在 Dockerfile 文件所在路径中执行 docker build 操作,操作命令中版本号后面与 “.” 之间有空格,具体命令如下:

  1. docker build -t 新镜像名字:版本号 .

如果名称不为 Dockerfile,需要使用 -f 指定 Dockerfile 文件名

  1. docker build -f Dockerfile文件名 -t 新镜像名字:版本号 .

实际工作中会使用 Alpine 这类体积较小的 Linux 镜像作为 Dockerfile 文件的基础镜像,使用案例参考 docke网络章节


案例演示:

创建一个具有 vim、ifconfig、jdk8 环境的 centos 镜像
image.png

编写 Dockerfile 文件:

  1. FROM centos:7.9.2009
  2. MAINTAINER dmbjz<dmbjzorg@gmail.com>
  3. ENV MYPATH /usr/local
  4. WORKDIR $MYPATH
  5. #安装vim编辑器
  6. RUN yum -y install vim
  7. #安装ifconfig命令查看网络IP
  8. RUN yum -y install net-tools
  9. #安装java8及lib库
  10. RUN yum -y install glibc.i686
  11. RUN mkdir /usr/local/java
  12. #ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
  13. ADD jdk-8u341-linux-x64.tar.gz /usr/local/java/
  14. #配置java环境变量
  15. ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
  16. ENV JRE_HOME $JAVA_HOME/jre
  17. ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  18. ENV PATH $JAVA_HOME/bin:$PATH
  19. EXPOSE 80
  20. CMD echo $MYPATH
  21. CMD echo "success--------------ok"
  22. CMD /bin/bash

image.png
image.png
image.png


Dockerfile 优化:

RUN 命令优化:

由于每条RUN命令都会在原有镜像的基础上创建出一个新镜像进行覆盖,因此当 RUN 操作较多时重复创建镜像将非常耗时,此时应该使用 && 将命令进行合并
对刚才演示的 Dockerfile 文件进行重新编写,优化后的内容如下:

  1. FROM centos:7.9.2009
  2. MAINTAINER dmbjz<dmbjzorg@gmail.com>
  3. ENV MYPATH /usr/local
  4. WORKDIR $MYPATH
  5. #安装vim编辑器
  6. RUN yum -y install vim && \
  7. yum -y install net-tools && \
  8. yum -y install glibc.i686 && \
  9. mkdir /usr/local/java
  10. #ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
  11. ADD jdk-8u341-linux-x64.tar.gz /usr/local/java/
  12. #配置java环境变量
  13. ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
  14. ENV JRE_HOME $JAVA_HOME/jre
  15. ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
  16. ENV PATH $JAVA_HOME/bin:$PATH
  17. EXPOSE 80
  18. CMD echo $MYPATH
  19. CMD echo "success--------------ok"
  20. CMD /bin/bash

复用缓存:

当使用同一个 Dockerfile 生成镜像时,会使用缓存来加快构建,但是当其中一行操作发生改变,后面的操作将不会再使用缓存
因此当需要对一个复用的 Dockerfile 文件的外部引用文件进行修改时,该外部文件的操作尽量放在末尾

案例演示:
先使用原版 Dockerfile 构建镜像:

  1. FROM python:3.9.5-slim
  2. COPY 1.txt /src/1.txt
  3. RUN pip install flask
  4. WORKDIR /src
  5. ENV FLASK success
  6. EXPOSE 5000
  7. CMD echo $FLASK

image.png

修改需要 COPY 的 1.txt 内容,重新构建镜像,COPY 之后的操作不使用缓存:
image.png

编辑 Dockerfile,将修改操作放到后面。然后创建镜像,紧接着修改1.txt文件,最后再次创建镜像,前面的步骤使用缓存:

  1. FROM python:3.9.5-slim
  2. RUN pip install flask
  3. WORKDIR /src
  4. ENV FLASK success
  5. EXPOSE 5000
  6. COPY 1.txt /src/1.txt
  7. CMD echo $FLASK

image.png

忽略文件:

在使用 docker build 时,会将当前 Dockerfile 文件所在目录的所有文件打包到 deamon 中,如果文件较多将会影响镜像生成速度,因此在实际工作中需要将无关文件在打包环节进行忽略
使用 .dockerignore 文件指定需要忽略打包到 deamon 中的文件,格式与 git 的 .gitignore 类似
image.png

编写 .dockerignore 文件,忽略掉 jdk压缩包、cig文件夹等其他文件:

  1. *.gz
  2. /cig
  3. *.yml

重新构建镜像,deamon 体积被大幅度精简:
image.png

多阶段编译:

降低构建复杂度,同时使缩小镜像尺寸
案例演示:
演示用镜像去运行一个C语言文件,C语言文件如下:

  1. #include <stdio.h>
  2. void main(int argc, char *argv[])
  3. {
  4. printf("hello %s\n", argv[argc - 1]);
  5. }

编写 Dockerfile,使用 gcc 编译C文件

  1. FROM gcc:9.4
  2. COPY hello.c /src/hello.c
  3. WORKDIR /src
  4. RUN gcc --static -o hello hello.c
  5. ENTRYPOINT [ "/src/hello" ]
  6. CMD []

image.png
image.png
实际上把 hello.c 编译完以后,并不需要 GCC 环境,只需要一个小的alpine镜像就可以了
修改原有 Dockerfile 为多阶段格式:

  1. FROM gcc:9.4 AS builder
  2. COPY hello.c /src/hello.c
  3. WORKDIR /src
  4. RUN gcc --static -o hello hello.c
  5. FROM alpine:3.13.5
  6. COPY --from=builder /src/hello /src/hello
  7. ENTRYPOINT [ "/src/hello" ]
  8. CMD []

image.png

使用非 root 用户:

如果不指定用户,Dockerfile 创建的镜像生成的容器默认会使用 root 用户进行访问。在某些时候,容器中的进程有机会访问到宿主机只有root用户才能访问的资源(例如数据卷挂载方式访问),对宿主机来说具有一定风险( 演示案例 )。因此在编写 Dockerfile 时,需要限制打开终端交互时为非 root 用户
在宿主机中通过 groupadd useradd 创建用户组和用户,然后在 Dockerfile 中通过 User 指定用户即可:

  1. USER 创建的用户

案例演示:
新建用户组 commonUser > 创建用户 axnothing 并指定用户组 > 设置密码 > 查看用户ID

  1. groupadd commonUser
  2. useradd -g commonUser axnothing
  3. passwd axnothing
  4. id -u

编写使用 axnothing 用户的 Dockerfile:

  1. FROM centos:7.9.2009
  2. USER 用户ID
  3. WORKDIR /src
  4. CMD echo "hello user"

image.png


虚悬镜像:

仓库名、标签都是的镜像,俗称 dangling image
image.png

查看当前所有虚悬镜像:

  1. docker image ls -f dangling=true

image.png

删除所有虚悬镜像:

  1. docker image prune

image.png