当您的应用程序是经过docker的方式运行时,在终止应用程序前进行适当的清理,也就是所谓的优雅退出也很是重要。尽管这仅仅取决于确保信号到达您的应用程序并对其进行处理,但仍有不少方面可能致使错误。python
从原则上讲,这很是简单:当您(或您的集群管理工具)运行docker stop时,Docker会向您的应用程序的入口点发送可配置的信号; SIGTERM是默认值。在对个人应用程序进行容器化时,我肯定了四个陷阱(但只进入了其中三个!),以便为您节省一些调查时间。docker
1:您使用了错误的ENTRYPOINT 格式 shell
Dockerfile容许您使用一种诱人的便捷字符串(称为shell格式)来定义入口点:数组
ENTRYPOINT "/app/bin/your-app arg1 arg2"
或者更使人讨厌的 exec 格式,它使您能够将命令行做为JSON数组提供:app
ENTRYPOINT ["/app/bin/your-app", "arg1", "arg2"]
长话短说:始终使用后一种exec格式。 Shell格式将您的入口点做为 /bin/sh -c 的子命令来运行,它带有不少问题,其中一个值得注意的问题是您在应用程序中永远看不到信号。框架
2:您的入口点是一个Shell脚本,您没有exec工具
若是您以常规方式从Shell脚本运行应用程序,则Shell将以新进程生成应用程序,而且您的应用程序将不会收到来自Docker的信号。命令行
您须要作的就是告诉您的Shell用您的应用程序替换自身。为此,shell具备 exec 命令(与前面讲到的 exec 格式类似)。详情见exec syscall。日志
因此替换code
/app/bin/your-app
为:
exec /app/bin/your-app
您确实使用了exec,可是您经过启动子shell欺骗了本身
我一直是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。
总结