183.png 198.png

一、是什么

DockerFile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。
Dockerfile是独立于docker引擎,存在于外面的一个文件
DockerFile官方说明地址:
https://docs.docker.com/engine/reference/builder/

构建三步骤

1、编写Docker文件
2、docker build命令构建镜像
3、docker run 依照新编写好的docker文件运行容器实例

二、DockerFile的构建过程

1、DockerFile内容基础知识

1、每条保留字指令都必须为大写字母,且后面要跟随至少一个参数
   保留字指令:像下图中官网界面的FROM、RUN、CMD等等
2、指令按照从上到下顺序执行
3、#表示注释
4、每条指令都会创建一个新的镜像层并对镜像进行提交

184.png

2、Docker执行DockerFile大致流程

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

3、小总结

从应用软件的角度来看,DockerFile、Docker镜像、Docker容器分别代表软件的三个不同阶段
1、DockerFile是软件的原材料
2、Docker镜像是软件的交付品
3、Docker容器则可认为软件镜像的运行态,也是依照镜像运行的容器实例
DockerFile面向开发、DockeFile成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。

如果把Docker体系的三个阶段比作看中医:
DockerFile是医生写中药方子
Docker镜像是依照药方抓好的中药
Docker容器是熬好的中药

1、DockerFile:需要定义一个DockerFile,DockerFile定义了进程需要的一切东西。DockerFile涉及的内容包括执行代码、文件、环境变量、依赖包、运行时环境、动态连接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道时,需要考虑如何设计namespace的权限控制)等等。
2、Docker镜像:在用dockerfile定义一个文件之后,docker build时会产生一个docker镜像,当运行docker镜像时会真正开始提供服务
3、Docker容器:容器是直接提供服务的

三、DockerFile常用保留字指令

1、FROM

基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是FROM
基础镜像在适合你自己项目的情况下,最好挑一些apline,slim之类的基础小镜像

如何确定我需要什么要的基础镜像?
1、Java应用当然是java基础镜像(SpringBoot应用)或者Tomcat基础镜像(War应用)
2、JS模块化应用一般用nodejs基础镜像
3、其他各种语言用自己的服务器或者基础环境镜像,如python、golang、java、php等

2、MAINTAINER

镜像维护者的姓名和邮箱地址

3、RUN

容器构建时需要执行的命令
构建时表示:根据Dockerfile创建一个镜像的过程
两种格式:
shell格式:RUN <命令行命令>    
#<命令行命令>等同于在终端操作的shell命令
exec格式:RUN ["可执行文件","参数1","参数2"]  
#RUN ["./test.php","dev","office"] 等价于 RUN ./test.php dev office   
RUN是在docker build时运行

4、EXPOSE

当前容器对外暴露的端口
EXPOSE指令通知Docker容器在运行时在指定的网络端口上进行侦听。 可以指定端口是侦听TCP还
是UDP,如果未指定协议,则默认值为TCP。

EXPOSE指令实际上不会发布端口。 它充当构建映像的人员和运行容器的人员之间的一种文档,即
有关打算发布哪些端口的信息。 要在运行容器时实际发布端口,请在docker run上使用-p标志发布
并映射一个或多个端口,或使用-P标志发布所有公开的端口并将其映射到高阶端口

EXPOSE <port> [<port>/<protocol>...] EXPOSE [80,443] EXPOSE 80/tcp EXPOSE 80/udp

5、WORKDIR

指定在容器创建后,终端默认登陆进来的工作目录,一个落脚点
为下面所有命令的运行指定了基础目录
如果 WORKDIR /app 下面还有一行 WORKDIR /abc ,那么最终的工作目录是 /app/abc
FROM alpine
#给镜像加一个标签
LABEL name=lori age=18
RUN pwd && ls -l
WORKDIR /aac
RUN pwd && ls -l

76.png

6、USER

指定该镜像以什么样的用户去执行,如果不指定,默认是root
FROM alpine
#给镜像加一个标签
LABEL name=lori age=18

