我有一个现有的服务,我无法直接修改它 - 假设它是闭源的,或者太复杂而无法编辑,或者自动更新,因此不是直接修改的好目标。
我想在 Docker 容器中干净地运行它,所以我需要一个单独的前台进程,它应该与服务一起生存和消亡,即启动进程应该启动服务,当服务终止(例如崩溃)时,根进程应该也终止,以便 Docker 容器终止。
是否有一个好的模式或现有工具可以做到这一点?理想情况下,它将服务的 stdout/stderr 重定向到它自己的 stdout/stderr。
该服务有一个常规的初始化脚本,并通过 启动/etc/init.d/myservice start
。
我经常看到的是容器启动服务,然后tail -n0 -F
在某些核心日志文件上使用。这可以为容器提供某种标准输出,但如果服务崩溃,容器就会变得安静,继续运行并且不再输出任何内容。一定有更好的方法,不是吗?
思考这一问题的一个例子可能是svnserve
。它实际上有--daemon --foreground
一个官方仅用于调试的选项,但可以使用。但如果它不存在怎么办?
答案1
如果二进制文件是动态链接的那么你可以LD_PRELOAD
一个fork
包装器,除了在第一次调用时设置一个标志之外什么也不做,并且在后续调用中会看到该标志并正常运行。
如果二进制文件是静态链接的那么你可以跟踪直到第一次fork
调用,跳过该调用并停止跟踪。
在 Linux 上,您可以在专用的环境中运行守护进程PID命名空间它只是在命名空间中以 PID 1 的身份运行监控脚本和守护进程。当守护进程退出时,命名空间中的PID 1(即监控脚本)会收到SIGCLD。
答案2
在 Linux 上,您可以通过副收割者包装器,它的作用类似于任何后代子进程的 pid one/init 程序,捕获并等待任何双分叉子进程。简单的例子:
$ cc -Wall -s daemon.c -o daemon
$ cc -Wall -s undaemon.c -o undaemon
$ ./daemon sleep 10
# the parent forks + exits immediately
$ ./undaemon ./daemon sleep 2
# waits until all children have terminated
这样做的优点是它不需要任何额外的权限,并且也可以与 setuid/setgid 程序一起使用。
另一种方法是递归地ptrace(2)
对子元素进行 - 操作,这可以通过strace(1)
.然而,它更重量级,并且可能以奇怪的方式与跟踪的程序交互。当然,就像 PID 命名空间或LD_PRELOAD
hack 一样,它不适用于 setuid 程序。
$ strace -fe trace=none ./daemon sleep 2
strace: Process 6743 attached
[pid 6742] +++ exited with 0 +++
+++ exited with 0 +++
# also waits for all children
undaemon.c:
#define _DEFAULT_SOURCE /* for kill() */
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/wait.h>
int main(int argc, char **argv){
int s;
if(prctl(PR_SET_CHILD_SUBREAPER, 1))
err(1, "prctl(PR_SET_CHILD_SUBREAPER)");
switch(fork()){
case -1:
err(1, "fork");
case 0:
if(!--argc || !*++argv) return 0;
execvp(*argv, argv);
err(1, "execvp %s", *argv);
default:
while(wait(&s) != -1 || errno == EINTR);
if(errno != ECHILD) err(1, "wait");
if(WIFSIGNALED(s)) kill(getpid(), WTERMSIG(s));
_exit(WEXITSTATUS(s));
}
}
守护进程.c:
#define _DEFAULT_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
int main(int argc, char **argv){
if(daemon(1, 1)) err(1, "daemon");
if(!--argc || !*++argv) exit(0);
execvp(*argv, argv);
err(1, "execvp %s", *argv);
}