我正在使用tail -f /dev/null
命令来保持容器正常运行。
该行本身放置在脚本中,前后都有回声。tail -f /dev/null
下的回声预计无法访问,但由于某种原因,我在日志中看到它。
问题发生后,每次重新启动容器都将导致容器启动并完成。只有 rm 和重建才能解决此问题。
我不确定它是否已连接,但我注意到在很短的时间内停止和启动计算机的一件事帮助我重现了该问题。
在什么情况下tail -f /dev/null
可以继续下一行?
基础映像:ubuntu 64x,14.0.4
计算机操作系统:ubuntu 64x, 14.0.4
这是保持容器运行的更好方法
sleep infinity
要回答您的问题,tail -f /dev/null
在什么情况下可能会完成,因此继续下一行,类似于 shell 脚本:
/dev/null
(与 Linux 中的所有内容一样)是一个文件。对任何文件执行tail
时,必须使用文件描述符打开该文件。并不是说tail -f /dev/null
终止是因为它已经完成(它永远不会完成),它终止是因为对文件描述符的干扰,这可能是由于多种原因而发生的,但是,在容器本身内部(很可能)没有其他事情发生会干扰文件描述符。
由于 docker 容器只是所谓的 Linux 命名空间的某种花哨的覆盖,因此在容器内运行的所有进程(即使它位于单独的 PID 命名空间内)实际上都在您的主机上运行。 因此,由于某种原因,您的主机干扰了您的文件描述符。
要检查进程创建的打开的文件描述符,可以执行以下命令:
$ sudo ls -la /proc/<pid>/fd
您将在输出中看到某些数字:
0
代表标准输入。1
代表标准输出。2
代表标准误差。
其余的是进程正在打开的文件。
<pid>
是要查看的进程的 ID。当将tail -f /dev/null
作为容器内的入口点运行时,很可能会在容器内1
pid。为了在主机上找到 pid,您可以像这样简单地对它进行 grep:
$ sudo ps aux | grep 'tail -f /dev/null'
要自己关闭文件描述符并手动重现在这些情况下会发生什么,您可以使用 GNU 调试器gdb
。 只需将调试器附加到之前找到的 pid 即可:
$ sudo gdb attach <pid>
现在你可以继续选择要关闭的文件描述符(很可能是第3
号,因为该过程不会打开任何其他文件):
(gdb) call (int)close(3)
$1 = 0
现在,在离开调试器时检查容器的日志:
(gdb) quit
根据您的配置,您可能会在容器日志中看到来自tail
的错误:
tail: error reading '/dev/null': Bad file descriptor
如前所述,还有一个标准错误 (2
的文件描述符。 您可以重复整个过程,并在同一调试器会话期间关闭标准错误和实际文件描述符:
(gdb) call (int)close(2)
$1 = 0
(gdb) call (int)close(3)
$2 = 0
(gdb) quit
这样做后,容器日志中不会有可见的错误,如果是 bash 脚本,它将继续下一行。
为了检查究竟是什么干扰了你的文件描述符,你必须在发生的时候广泛地监控你的主机系统。
曾经在我的一些测试环境中/dev/null
以某种方式是一个常规文件 - 也许情况也是如此?
否则我会echo EXIT CODE=$?
作为第二个回声并从那里跳舞。 此外,对于测试 - 也许尝试将tail
替换为长时间睡眠,然后通过docker exec
执行tail
命令,看看是否可以重现相同的行为。
我遇到了同样的问题,答案是你如何写路线,它必须像这个"tail -f dev/null",仅此而已。
使用您选择的基本映像(例如 Ubuntu 64 位 14.0.4)创建一个 Dockerfile。在 Dockerfile 的末尾,添加如下行:
ENTRYPOINT ["tail", "-f", "/dev/null"]
你可以使用 docker 命令
docker run -d --name alpine alpine tail -f /dev/null
参见 使用"退出"后如何保留码头工人高山容器?