#开一个用户以及用户所在的组
#1000为用户的id和组的id
#此后所有的命令都会用此用户来执行,所以需要给执行的命令赋权限
USER 1000:1000

#把复制来的文件给用户所有权
COPY --chown=1000:1000 *.txt /a.txt

#给文件追加文字
RUN echo 66666 >> a.txt

同时在Dockerfile同级目录下创建一个文件a.txt 77.png

7、ADD

将宿主机目录下的文件拷贝进镜像,且会自动处理URL和解压tar压缩包。
把宿主机文件复制到镜像里面,如果是压缩包,自动解压,如果是远程文件,自动下载

8、COPY

类似ADD,拷贝文件和目录到镜像中
不可以从远程下载

9、VOLUME

容器数据卷,用于数据保存和持久化工作
FROM alpine
#给镜像加一个标签
LABEL name=lori age=18

#挂载容器内的指定文件夹,不存在则在容器内创建
#指定了VOLUME ,即使启动时容器没有指定 -v,也会进行自动的匿名卷挂载
#hello文件夹和app文件夹是指容器内的这两个文件夹挂载到宿主机上
VOLUME [ "/hello" ,"/app"]

78.png docker inspect 容器id 79.png

  • 我们知道 docker run -v这个命令,就是将宿主机的目录和容器的目录绑定,以便容器中的数据可以保存到宿主机上,删除容器时,数据还在,还有一个作用就是可以多个容器共享一个宿主机的文件如配置之类的。
  • 在Dockerfile中还有一个指令VOLUME,这个指令和docker run -v有什么区别呢?volume指令指定的位置在容器被删除以后数据文件会被删除吗?如果-v和volume指定了同一个位置,会发生什么事呢?
  • 容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
  • 那么Dockerfile中的VOLUME指令实际使用中是不是就是跟docker run中的-v参数一样是将宿主机的一个目录绑定到容器中的目录以达到共享目录的作用呢? 其实VOLUME指令只是起到了声明了容器中的目录作为匿名卷,但是并没有将匿名卷绑定到宿主机指定目录的功能。 当我们生成镜像的Dockerfile中以Volume声明了匿名卷,并且我们以这个镜像run了一个容器的时候,docker会在安装目录下的指定目录下面生成一个目录来绑定容器的匿名卷(这个指定目录不同版本的docker会有所不同),我当前的目录为:/var/lib/docker/volumes/{容器ID}。 总结: volume只是指定了一个目录,用以在用户忘记启动时指定-v参数也可以保证容器的正常运行。比如mysql,你不能说用户启动时没有指定-v,然后删了容器,就把mysql的数据文件都删了,那样生产上是会出大事故的,所以mysql的dockerfile里面就需要配置volume,这样即使用户没有指定-v,容器被删后也不会导致数据文件都不在了。还是可以恢复的。 volume指定的位置在容器被删除以后数据文件会被删除吗 volume与-v指令一样,容器被删除以后映射在主机上的文件不会被删除。 如果-v和volume指定了不同的位置,会发生什么事呢? 会以-v设定的目录为准,其实volume指令的设定的目的就是为了避免用户忘记指定-v的时候导致的数据丢失,那么如果用户指定了-v,自然而然就不需要volume指定的位置了。 总结 其实一般的dockfile如果不是数据库类的这种需要持久化数据到磁盘上的应用,都是无需指定volume的。指定volume只是为了避免用户忘记指定-v时导致的数据全部在容器中,这样的话容器一旦被删除所有的数据都丢失了。 那么为什么dockerfile中不提供一个能够映射为主机目录:容器目录这样的指令呢? 其实这样的设计是有道理的,如果在dockerfile中指定了主机目录,这样dockerfile就不具备了可移植性了,毕竟每个人所需要映射的目录可能是不同的,那么最好的办法就是把这个权利交给每个运行这个dockerfile的人,所以才会有 run -v 主机目录:容器目录 这样的指令。 ———————————————— 版权声明:本文为CSDN博主「诺浅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq32933432/article/details/120944205

