通过之前章节可以得知,如果要下载一个有其他依赖的镜像,可以在原有镜像容器的基础上手动下载所需的依赖,然后使用 commit 方式打包为一个新镜像。但是这种方式过于繁琐,如果后续需要不断添加依赖,就需要重复 commit ,DockerFile 应运而生
DockerFile 是用于构建 Docker 镜像的文本文件,构建步骤为:编写 Dockerfile 文件 > docker build命令构建镜像 > docker run 运行容器实例。具体说明可参考 官方文档
DockerFile 执行流程:
1. docker从基础镜像运行一个容器
2. 执行一条指令并对容器作出修改
3. 执行类似docker commit的操作提交一个新的镜像层
4. docker再基于刚提交的镜像运行一个新容器
5. 执行dockerfile中的下一条指令直到所有指令都执行完成
6. 打包成一个新镜像
DockerFile 保留字:
FROM:
基础镜像来源,说明当前新镜像是基于哪个镜像的进行构建,必须指定一个已经存在的镜像作为模板,且 dockerFile 第一条保留字必须是 from
MAINTAINER:
当前 dockerFile 构建生成的镜像的维护者姓名和邮箱地址
RUN:
容器构建时需要运行的命令,分为 shell 与 exec 两种格式,该保留字在 docker build 时运行
每条RUN命令都会在原有镜像的基础上创建出一个新镜像进行覆盖,因此在编写 Dockefile 时,应该将多条 RUN 命令合并为一个,以此来减少重复生成镜像所花费的时间
shell格式:
执行语句: RUN 命令行
例如: RUN yum -y install vim
exec 格式:
执行语句: RUN ["可执行文件","参数1","参数2"...]
例如: RUN ["/test.php","dev","offline"] 等价于 RUN ./test.php dev offline
EXPOSE:
当前容器对外暴露出的端口,相当于 docker run 的 -p 的容器端口参数,实际意义是让用户知道该镜像在 docker run 使用-p 参数时需要指定监听的容器端口号是多少
WORKDIR:
指定在创建容器后,终端默认登陆的进来工作目录,如果目录不存在将会自动创建
USER:
指定该镜像只能用什么样的用户去执行,如果不指定默认是root,通常自行编写 dockerFile 时不会使用该参数
ENV:
用来在构建镜像过程中设置环境变量,相当于 Java 中的变量
写法一:
ENV [变量名]=[变量值]
写法二:
ENV [变量名] [变量值]
ARG:
变量参数,在构建镜像完成后生效,因此该参数指定的变量值不会存在于最终生成的镜像中
可以在 docker build 时通过 —build-arg 变量名=XXX 改变其原有变量值
写法一:
ENV [变量名]=[变量值]
写法二:
ENV [变量名] [变量值]
VOLUME:
指定需要挂载到宿主机的容器内路径,相当于 docker run 的 -v 参数的容器路径,只不过宿主机的目录是随机的
由于宿主机目录是随机的,因此不能使用该参数做容器数据卷,而是在创建容器时使用 -v 来创建容器数据卷
ADD:
将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包,相当于 COPY + 解压,如果解压目录不存在将会自动创建
ADD 后面的文件必须和 DockerFile 文件在同一路径下
常用操作命令如下所示:
ADD [文件] [解压路径1] [解压路径2] ...
COPY:
拷贝宿主机的文件和目录到镜像中,类似于反过来的 docker cp,如果路径不存在将会自动创建
复制到容器的文件,其读写权限与当时的宿主机源文件一致
COPY [宿主机文件] [容器路径]
CMD:
指定容器启动后的要干的事情,一个 Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效( echo 这类例外 )
CMD 会被 docker run 之后的参数替换,该保留字在 docker run 时执行,案例参考 ENTRYPOINT 演示
与 RUN 相同,语法也是两种格式
shell格式:
执行语句: CMD [命令行]
例如: CMD ["catalina.sh", "run"]
exec 格式:
通常该格式是作为 ENTRYPOINT 的参数进行使用,可执行文件代表 SHELL 中的可执行文件
执行语句: CMD ["可执行文件","参数1","参数2"...]
详情参考 ENTRYPOINT 保留字说明
Tomcat 的参数替换演示:
shell 与 exec 写法案例:
exec 格式需要使用可执行文件才能执行后面的参数,这里以输出语句为例进行演示
shell格式写法:
FROM ubuntu:20.04
ENV NAME docker
CMD echo "hello $NAME"
exec格式写法:
FROM ubuntu:20.04
ENV NAME docker
CMD ["sh", "-c", "echo hello $NAME"]
ENTRYPOINT:
用来指定一个容器启动时要运行的命令
类似于 CMD 指令,但是不会被 docker run 后面的命令覆盖, 而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序
可以和 CMD 一起用,组合在一起时 CMD 将会作为变参,此时 CMD 等于是在给 ENTRYPOINT 传参。相当于
ENTRYPOINT 命令格式如下:
ENTRYPOINT ["<executeable>","<param1>","<param2>"...]
与 CMD 一同使用案例说明:
当不传递参数执行时,将按照 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 操作,操作命令中版本号后面与 “.” 之间有空格,具体命令如下:
docker build -t 新镜像名字:版本号 .
如果名称不为 Dockerfile,需要使用 -f 指定 Dockerfile 文件名:
docker build -f Dockerfile文件名 -t 新镜像名字:版本号 .
实际工作中会使用 Alpine 这类体积较小的 Linux 镜像作为 Dockerfile 文件的基础镜像,使用案例参考 docke网络章节
案例演示:
创建一个具有 vim、ifconfig、jdk8 环境的 centos 镜像
编写 Dockerfile 文件:
FROM centos:7.9.2009
MAINTAINER dmbjz<dmbjzorg@gmail.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u341-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
Dockerfile 优化:
RUN 命令优化:
由于每条RUN命令都会在原有镜像的基础上创建出一个新镜像进行覆盖,因此当 RUN 操作较多时重复创建镜像将非常耗时,此时应该使用 && 将命令进行合并
对刚才演示的 Dockerfile 文件进行重新编写,优化后的内容如下:
FROM centos:7.9.2009
MAINTAINER dmbjz<dmbjzorg@gmail.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
#安装vim编辑器
RUN yum -y install vim && \
yum -y install net-tools && \
yum -y install glibc.i686 && \
mkdir /usr/local/java
#ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u341-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
复用缓存:
当使用同一个 Dockerfile 生成镜像时,会使用缓存来加快构建,但是当其中一行操作发生改变,后面的操作将不会再使用缓存
因此当需要对一个复用的 Dockerfile 文件的外部引用文件进行修改时,该外部文件的操作尽量放在末尾
案例演示:
先使用原版 Dockerfile 构建镜像:
FROM python:3.9.5-slim
COPY 1.txt /src/1.txt
RUN pip install flask
WORKDIR /src
ENV FLASK success
EXPOSE 5000
CMD echo $FLASK
修改需要 COPY 的 1.txt 内容,重新构建镜像,COPY 之后的操作不使用缓存:
编辑 Dockerfile,将修改操作放到后面。然后创建镜像,紧接着修改1.txt文件,最后再次创建镜像,前面的步骤使用缓存:
FROM python:3.9.5-slim
RUN pip install flask
WORKDIR /src
ENV FLASK success
EXPOSE 5000
COPY 1.txt /src/1.txt
CMD echo $FLASK
忽略文件:
在使用 docker build 时,会将当前 Dockerfile 文件所在目录的所有文件打包到 deamon 中,如果文件较多将会影响镜像生成速度,因此在实际工作中需要将无关文件在打包环节进行忽略
使用 .dockerignore 文件指定需要忽略打包到 deamon 中的文件,格式与 git 的 .gitignore 类似
编写 .dockerignore 文件,忽略掉 jdk压缩包、cig文件夹等其他文件:
*.gz
/cig
*.yml
重新构建镜像,deamon 体积被大幅度精简:
多阶段编译:
降低构建复杂度,同时使缩小镜像尺寸
案例演示:
演示用镜像去运行一个C语言文件,C语言文件如下:
#include <stdio.h>
void main(int argc, char *argv[])
{
printf("hello %s\n", argv[argc - 1]);
}
编写 Dockerfile,使用 gcc 编译C文件
FROM gcc:9.4
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
ENTRYPOINT [ "/src/hello" ]
CMD []
实际上把 hello.c 编译完以后,并不需要 GCC 环境,只需要一个小的alpine镜像就可以了
修改原有 Dockerfile 为多阶段格式:
FROM gcc:9.4 AS builder
COPY hello.c /src/hello.c
WORKDIR /src
RUN gcc --static -o hello hello.c
FROM alpine:3.13.5
COPY --from=builder /src/hello /src/hello
ENTRYPOINT [ "/src/hello" ]
CMD []
使用非 root 用户:
如果不指定用户,Dockerfile 创建的镜像生成的容器默认会使用 root 用户进行访问。在某些时候,容器中的进程有机会访问到宿主机只有root用户才能访问的资源(例如数据卷挂载方式访问),对宿主机来说具有一定风险( 演示案例 )。因此在编写 Dockerfile 时,需要限制打开终端交互时为非 root 用户
在宿主机中通过 groupadd 和 useradd 创建用户组和用户,然后在 Dockerfile 中通过 User 指定用户即可:
USER 创建的用户
案例演示:
新建用户组 commonUser > 创建用户 axnothing 并指定用户组 > 设置密码 > 查看用户ID
groupadd commonUser
useradd -g commonUser axnothing
passwd axnothing
id -u
编写使用 axnothing 用户的 Dockerfile:
FROM centos:7.9.2009
USER 用户ID
WORKDIR /src
CMD echo "hello user"
虚悬镜像:
仓库名、标签都是
查看当前所有虚悬镜像:
docker image ls -f dangling=true
删除所有虚悬镜像:
docker image prune