직접 수정할 수 없는 기존 서비스가 있습니다. 비공개 소스이거나 편집하기 너무 복잡하거나 자동으로 업데이트되므로 직접 수정하기에 좋은 대상이 아니라고 가정합니다.
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);
}