10、CMD

容器运行时需要执行的命令
运行时表示根据之前创建的镜像启动一个容器的过程
CMD 容器启动命令
CMD指令的格式和RUN相似,也是有两种:
1、shell:CMD <命令>
2、exec:CMD ["可执行文件","参数1","参数2"......]
参数列表格式:CMD["参数1","参数2"],在指定了下面的ENTRYPOINT指令后,用CMD指定具体的参数
指定容器启动后要干的事情
dockeFile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后 的参数替换,我们来验证一下这一点:
1、首先启动一下tomcat:docker run -it -p 8080:8080 30ef4019761d,执行命令后访问界面,我们发现可以正常启动,说明执行力tomcat里面的最后一行命令:CMD ["catalina.sh", "run"]
2、前面说CMD只有最后一个指令可以有多个,但只有最后一个生效,CMD会被docker run之后 的参数替换,那么我们在命令后加一个/bin/bash:docker run -it -p 8080:8080 30ef4019761d /bin/bash
我们发现tomcat的状态是启动状态,但界面无法访问了,也就证明tomcat的dockerfile最后一句话CMD ["catalina.sh", "run"]被覆盖了

通常Docker中推荐使用exec格式语法,原因有二。一方面,shell格式语法下会通过/bin/sh -c来执行命令;另一方面,某些镜像甚至不包含Shell,致使shell格式下的命令无法被正常执行。但使用exec格式时,会无法获取环境变量的值。此时则可以考虑使用shell格式语法 187.png 185.png 186.png

189.png 190.png

再来举一个直观的例子,我们在Dockerfile中 CMD指定ping www.baidu.com,在启动命令时 ping www.weibo.com

1、创建一个Dockerfile文件

FROM alpine
#给镜像加一个标签
LABEL name=lori age=18

CMD ping www.baidu.com

2、构建镜像

docker build -t demo20:test -f Dockerfile20 .

82.png

3、启动容器

#在容器停止后删除容器 --rm
docker run -it --rm  demo20:test 

83.png 4、启动命令后面加ping www.weibo.com再启动一个容器,我们发现CMD的命令被启动命令覆盖 84.png

CMD和RUN的区别

CMD是在docker run时运行
RUN是在docker build时运行

11、ENTRYPOINT

也是用来指定一个容器启动时要运行的命令;
类似于CMD指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当做参数送给ENTRYPOINT指令指定的程序。
命令格式:
ENTRYPOINT ["<executeable>","<param1>","<param2>",......]
ENTRYPOINT 可以和 CMD一起用,一般是变参才会使用CMD,这里的CMD等于是在给ENTRYPOINT 传参。
当指定了ENTRYPOINT 后,CMD的含义就发生了变化,不再是直接运行其命令,而是将CMD的内容作为参数传递给ENTRYPOINT 指令,它两个组合会变成<ENTRYPOINT> "<CMD>"
案例如下:假设已通过Dockerfile 构建了nginx.text 镜像
FROM nginx
ENTRYPOINT ["nginx","-c"] #定参
CMD ["/etc/nginx/nginx.conf"] #变参
是否传参 不另外传参 另外传参
命令行执行的Docker命令 docker run nginx:text docker run nginx:text -c /etc/nginx/new.conf
实际运行的命令 nginx -c /etc/nginx/nginx.conf nginx -c /etc/nginx/new.conf

演示ENTRYPOINT命令不会被启动容器命令覆盖的例子 1、编写Dockerfile

FROM alpine
#给镜像加一个标签
LABEL name=lori age=18

ENTRYPOINT ping www.baidu.com

2、构建镜像:docker build --no-cache -t demo21:test -f Dockerfile21 . 85.png

3、启动容器:docker run --rm -it demo21:test ping www.weibo.com ,我们发现执行的还是Dockerfile里面ENTRYPOINT 指令的内容 86.png

