dockerfile
docker build过程
docker build的上下文:dcokerfile文件所在目录中的所有内容就是docker build的上下文
在通过dockerfile组装镜像的流程中,每条指令都是单独执行的,从FROM指令捞取一个base image开始,每条指令都会生成一个新的镜像层。dockerfile最终生成的镜像就是在基础镜像上一层层打包组装的。
在build过程中,如果发现这一层镜像已经在缓存中有了,则命中缓存直接使用该镜像,否则才生成新的。在对比过程中,COPY和ADD指令比较的是生成镜像的指令字符串和容器内文件内容,其他指令都是仅比较指令字符串
在构建过程中,一旦缓存失效,后续的指令都生成新的镜像不再使用缓存。
dockerfile指令
docker指令不区分大小写,但是约定俗成会让指令大写,参数小写。例如:
INSTRUCTION arguments
dockerfile中的#,出现在行开头回被判定为注释,但在行中间就会被当作参数,例如:
#Comment
RUN echo ‘we are running some # of cool things’
当一行内容较长,可以使用反斜杠\表示行未终结的换行符
FROM指令
功能是为后面的指令提供基础镜像。一个有效的dockerfile第一个非注释指令必须是FROM指令。FROM可以选择任何有效镜像。如果一个dockerfile有多个FROM指令,则会构建多个镜像。镜像构建完成后,docker命令行界面会输出该镜像的ID
FROM image<:tag>
当tag为空时,默认使用latest
ENV指令和ARG指令
ENV指令用于为镜像创建出来的容器声明环境变量,这些变量会被后面特定指令(ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER)解析使用。其他指令使用环境变量的格式为:
$variable_name
或${variable_name}
如果在变量引用前面加反斜杠\转义,将不当作变量处理,而是当作常量,例如:
\$varible_name
被解析为$variable_name
而不是它对应的变量值
ARG指令也是指定变量,用法和ENV差不多,但是二者的区别是ARG声明的变量仅在docker build过程中生效,而ENV指定的变量在build过程生效,且container启动后依旧作为环境变量生效
二者在多变量赋值的时候也有格式差异,即ENV给多个变量赋值不换行,使用\进行分割:
ARG var1=var1
ARG var2=var2
ENV var3=var3 \
var4=var4
COPY指令
COPY src dst
COPY指令用于赋值src对应的目录或文件,添加到镜像中。复制的文件或目录在镜像中的路径是dst。
src
src可以有多个,但必须在上下文中,必须是上下文根目录的相对路径。不能使用../something
这样的相对路径,或/something
这样的绝对路径
可以使用通配符,例如COPY home* /mydir/
,表示赋值所有home开头的文件到对应目录
dst
dst可必须写绝对路径,或通过WORKDIR指令指定的相对路径。
dst可以是路径或文件,以/结尾代表路径,否则代表文件。如果dst是文件,src的内容会写入到对应文件中,否则,src的文件会赋值到目录里。但当src是多个源时,dst只能是目录。
dst不存在,并不会主动创建目录。
ADD指令
ADD src dst
ADD *.jar /app.jar
ADD指令用于添加jar包,把dockerfile当前目录中的*.jar
添加到容器根目录下重命名为app.jar
RUN指令
RUN指令的两种格式:
# shell格式:
RUN <command>
# exec格式:
RUN [“executable”, “param1”, “param2”]
RUN指令会在前一条命令创建出的镜像基础上创建一个容器,并在容器中执行对应命令,命令结束运行后提交容器为新镜像,给dockerfile下一条指令使用
RUN指令使用shell格式时,命令通过/bin/sh-c
运行,使用exec格式是直接运行的,容器不需要调用shell程序。
推荐使用exec格式。
在exec格式中,参数当成JSON数组解析,因此只能用双引号,不能用单引号。同时JSON数组里面的内容也不能用变量引用替换,因为不是shell执行的。如果强行使用shell执行,可以使用:
RUN [“sh”, “-c”, “echo”, “$HOME”]
在exec格式中,因为是JSON,必须对\进行转义,因此在使用路径分割符的时候要写成
RUN ["c:\windows\system32\tasklist.exe"]
在shell格式中,如果命令过长,可以使用\换行
CMD指令
CMD指令有三种格式:
# shell格式:
CMD <command>
# exec格式:
CMD [“executable”, “param1”, “param2”]
# 为ENTRYPOINT提供参数:
CMD [“param1”, “param2”]
CMD提供容器运行时默认值。如果写了多条CMD命令,则只会执行最后一条,如果后续存在ENTRYPOINT命令,则CMD命令或被充当参数或者覆盖。
CMD和RUN的区别:执行命令的时机不同,RUN命令适用于在 docker build 构建docker镜像时执行的命令,而CMD命令是在 docker run 执行docker镜像构建容器时使用
ENTRYPOINT指令
# shell格式:
ENTRYPOINT <command>
#exec格式:
ENTRYPOINT [“executable”, “param1”, “param2”]
与CMD指令类似,可以让容器在每次启动时执行相同命令。一个Dockerfile可以有多个ENTRYPOINT指令,但只有最后一条有效。
以shell格式运行时,获取CMD指令和docker run
命令的参数,并且运行在/bin/sh -c
中,即ENTRYPOINT指令进程是/bin/sh -c
的子进程,即PID不是1,且不接受UNIX信号,因此在docker stop时,命令进程接收不到SIGTERM信号
推进使用exec格式,可以接收CMD和docker run
的参数,docker run
可以覆盖CMD的,但不能覆盖ENTRYPOINT的。例如:
# CMD的hello作为ENTRYPOINT echo指令的参数
FROM centos
CMD ["hello"]
ENTRYPOINT ["echo"]
# 使用docker run 覆盖
docker run xx hello-docker
# 使用hello-docker覆盖CMD的hello作为echo的输出参数,而如果ENTRYPOINT的echo后面优参数就覆盖不掉
ONBUILD
ONBUILD <instruction>
ONBUILD会添加一条指令,但是在本次构建中不生效。一旦有其他镜像要FROM本镜像进行构建,就会执行ONBUILD的命令
docker容器与信号
信号
我们可能都使用过 docker stop
命令来停止正在运行的容器,有时可能会使用 docker kill
命令强行关闭容器或者把某个信号传递给容器中的进程。这些操作的本质都是通过从主机向容器发送信号实现主机与容器中程序的交互。
信号是一种进程间通信的形式。一个信号就是内核发送给进程的一个消息,告诉进程发生了某种事件。当一个信号被发送给一个进程后,进程会立即中断当前的执行流并开始执行信号的处理程序(这么说不太准确,信号是在特定的时机被处理)。如果没有为这个信号指定处理程序,就执行默认的处理程序。
常见的信号例如:SIGTERM优雅退出(给pod30s时间完成优雅停机重启)、SIGKILL粗暴结束
在某些情况下,Kubernetes 会因为 Pod 出现故障,或者因为主机上的资源不足(称为驱逐)而关闭 Pod。每当 Kubernetes 出于任何原因需要终止 pod 时,它都会向 pod 中运行的容器发送 SIGTERM。
Kubernetes 终止 pod 的完整过程如下:
Pod 设置为 Terminating 状态:然后 Kubernetes 将其从所有服务中删除,并停止接收新流量。此时,在 pod 上运行的容器并不会感知到这一变化。
preStop hook:这是一个特殊的命令,在 pod 开始终止之前发送到 pod 中的容器。您可以在容器中使用此 hook 来启动正常关闭。虽然最好直接处理 SIGTERM 信号(在下一步中发送),但如果由于任何原因无法执行,则可以使用 preStop hook,且无需更改应用程序的代码。
SIGTERM 信号发送到 pod:Kubernetes 将 SIGTERM 发送到 pod 中的所有容器。理想情况下,您的应用程序应该处理 SIGTERM 信号并启动干净的关闭过程。请注意,即使处理了 preStop hook,您仍然需要测试并了解您的应用程序如何处理 SIGTERM。对 preStop 和 SIGTERM 的冲突或重复反应可能导致生产问题。
宽限期:发送 SIGTERM 后,Kubernetes 会等待 TerminationGracePeriod,默认为 30 秒,以允许容器关闭。您可以在每个 pod 的 YAML 模板中自定义宽限期。注意:Kubernetes 不会等待 preStop hook 完成,它从发送 SIGTERM 信号的那一刻开始计算宽限期。如果容器在宽限期结束之前自行退出,Kubernetes 将停止等待并进入下一步。
向 pod 发送 SIGKILL 信号:所有正在运行的容器进程在主机上立即终止,并且 kubelet 将清理所有相关的 Kubernetes 对象。
Docker 的 stop 和 kill 命令都是用来向容器发送信号的。注意,只有容器中的 1 号进程能够收到信号,这一点非常关键!stop 命令会首先发送 SIGTERM 信号,并等待应用优雅的结束。如果发现应用没有结束(用户可以指定等待的时间),就再发送一个 SIGKILL 信号强行结束程序。kill 命令默认发送的是 SIGKILL 信号,当然你可以通过 -s
选项指定任何信号。
接收信号的进程
容器中PID为1的进程负责接收信号,该进程具有处理/转发信号的能力,容器就能处理信号,如果没有,容器就无法响应信号
可以通过docker run
或dockerfile指定容器的1号进程,例如
docker run -it -d --name haproxy --link APP1:APP1 --link APP2:APP2 -p 6301:6301 -v /opt/tycloud /HAProxy:/tmp haproxy /bin/bash
以/bin/bash
作为1号进程,或在dockerfile中添加
ENTRYPOINT [‘/bin/bash’]
tini进程
以/bin/bash
作为1号进程存在一些问题,例如自动退出,如果容器中的主业务因为某种原因退出了,/bin/bash
就会自动退出,容器就会变成完成状态,且/bin/bash
是无法处理信号的
tini是一个被经常用于docker1号进程的工具,他的作用包括:
僵尸进程(Zombie Process):当子进程终止但父进程没有正确地调用 wait() 函数回收其资源时,子进程就会变为僵尸进程。在容器环境中,若父进程未能妥善处理这种情况,可能会导致僵尸进程堆积。
信号转发(Signal Propagation):当发送给容器的信号(例如通过 docker stop 发送的 SIGTERM)被容器内的 PID 1 进程接收到时,需要确保这些信号能够正确地传递给所有的子进程。标准 shell(如 bash 或 sh)并不总是适合作为容器的 PID 1 进程,因为它们可能无法正确处理或转发信号。
例如
ENTRYPOINT ["/usr/local/bin/tini", "--", "/docker-entrypoint.sh"]
不使用tini的话,也可以写为
ENTRYPOINT ["/docker-entrypoint.sh"]
评论区