既存サービスの悪魔化解除

既存サービスの悪魔化解除

直接変更できない既存のサービスがあります。プライベートソースであるか編集するのが複雑すぎるか自動的に更新されるため、自分で編集するのに良いターゲットではないとします。

Dockerコンテナできちんと実行したいので、サービスと一緒に暮らして死ぬ必要がある別のフォアグラウンドプロセスが必要です。つまり、開始プロセスがサービスを開始する必要があり、サービスが終了(クラッシュ)した場合は、ルートプロセスも開始する必要があります。 quit 、 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やハッキングのように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));
        }
}

daemon.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);
}

関連情報