使用 'docker stop' 和官方 java 图像的 java 进程未收到 SIGTERM

SIGTERM not received by java process using 'docker stop' and the official java image

我正在 运行在 Docker 容器中使用基于 debian/jessie.

的图像 java:7u79 创建一个 dropwizard Java 应用程序

我的 Java 应用程序处理 SIGTERM 信号以正常关闭。当我 运行 没有 Docker 的应用程序时,SIGTERM 处理工作完美。

当我在 Docker 容器中 运行 时,当我发出 docker stop 命令时,SIGTERM 没有到达 Java 应用程序。它会在 10 秒后突然终止进程。

我的Dockerfile:

FROM java:7u79

COPY dropwizard-example-1.0.0.jar /opt/dropwizard/
COPY example.keystore /opt/dropwizard/
COPY example.yml /opt/dropwizard/

WORKDIR /opt/dropwizard

RUN java -jar dropwizard-example-1.0.0.jar db migrate /opt/dropwizard/example.yml

CMD java -jar dropwizard-example-1.0.0.jar server /opt/dropwizard/example.yml

EXPOSE 8080 8081

这个Dockerfile有什么问题?有没有其他方法可以解决这个问题?

假设您通过在 Dockerfile 中定义以下内容来启动 Java 服务:

CMD java -jar ...

当您现在进入容器并列出进程时,例如通过 docker exec -it <containerName> ps AHf(我没有尝试使用 java 但使用 ubuntu 图像)你看到你的 Java 进程不是根进程(不是带有 PID 的进程1) 但是 /bin/sh 进程的子进程:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 18:27 ?        00:00:00 /bin/sh -c java -jar ...
root         8     1  0 18:27 ?        00:00:00   java -jar ...

所以基本上你有一个 Linux shell,它是 PID 1 的主进程,它有一个 PID 8 的子进程 (Java)。

要使信号处理正常工作,您应该避免那些 shell 父进程。这可以通过使用内置 shell 命令 exec 来完成。这将使子进程接管父进程。所以最后,以前的父进程不再存在了。子进程成为 PID 为 1 的进程。在 Dockerfile:

中尝试以下操作
CMD exec java -jar ...

然后进程列表应该显示如下内容:

UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 18:30 ?        00:00:00 java -jar ...

现在你只有一个 PID 为 1 的进程。通常一个好的做法是让 docker 容器只包含一个进程 - PID 为 1 的进程(或者如果你真的需要更多进程,那么你应该使用例如 supervisord 作为 PID 1,它本身负责为其子进程处理信号)。

使用该设置,SIGTERM 将直接由 Java 进程处理。不再有 shell 进程,其间可能会中断信号处理。

编辑:

同样的 exec 效果可以通过使用不同的 CMD 隐含的语法来实现(感谢 Andy 的评论):

CMD ["java", "-jar", "..."]

@h3nrik 的回答是正确的,但有时您确实需要使用脚本来设置启动。在大多数情况下,只需使用 exec 命令即可解决问题:

#!/bin/sh

#--- Preparations

exec java -jar ...

看看这个精彩blog post