为什么您的Docker应用程序接收不到信号?

当您的应用程序是通过docker的方式运行时,在终止应用程序前进行适当的清理,也就是所谓的优雅退出也非常重要。尽管这仅仅取决于确保信号到达您的应用程序并对其进行处理,但仍有很多方面可能导致错误。

从原则上讲,这非常简单:当您(或您的集群管理工具)运行docker stop时,Docker会向您的应用程序的入口点发送可配置的信号; SIGTERM是默认值。在对我的应用程序进行容器化时,我确定了四个陷阱(但只进入了其中三个!),以便为您节省一些调查时间。

1:您使用了错误的ENTRYPOINT 格式

Dockerfile允许您使用一种诱人的便捷字符串(称为shell格式)来定义入口点:

ENTRYPOINT "/app/bin/your-app arg1 arg2"

或者更令人讨厌的 exec 格式,它使您可以将命令行作为JSON数组提供:

ENTRYPOINT ["/app/bin/your-app", "arg1", "arg2"]

长话短说:始终使用后一种exec格式。 Shell格式将您的入口点作为 /bin/sh -c 的子命令来运行,它带有很多问题,其中一个值得注意的问题是您在应用程序中永远看不到信号。

2:您的入口点是一个Shell脚本,您没有exec

如果您以常规方式从Shell脚本运行应用程序,则Shell将以新进程生成应用程序,并且您的应用程序将不会收到来自Docker的信号。

您需要做的就是告诉您的Shell用您的应用程序替换自身。为此,shell具有 exec 命令(与前面讲到的 exec 格式相似)。详情见exec syscall。

所以替换

/app/bin/your-app

为:

exec /app/bin/your-app

您确实使用了exec,但是您通过启动子shel​​l欺骗了自己

我一直是runit日志记录的忠实拥护者,您只需在没有时间戳的情况下就将日志记录到stdout,并且runit会在所有日志条目前添加tai64n时间戳。

例如:

exec /app/bin/your-app | tai64n

但是,这导致您的应用程序在子shell中执行,其结果通常是:没有信号给您。

3:您监听了错误的信号

尽管SIGTERM在进程管理器中盛行,但许多框架仍希望使用SIGINT ,例如Control-C停止应用程序。特别是在Python生态系统中,通常要做:

try:
    do_work()
except KeyboardInterrupt:
    cleanup()

如果不采取任何进一步的措施,则如果您的应用程序收到SIGTERM,将永远不会调用cleanup()。更糟的是,如果您的PID为1,那么超过最大等待时间,然后将SIGKILL发送到入口点之前,实际上什么也不会发生。

因此,如果您曾经想知道为什么docker stop会花费这么长时间–这可能的原因:您没有监听SIGTERM,并且由于PID为1,信号从您的进程中弹回。没有清理,缓慢关闭。

最简单的解决方法是在Dockerfile中添加一行:

STOPSIGNAL SIGINT

但是,您实际上应该尝试同时支持这两种信号,并首先避免成为PID 1。

总结

  • 使用ENTRYPOINT的 exec/JSON 数组形式。
  • 在shell程序入口点中使用exec。
  • 不要pipe应用程序的输出。
  • 避免成为PID 1。
  • 侦听SIGTERM或在Dockerfile中设置STOPSIGNAL。

你可能感兴趣的:(docker,linux,kubernetes,k8s)