12、ARG和ENV

1、ARG

  • ARG指令定义了一个变量,用户可以在构建时使用--build-arg<varname>=<value>传递,docker build命令会将其传递给构建器。(docker build --build-arg param=value1 --build-arg param2=value2 -t 镜像名称:镜像标签 -f Dcokerfile .)
  • --build-arg指定参数会覆盖Dockerfile中指定的同名参数
  • 如果用户指定了未在Dockerfile中定义的构建参数,则构建会输出警告
  • ARG只在构建期有效,在运行期无效
  • 不建议使用构建时变量来传递github密钥,用户凭据等机密。因为构建时变量使用docker history是可见的。
  • ARG变量定义从Dockerfile中定义的行开始生效,在定义行之前使用不会生效。
  • 使用ENV指令定义的环境变量始终会覆盖同名的ARG指令
  • ARG只允许有一个参数,如果想要多个,另起一行新增一个ARG。

2、ENV

  • 构建期运行期都可以生效
  • 构建期无法修改ENV的值,运行期修改ENV:docker run -it -e 键=值 镜像名:镜像标签
  • 在构建阶段中所有后续指令的环境中使用,并且在很多情况下也可以内联替换。(ENV可以引用ARG)
  • 引号和反斜杠可用于在值中包含空格
  • ENV可以使用key value的写法,但是不建议使用了,后续版本可能删除
  • ENV在构建阶段就会被解析并持久化,直接在镜像里面就已经被写死,可以通过docker image inspect 镜像id命令查看

四、案例

1、自定义镜像mycentosjava8

题目说明

在docker里下载一个centos的镜像,下载的镜像运行后,发现不支持 vim、ifconfig、java -version等命令,现在我们实现docker下的centos支持上面三个命令

191.png

编写 1、首先下载一个jdk,再拉到linux里面

	下载jdk地址:https://mirrors.yangxingzhen.com/jdk/
下载后拉到linux的myfile目录下

192.png

在myfile目录下,我们新建一个文件Dockerfile,内容如下

Docekerfile文件

FROM centos:8
MAINTAINER lori<lori@126.com>
 
ENV MYPATH /usr/local
#启动后进去/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-8u171-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

构建

docker build -t 新镜像名字:Tag .
注意:上面Tag后面有个空格,有个点
-t :设置tag build的构建状态
docker build -t centosjava8:1.5 .
执行构建命令后我们发现报下面错误

193.png

由于CentOS 已经停止维护的问题。2020 年 12 月 8 号,CentOS 官方宣布了停止维护 CentOS Linux 的计划,并推出了 CentOS Stream 项目,CentOS Linux 8 作为 RHEL 8 的复刻版本,生命周期缩短,于 2021 年 12 月 31 日停止更新并停止维护(EOL),更多的信息可以查看 CentOS 官方公告。如果需要更新 CentOS,需要将镜像从 mirror.centos.org 更改为 vault.centos.org
针对这种问题我们来给Dokckerfile在yum命令上面加下面的命令:
#首先,进入到 yum 的 repos 目录
RUN cd /etc/yum.repos.d/
其次,修改 centos 文件内容
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
#然后,生成缓存更新(第一次更新,速度稍微有点慢,耐心等待两分钟左右)
yum makecache
#最后,运行 yum update 并重新安装 vim
RUN yum update -y
加入之后再执行构建命令:
docker build -t centosjava8:1.5 .
可以正常构建

194.png

我们来验证一下我们添加的功能是否都已经添加

195.png

2、虚悬镜像

仓库名、标签都是<none>的镜像,俗称dangling image
我们手动创建一个虚悬镜像
首先新建一个文件夹,再文件夹下编写一个内容为一下的Dockerfile:
from ubuntu
CMD echo 'action is success'
退出保存后编译:docker build  .
此时产生的none就是虚悬镜像
查询虚悬镜像的命令:
docker image ls -f dangling=true
删除虚悬镜像:
docker image prune

196.png 197.png