对现有服务进行去守护进程

对现有服务进行去守护进程

我有一个现有的服务,我无法直接修改它 - 假设它是闭源的,或者太复杂而无法编辑,或者自动更新,因此不是直接修改的好目标。

我想在 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_PRELOADhack 一样,它不适用于 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);
}

相关内容