理解tini之前先写两个dockerfile
基于centos的nginx
FROM centos
RUN \
cd /etc/yum.repos.d/ && \
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && \
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* && \
yum install -y https://mirrors.aliyun.com/epel/epel-release-latest-8.noarch.rpm && \
curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repo && \
yum -y install vim tree lrzsz gcc make gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel iproute net-tools iotop
ADD nginx-1.16.1.tar.gz /usr/local/src/
RUN \
cd /usr/local/src/nginx-1.16.1 && ./configure --prefix=/usr/local/nginx --with-http_sub_module && make && make install && \
useradd nginx -s /sbin/nologin && \
ln -sv /usr/local/nginx/sbin/nginx /usr/sbin/nginx
COPY monitor /usr/local/nginx/html
EXPOSE 80 443
CMD ["nginx","-g","daemon off;"]
基于ubuntu的nginx
FROM ubuntu
RUN \
apt update && \
apt install -y nginx && \
rm -rf /var/lib/apt/lists/* && \
echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
chown -R www-data:www-data /var/lib/nginx
VOLUME ["/etc/nginx/sites-enabled", "/etc/nginx/certs", "/etc/nginx/conf.d", "/var/log/nginx", "/var/www/html"]
COPY monitor /var/www/html
WORKDIR /etc/nginx
CMD ["nginx"]
EXPOSE 80
EXPOSE 443
下面是tini作者关于为什么使用tini的解释
提问:|注意到 Jenkins 的官方图像使用的是 Tini,所以我很好奇它是什么。
看起来它一定很有用,并且可能解决了一些我不知道的问题。您能否以“Linux for Dummies”的方式简要解释一下Tini与直接作为CMD运行shell脚本的优势是什么?我有几个容器,其中包含一种脚本类型,基本上在最后执行 - 我应该使用 Tini 吗?docker-entrypoint.shexec “$@”
作者回答:首先,让我们谈谈 Docker。当您运行 Docker 容器时,Docker 会继续将其与系统的其余部分隔离开来。这种隔离发生在不同的级别(例如网络、文件系统、进程)。Tini并不真正关心网络或文件系统,所以让我们关注Tini上下文中重要的事情:进程。每个 Docker 容器都是一个 PID 命名空间,这意味着容器中的进程与主机上的其他进程隔离。PID 命名空间是一棵树,从 PID 1 开始,通常称为 。init
注意:当你运行一个 Docker 容器时,PID 1 是你设置为你的任何内容(或者如果你没有,那么它要么是你的 shell 要么是另一个程序,这取决于你的格式)。ENTRYPOINTCMD
现在,与其他进程不同,PID 1 具有独特的责任,即收获僵尸进程。
僵尸进程是具有以下特征的进程:
已退出。
未由其父进程打开(是系统调用父进程用于检索其子进程的退出代码)。waitwait
失去了他们的父母(即他们的父母也退出了),这意味着他们永远不会被父母教育。wait
当一个僵尸被创建时(即当它的父僵尸退出时发生,因此它被它控制的所有机会都消失了),它被重新父级化,预计它会收获它(这意味着调用它)。waitinitwait
换句话说,必须有人清理那些让孩子不上学的“不负责任”的父母,这就是PID 1的工作。wait
这就是 Tini 所做的,也是 JVM(当你这样做时运行)不会做的事情,这也是你不想将 Jenkins 作为 PID 1 运行的原因。exec java …
请注意,创建僵尸通常首先是不受欢迎的(即理想情况下,您应该修复代码,这样它就不会创建僵尸),但对于像 Jenkins 这样的东西,它们是不可避免的:由于 Jenkins 通常运行不是由 Jenkins 维护者编写的代码(即您的构建脚本),因此他们无法“修复代码”。
这就是 Jenkins 使用 Tini:在构建创建僵尸的脚本后进行清理的原因。
现在,Bash 实际上做了同样的事情(收割僵尸),所以你可能想知道:为什么不使用 Bash 作为 PID 1?
一个问题是,如果你将 Bash 作为 PID 1 运行,那么你发送到 Docker 容器的所有信号(例如使用 or )最终都会发送到 Bash,它不会将它们转发到任何地方(除非你自己编码)。换句话说,如果你使用 Bash 运行 Jenkins,然后运行 ,那么 Jenkins 将永远不会看到停止命令!docker stopdocker killdocker stop
Tini 通过“转发信号”进行修复:如果您向 Tini 发送信号,那么它会将相同的信号发送到您的子进程(在您的情况下为 Jenkins)。
第二个问题是,一旦您的进程退出,Bash 也将继续退出。如果你不小心,Bash 可能会以退出代码 0 退出,而你的进程实际上崩溃了(0 表示“一切正常”;这将导致 Docker 重启策略无法执行预期)。您真正想要的是 Bash 返回与您的进程相同的退出代码。
请注意,您可以通过在 Bash 中创建信号处理程序来实际执行转发并返回正确的退出代码来解决此问题。另一方面,这是更多的工作,而添加Tini是Dockerfile中的几行。
现在,将有另一种解决方案,例如在 Jenkins 中添加另一个线程来收获僵尸,并将 Jenkins 作为 PID 1 运行。
这也不是理想的,原因有两个:
首先,如果 Jenkins 作为 PID 1 运行,那么很难区分重新成为 Jenkins 父级的进程(应该收获)和 Jenkins 生成的进程(不应该,因为还有其他代码已经期望它们)。我相信你可以在代码中解决这个问题,但同样:当你可以把 Tini 放进去时,为什么要写它?wait
其次,如果 Jenkins 以 PID 1 的形式运行,那么它可能不会收到你发送的信号!
这是PID 1中的一个微妙之处。与其他不同的进程不同,PID 1 没有默认的信号处理程序,这意味着如果 Jenkins 没有显式安装 的信号处理程序,那么该信号在发送时将被丢弃(而默认行为是终止进程)。SIGTERM
Tini确实安装了显式信号处理程序(顺便转发它们),因此这些信号不再被丢弃。相反,它们被发送到 Jenkins,它不像 PID 1(Tini 是)那样运行,因此具有默认的信号处理程序(注意:这不是 Jenkins 使用 Tini 的原因,他们使用它来接收信号,但它在 RabbitMQ 映像中使用了这个原因)。
请注意,Tini中还有一些额外的功能,在Bash或Java中很难复制(例如,Tini可以注册为subreaper,因此它实际上不需要作为PID 1运行来完成其僵尸收割工作),但这些对于专业用例大多有用。
做 PID 1 需要做的所有事情,没有别的。诸如读取环境文件,更改用户,过程监督之类的事情超出了Tini的范围(还有其他更好的工具)
它需要零配置才能正确完成其工作(Tini >= 0.6 也会在您未正确运行它时警告您)
至于你是否应该使用Tini。
显然,它并不总是需要的(例如,我在十几个 Docker 容器中运行 http://apt-browse.org/,其中只有一个使用 Tini),但这里有一些启发式方法:
您的容器中是否有僵尸(“已失效”)进程?如果是这样,要么修复生成它们的任何内容,要么 - 如果你不能(像Jenkins) - 使用Tini。
入口点中的任何进程是否在注册信号处理程序?解决这个问题的一个好方法是检查您的过程是否正确响应,例如 (或者如果它在退出前等待 10 秒)execdocker stop
现在,Tini是透明的,所以如果你不确定,添加它应该不会有什么坏处。
下面我们实践一下
使用下面的dockerfile加入tini
FROM ubuntu
RUN \
apt update && \
apt install -y nginx && \
rm -rf /var/lib/apt/lists/* && \
echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
chown -R www-data:www-data /var/lib/nginx
VOLUME ["/etc/nginx/sites-enabled", "/etc/nginx/certs", "/etc/nginx/conf.d", "/var/log/nginx", "/var/www/html"]
COPY monitor /var/www/html
WORKDIR /etc/nginx
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["nginx"]
EXPOSE 80
EXPOSE 443
docker build 一下
docker build -t nginx_tini_ubuntu:v0.0.1 -f ubuntu_nginx .
基于构建的镜像运行容器
docker run -itd -P --name tini_nginx 4763df17ecae
网页访问一下
可以访问
进入容器内看看