Merge branch 'ckuehl_exit_status_better_help'
This commit is contained in:
commit
b535104e3b
10 changed files with 167 additions and 10 deletions
2
Makefile
2
Makefile
|
@ -1,4 +1,4 @@
|
||||||
DOCKER_TEST := sh -c 'dpkg -i /mnt/dist/*.deb && cd /mnt/test && ./test'
|
DOCKER_TEST := sh -c 'dpkg -i /mnt/dist/*.deb && cd /mnt && ./test'
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
|
|
79
README.md
Normal file
79
README.md
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
dumb-init
|
||||||
|
========
|
||||||
|
|
||||||
|
`dumb-init` is a simple process designed to run as PID 1 inside Docker
|
||||||
|
containers and proxy signals to a single child process.
|
||||||
|
|
||||||
|
In Docker containers, a process typically runs as PID 1, which means that
|
||||||
|
signals like TERM will just bounce off your process unless it goes out of its
|
||||||
|
way to handle them (see the "Why" section below). This is a big problem with
|
||||||
|
scripts in languages like Python, Bash, or Ruby, and can lead to leaking Docker
|
||||||
|
containers if you're not careful.
|
||||||
|
|
||||||
|
|
||||||
|
## Why you need a signal proxy
|
||||||
|
|
||||||
|
Normally, when processes are sent a signal like `TERM`, the Linux kernel will
|
||||||
|
try to trigger any custom handlers the process has registered for that signal.
|
||||||
|
|
||||||
|
If the process hasn't registered custom handlers, the kernel will fall back to
|
||||||
|
default behavior for that signal (such as killing the process, in the case of
|
||||||
|
`TERM`).
|
||||||
|
|
||||||
|
However, processes which run as PID 1 get special treatment by the kernel, and
|
||||||
|
default signal handlers won't be applied. If your process doesn't explicitly
|
||||||
|
handle these signals, a `TERM` will have no effect at all.
|
||||||
|
|
||||||
|
For example, if you have Jenkins jobs that do `docker run my-container script`,
|
||||||
|
sending TERM to the `docker run` process will typically kill the `docker run`
|
||||||
|
command, but leave the container running in the background.
|
||||||
|
|
||||||
|
|
||||||
|
## What `dumb-init` does
|
||||||
|
|
||||||
|
`dumb-init` runs as PID 1, acting like a simple init system. It launches a
|
||||||
|
single process, and then proxies all received signals to that child process.
|
||||||
|
|
||||||
|
Since your actual process is no longer PID 1, when it receives signals from
|
||||||
|
`dumb-init`, the default signal handlers will be applied, and your process will
|
||||||
|
behave as you would expect.
|
||||||
|
|
||||||
|
If your process dies, `dumb-init` will also die.
|
||||||
|
|
||||||
|
|
||||||
|
## Installing inside Docker containers
|
||||||
|
|
||||||
|
You have a few options for using `dumb-init`:
|
||||||
|
|
||||||
|
|
||||||
|
### Option 1: Installing via an internal apt server
|
||||||
|
|
||||||
|
If you have an internal apt server, uploading the `.deb` to your server is the
|
||||||
|
recommended way to use `dumb-init`. In your Dockerfiles, you can simply
|
||||||
|
`apt-get install dumb-init` and it will be available.
|
||||||
|
|
||||||
|
|
||||||
|
### Option 2: Installing the `.deb` package manually
|
||||||
|
|
||||||
|
If you don't have an internal apt server, you can use `dpkg -i` to install the
|
||||||
|
`.deb` package. You can choose how you get the `.deb` onto your container
|
||||||
|
(mounting a directory or `wget`-ing it are some options).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once installed inside your Docker container, simply prefix your commands with
|
||||||
|
`dumb-init`. For example:
|
||||||
|
|
||||||
|
$ docker run my_container dumb-init python -c 'while True: pass'
|
||||||
|
|
||||||
|
Running this same command without `dumb-init` would result in being unable to
|
||||||
|
stop the container without SIGKILL, but with `dumb-init`, you can send it more
|
||||||
|
humane signals like TERM.
|
||||||
|
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
* [Docker and the PID 1 zombie reaping problem (Phusion Blog)](https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/)
|
||||||
|
* [Trapping signals in Docker containers (@gchudnov)](https://medium.com/@gchudnov/trapping-signals-in-docker-containers-7a57fdda7d86)
|
||||||
|
* [pgctl](https://github.com/Yelp/pgctl)
|
9
debian/changelog
vendored
9
debian/changelog
vendored
|
@ -1,5 +1,12 @@
|
||||||
|
dumb-init (0.0.2) unstable; urgency=low
|
||||||
|
|
||||||
|
* Exit with the same exit status as the process we call.
|
||||||
|
* Print a more useful help message when called with no arguments.
|
||||||
|
|
||||||
|
-- Chris Kuehl <ckuehl@yelp.com> Thu, 06 Aug 2015 13:51:38 -0700
|
||||||
|
|
||||||
dumb-init (0.0.1) unstable; urgency=low
|
dumb-init (0.0.1) unstable; urgency=low
|
||||||
|
|
||||||
* Initial release.
|
* Initial release.
|
||||||
|
|
||||||
-- Chris Kuehl <ckuehl@yelp.com> Wed, 29 Jul 2015 15:39:11 -0700
|
-- Chris Kuehl <ckuehl@yelp.com> Thu, 06 Aug 2015 13:51:38 -0700
|
||||||
|
|
36
dumb-init.c
36
dumb-init.c
|
@ -31,12 +31,35 @@ void signal_handler(int signum) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void print_help(char *argv[]) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: %s COMMAND [[ARG] ...]\n"
|
||||||
|
"\n"
|
||||||
|
"Docker runs your processes as PID1. The kernel doesn't apply default signal\n"
|
||||||
|
"handling to PID1 processes, so if your process doesn't register a custom\n"
|
||||||
|
"signal handler, signals like TERM will just bounce off your process.\n"
|
||||||
|
"\n"
|
||||||
|
"This can result in cases where sending signals to a `docker run` process\n"
|
||||||
|
"results in the run process exiting, but the container continuing in the\n"
|
||||||
|
"background.\n"
|
||||||
|
"\n"
|
||||||
|
"A workaround is to wrap your script in this proxy, which runs as PID1. Your\n"
|
||||||
|
"process then runs as some other PID, and the kernel won't treat the signals\n"
|
||||||
|
"that are proxied to them specially.\n"
|
||||||
|
"\n"
|
||||||
|
"The proxy dies when your process dies, so it must not double-fork or do other\n"
|
||||||
|
"weird things (this is basically a requirement for doing things sanely in\n"
|
||||||
|
"Docker anyway).\n",
|
||||||
|
argv[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int signum;
|
int signum, exit_status, status = 0;
|
||||||
char* debug_env;
|
char *debug_env;
|
||||||
|
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
fprintf(stderr, "Try providing some arguments.\n");
|
print_help(argv);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,10 +93,13 @@ int main(int argc, char *argv[]) {
|
||||||
if (debug)
|
if (debug)
|
||||||
fprintf(stderr, "Child spawned with PID %d.\n", child);
|
fprintf(stderr, "Child spawned with PID %d.\n", child);
|
||||||
|
|
||||||
waitpid(child, NULL, 0);
|
waitpid(child, &status, 0);
|
||||||
|
exit_status = WEXITSTATUS(status);
|
||||||
|
|
||||||
if (debug)
|
if (debug)
|
||||||
fprintf(stderr, "Child exited, goodbye.\n");
|
fprintf(stderr, "Child exited with status %d, goodbye.\n", exit_status);
|
||||||
|
|
||||||
|
return exit_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
15
test
Executable file
15
test
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh -eux
|
||||||
|
run_tests() {
|
||||||
|
./test-proxies-signals
|
||||||
|
./test-exit-status
|
||||||
|
./test-help-message
|
||||||
|
}
|
||||||
|
|
||||||
|
cd tests
|
||||||
|
|
||||||
|
echo "Running tests in normal mode."
|
||||||
|
run_tests
|
||||||
|
|
||||||
|
echo "Running tests in debug mode."
|
||||||
|
export DUMB_INIT_DEBUG=1
|
||||||
|
run_tests
|
|
@ -2,7 +2,7 @@
|
||||||
# Print received signals into a file, one per line
|
# Print received signals into a file, one per line
|
||||||
file="$1"
|
file="$1"
|
||||||
|
|
||||||
. ./testlib.sh
|
. ./lib/testlib.sh
|
||||||
|
|
||||||
for i in $(catchable_signals); do
|
for i in $(catchable_signals); do
|
||||||
trap "echo $i > \"$file\"" "$i"
|
trap "echo $i > \"$file\"" "$i"
|
10
tests/test-exit-status
Executable file
10
tests/test-exit-status
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/sh -u
|
||||||
|
# dumb-init should exit with the same exit status as the process it launches.
|
||||||
|
for i in $(seq 0 255); do
|
||||||
|
status=$(dumb-init sh -c "exit $i"; echo $?)
|
||||||
|
|
||||||
|
if [ "$status" -ne "$i" ]; then
|
||||||
|
echo "Error: Expected exit status $i, got $status."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
18
tests/test-help-message
Executable file
18
tests/test-help-message
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh -u
|
||||||
|
# dumb-init should say something useful when called with no arguments, and exit
|
||||||
|
# nonzero.
|
||||||
|
|
||||||
|
status=$(dumb-init > /dev/null 2>&1; echo $?)
|
||||||
|
|
||||||
|
if [ "$status" -ne 0 ]; then
|
||||||
|
msg=$(dumb-init 2>&1 || true)
|
||||||
|
msg_len=${#msg}
|
||||||
|
|
||||||
|
if [ "$msg_len" -le 50 ]; then
|
||||||
|
echo "Error: Expected dumb-init with no arguments to print a useful message, but it was only ${msg_len} chars long."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Error: Expected dumb-init with no arguments to return nonzero, but it returned ${status}."
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -1,7 +1,9 @@
|
||||||
#!/bin/sh -eum
|
#!/bin/sh -eum
|
||||||
|
# dumb-init should proxy all possible signals to the child process.
|
||||||
|
|
||||||
# Try sending all signals via dumb-init to our `print-signals` script, ensure
|
# Try sending all signals via dumb-init to our `print-signals` script, ensure
|
||||||
# they were all received.
|
# they were all received.
|
||||||
. ./testlib.sh
|
. ./lib/testlib.sh
|
||||||
|
|
||||||
# The easiest way to communicate with the background process is with a FIFO.
|
# The easiest way to communicate with the background process is with a FIFO.
|
||||||
# (piping spawns additional subshells and makes it hard to get the right PID)
|
# (piping spawns additional subshells and makes it hard to get the right PID)
|
||||||
|
@ -9,7 +11,7 @@ fifo=$(mktemp -u)
|
||||||
mkfifo -m 600 "$fifo"
|
mkfifo -m 600 "$fifo"
|
||||||
read_cmd="timeout 1 head -n1 $fifo"
|
read_cmd="timeout 1 head -n1 $fifo"
|
||||||
|
|
||||||
dumb-init ./print-signals "$fifo" &
|
dumb-init ./lib/print-signals "$fifo" &
|
||||||
pid="$!"
|
pid="$!"
|
||||||
|
|
||||||
# Wait for `print-signals` to indicate it's ready.
|
# Wait for `print-signals` to indicate it's ready.
|
Loading…
Reference in a new issue