文章目录
基础信息
是什么
Dockerfile 是镜像的描述文件,Docker 可以读取这个文件生成新的镜像。
Dockerfile、镜像、容器三者关系
从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段:
- Dockerfile是软件的原材料
- Docker镜像是软件的交付品
- Docker容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例
Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。
构建三步骤:
- 编写 Dockerfile 文件
- Docker build 命令构建镜像
- Docker run 依镜像运行容器实例
Docker 执行 Dockerfile 的大致流程
- Docker从基础镜像运行一个容器
- 执行一条指令并对容器作出修改
- 执行类似 Docker commit 的操作提交一个新的镜像层
- Docker 再基于刚提交的镜像运行一个新容器
- 执行 Dockerfile 中的下一条指令直到所有指令都执行完成
编写 Dockerfile
Docker hub 上的镜像也给出了 Dockerfile 文件链接,可以点击查看参照。
Dockerfile 基础信息:
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- #表示注释
- 每条指令都会创建一个新的镜像层并对镜像进行提交
Dockerfile 文件保留字
FROM
基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是 FORM
MAINTAINER
镜像维护者的姓名和邮箱地址
RUN
构建镜像时(Docker build)运行的命令
两种格式
- shell 格式:
RUN 命令
- RUN 后面的命令相当于在 FORM 的基础镜像上,实例出的容器里的终端中运行。
- 比如想要基于 centos7 构建一个带有 node 环境的 centos7Plus 镜像,
RUN yum install -y node
- 那就相当于在原版 centos7 的终端中执行
yum install -y node
- RUN 后面的命令相当于在 FORM 的基础镜像上,实例出的容器里的终端中运行。
- exec 格式(json 数组形式):
RUN ["可执行文件", "参数1", "参数2"]
- eg:
RUN ["./test.php", "dev", "offline"
等同于RUN ./test.php dev offline
- eg:
EXPOSE
当前容器对外暴露出的端口
WORKDIR
指定在创建容器后,终端默认登录的进来工作目录,一个落脚点。比如 Tomcat 镜像,一进入容器终端,当前目录为/usr/local/tomcat
USER
指定该镜像以什么样的用户去执行,如果都不指定,默认是root。
USER 用户名:用户组 或
USER 用户id:组id
ENV
用来在构建镜像过程中设置环境变量。
举个例子:
ENV MY_PATH /usr/mytest
这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面定义了别名一样;也可以在其它指令中直接使用这些环境变量。
比如,设置默认工作目录:WORKDIR $MY_PATH
ADD 和 COPY
ADD <src> <dest>
- 详解:该命令将复制指定的 路径下内容到镜像中的 路径下
- :可以是 Dockerfile 所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)
- :可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径
- 路径:支持正则表达式,
COPY 类似 ADD,拷贝文件和目录到镜像中。
将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。
和 RUN 一样两种格式:
COPY src dest
COPY ["src", "dest"]
<src源路径>:源文件或者源目录
<dest目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
注意
- 来自远程 URL 资源不会被解压缩。如果一个目录被复制或解压时,它的行为与 tar -x 相同。
- 是绝对路径,或相对于 WORKDIR 的路径。并且表示目录要以斜杠结尾,要不然会被视为文件,内容将会追加入该文件中。
# 使用相对路径的栗子
ADD test.txt relativeDir/
# 等价于
ADD test.txt <WORKDIR>/relativeDir/
# 使用绝对路径的栗子
# 将 test.txt 添加到 /absoluteDir/ 目录下
ADD test.txt /absoluteDir/
- 如果是目录,只会复制目录中的内容过去,目录本身不复制过去。并且**是 Dockerfile 文件所在目录的相对路径。**
- 不能添加 …/something 、 /something 这样的目录,因为 docker 构建的第一步是将上下文目录,也就是 Dockerfile 文件所在的目录(和子目录)发送到 docker 守护进程
# test.txt 是相对路径,相对于构建上下文
COPY test.txt /mkdir/
# 错误写法,文件均不在上下文目录中,并不会被找到
# 这个找的就是构建上下文的上级目录的 test.txt
COPY ../test.txt /mkdir/
# 这个找的是本机根目录下的 test.txt
COPY /test.txt /mkdir/
根据官方 Dockerfile 最佳实践,除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY。
- ADD 从远程 url 获取文件和复制的效果并不理想,因为该文件会增加 Docker Image 最终的大小
- 相反,应该使用 curl huo wget 来获取远程文件,然后在不需要它时进行删除
总结:
- ADD 支持添加远程 url 和自动提取压缩格式的文件,COPY 只允许从本机中复制文件
- COPY 支持从其他构建阶段中复制源文件(–from)
- 根据官方 Dockerfile 最佳实践,除非真的需要从远程 url 添加文件或自动提取压缩文件才用 ADD,其他情况一律使用 COPY
VOLUME
容器数据卷,用于数据保存和持久化工作
CMD 和 ENTRYPOINT
两者都是指定新建的镜像运行时(docker run),容器终端中默认执行的命令。
比如运行 ubuntu 镜像:docker run -it ubuntu
,一运行就直接启动了 Ubuntu 系统的 bash。应该就是构建 ubuntu 镜像时,Dockerfile 通过 CMD 或者 ENTRYPOINT 指定了默认启动 bash。
两者指定的命令,最好是阻塞式命令,也就是有前台任务的命令,比如 bash,tail 这些。要不然如果是非阻塞式的命令,命令一运行完毕,容器就退出停止执行了。
Dockerfile 中这两个指令必须要有一个,如果没有则从父镜像中继承,如果父镜像也没有,则镜像构建失败。
两者和 RUN 一样有两种格式定义命令,shell 和 exec。
- Shell格式:。例如:apt-get install python3
- Exec格式:[“executable”, “param1”, “param2”, …]。例如: [“apt-get”, “install”, “python3”]
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
区别
- Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,并且 CMD 指定的默认命令会被 docker run 之后手动传入的命令替换。
比如 Redis,直接运行docker run -it redis
它直接进入 Redis 控制命令行。如果运行docker run -it redis /bin/bash
,就会进入 Redis 容器的虚拟 Linux 环境终端,需要再运行redis-cli
才能进入 Redis 命令控制行。
[root@192 app]# docker run -it redis
1:C 08 Feb 2023 19:57:36.965 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
......
1:M 08 Feb 2023 19:57:36.968 * Ready to accept connections
(ctrl + c)
1:M 08 Feb 2023 19:58:37.474 # Redis is now ready to exit, bye bye...
[root@192 app]# docker run -it redis /bin/bash
root@b5df641baaa0:/data# redis-cli
- ENTRYPOINT 为 shell 格式,则以 ENTRYPOT 为准,忽略 CMD 和 docker run 后手动添加的命令。
- ENTRYPOINT 为 exec 格式且 CMD 也为 exec 格式时,无论是 CMD 还是手动输入的命令都是拼接到 ENTRYPOINT 的命令后面作为参数。
- 注意:docker run 后面存在命令时,始终会覆盖 CMD 的命令
FROM alpine
WORKDIR /docker
RUN echo 111 >> 1.txt
RUN echo 222 >> 2.txt
RUN echo 333 >> 3.txt
CMD ["cat","/docker/2.txt"]
ENTRYPOINT ["cat","/docker/1.txt"]
docker build -t test1 .
docker run test1 #执行的命令相当于 cat 1.txt cat 2.txt
> cat: can't open 'cat': No such file or directory
> 111
> 222
docker run test1 3.txt #执行的命令相当于 cat 1.txt 3.txt
> 111
> 333 #docker run 后面参数代替了 CMD
- ENTRYPOINT 为 exec 格式且 CMD 为 shell 时,拼接的结果是
[ENTRYPOINT命令] /bin/sh -c [CMD命令]
,因为整个命令中间多了一个/bin/sh -c
命令通常会导致整个命令无效。所以这种混合格式使用要少用。sh -c
命令,它可以让 bash 将一个字串作为完整的命令来执行。
FROM alpine
WORKDIR /docker
RUN echo 111 >> 1.txt
RUN echo 222 >> 2.txt
RUN echo 333 >> 3.txt
CMD cat /docker/2.txt
ENTRYPOINT ["cat","/docker/1.txt"]
docker build -t test1 .
docker run test2 #执行的命令相当于 cat 1.txt /bin/sh -c cat 2.txt
#因为 cat 命令无法识别 -c 选项,所以导致整个命令无效报错
cat: unrecognized option: c
BusyBox v1.34.1 (2021-11-23 00:57:35 UTC) multi-call binary.
Usage: cat [-nbvteA] [FILE]...
Print FILEs to stdout
-n Number output lines
-b Number nonempty lines
-v Show nonprinting characters as ^x or M-x
-t ...and tabs as ^I
-e ...and end lines with $
-A Same as -vte
docker run test2 3.txt #覆盖了CMD,所以命令正常
> 111
> 333
官网介绍的区别:
Understand how CMD and ENTRYPOINT interact
Both and instructions define what command gets executed when running a container. There are few rules that describe their co-operation.CMD ENTRYPOINT
- Dockerfile should specify at least one of or commands.
CMD ENTRYPOINT
ENTRYPOINT
should be defined when using the container as an executable.CMD
should be used as a way of defining default arguments for an command or for executing an ad-hoc command in a container.ENTRYPOINT
CMD
will be overridden when running the container with alternative arguments.
The table below shows what command is executed for different / combinations:ENTRYPOINTCMD
Note:
If is defined from the base image, setting will reset to an empty value. In this scenario, must be defined in the current image to have a value.CMD ENTRYPOINT CMD CMD
总结
不同阶段,发挥作用的关键字。
BUILD | BOTH | RUN |
---|---|---|
FORM | WORKDIR | CMD |
MAINTAINER | USER | ENV |
COPY | EXPOSE | |
ADD | VOLUME | |
RUN | ENTRYPOINT | |
ONBUILD | ||
.dockerignore |
练习
需求:构建带有 vim+ifconfig+jdk17 的 centos 镜像。
下载 jdk17 到 Dockerfile 文件所在目录(这里为:/docker)
wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz -P /docker/
编写 Dockerfile:
FROM centos7
MAINTAINER ikun
ENV MYPATH /usr/local
WORKDIR $MYPATH
#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD 是相对路径jar,把jdk添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-17_linux-x64_bin.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk-17.0.6
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH
EXPOSE 80
#CMD的覆盖不影响CMD输出
CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash
构建镜像
build 命令构建镜像
语法:docker build -t <镜像名>:<tag> <Dockerfile文件目录>
举例:docker build -t myCentos .
- tag 省略,默认为 latest
- . 表示当前目录
虚悬镜像(dangling image)
虚悬镜像就是镜像名和 tag 都是 none 的镜像。
我们在构建的时候,不小心忘记填写镜像名和 tag,如docker build .
就会构建出虚悬镜像。
查看本机的虚悬镜像列表:docker image ls -f dangling=true
虚悬镜像没有价值,建议删除:docker image prune