diff --git a/.gitignore b/.gitignore index e2cf2df..86eb587 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ *.deb *.egg-info .tox +build/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..018e91b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +Contributing to dumb-init +======== + +`dumb-init` is primarily developed by [Yelp](https://yelp.github.io/), but +contributions are welcome from everyone! + +Code is reviewed using GitHub pull requests. To make a contribution, you should: + +1. Fork the GitHub repository +2. Push code to a branch on your fork +3. Create a pull request and wait for it to be reviewed + + +## Releasing new versions + +`dumb-init` uses [semantic versioning](http://semver.org/). If you're making a +contribution, please don't bump the version number yourself—we'll take care +of that after merging! + +The process to release a new version is: + +1. Run integration tests (`make itest`). Travis doesn't run these, so it's a + good idea to make sure they still pass. +2. Update the version in `setup.py` +3. Update the Debian changelog with `dch -v {new version}`. +4. Commit the changes and tag the commit like `v1.0.0`. +5. `git push --tags origin master` +6. Run `rm -rf dist && python setup.py sdist` to create a source distribution +7. Run `twine dist/*` to upload the new version to PyPI +8. Run `make builddeb` and upload the resulting Debian package to a new + [GitHub release](https://github.com/Yelp/dumb-init/releases) diff --git a/README.md b/README.md index dabe80c..008687a 100644 --- a/README.md +++ b/README.md @@ -5,31 +5,31 @@ 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. +containers and proxy signals to child processes. 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 +scripts in languages like Python, Ruby, or Bash, 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. +When processes are sent a signal on a normal Linux system, the kernel will +first check for any custom handlers the process has registered for that signal, +and otherwise fall back to default behavior (for example, killing the process +on `TERM`). -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, if the process receiving the signal is PID 1, it gets special +treatment by the kernel; if it hasn't registered a handler for the signal, the +kernel won't fall back to default behavior, and nothing happens. In other +words, if your process doesn't explicitly handle these signals, a `TERM` will +have no effect at all. -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. +A common example is CI 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 @@ -39,9 +39,33 @@ 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. +behave as you would expect. If your process dies, `dumb-init` will also die. -If your process dies, `dumb-init` will also die. + +### Process group behavior + +In its default mode, `dumb-init` establishes a process group rooted at the +child, and sends signals to the entire process group. This is useful if you +have a poorly-behaving child (such as a shell script) which won't normally +signal its children before dying. + +This can actually be useful outside of Docker containers in regular process +supervisors like [daemontools][daemontools] or [supervisord][supervisord] for +supervising shell scripts. Normally, a signal like SIGTERM received by a shell +isn't forwarded to subprocesses; instead, only the shell process dies. With +dumb-init, you can just write shell scripts with dumb-init in the shebang: + + #!/usr/bin/dumb-init /bin/sh + my-web-server & # launch a process in the background + my-other-server # launch another process in the foreground + +Ordinarily, a TERM sent to the shell would leave those processes running. With +dumb-init, your subprocesses will receive the same signals your shell does. + +If you'd like for signals to only be sent to the direct child, you can set the +environment variable `DUMB_INIT_PROCESS_GROUP=0` when running `dumb-init`. In this +mode, dumb-init is completely transparent; you can even string multiple +together (like `dumb-init dumb-init echo 'oh, hi'`). ## Installing inside Docker containers @@ -55,6 +79,9 @@ 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. +Debian packages are available from the [GitHub Releases tab][gh-releases], or +you can run `make builddeb` yourself. + ### Option 2: Installing the `.deb` package manually @@ -62,13 +89,20 @@ 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). +One possibility is with the following commands in your Dockerfile: + +```bash +RUN wget https://github.com/Yelp/dumb-init/releases/download/v0.1.0/dumb-init_0.1.0_amd64.deb +RUN dpkg -i dumb-init_*.deb +``` + ### Option 3: Installing from PyPI dumb-init can be installed [from PyPI](https://pypi.python.org/pypi/dumb-init) -using pip. Since dumb-init is written in C, you'll want to first install a C compiler -(on Debian/Ubuntu, `apt-get install gcc` is sufficient), then just `pip install -dumb-init`. +using pip. Since dumb-init is written in C, you'll want to first install a C +compiler (on Debian/Ubuntu, `apt-get install gcc` is sufficient), then just +`pip install dumb-init`. ## Usage @@ -88,3 +122,8 @@ humane signals like TERM. * [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) + + +[daemontools]: http://cr.yp.to/daemontools.html +[supervisord]: http://supervisord.org/ +[gh-releases]: https://github.com/Yelp/dumb-init/releases diff --git a/dumb-init.c b/dumb-init.c index 6c50baa..15d7f66 100644 --- a/dumb-init.c +++ b/dumb-init.c @@ -42,6 +42,9 @@ void print_help(char *argv[]) { fprintf(stderr, "Usage: %s COMMAND [[ARG] ...]\n" "\n" + "dumb-init is a simple process designed to run as PID 1 inside Docker\n" + "containers and proxy signals to child processes.\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" @@ -58,7 +61,7 @@ void print_help(char *argv[]) { "weird things (this is basically a requirement for doing things sanely in\n" "Docker anyway).\n" "\n" - "By default, dumb-init starts a process group and kills all processes in it.\n" + "By default, dumb-init starts a process group and signals all processes in it.\n" "This is usually useful behavior, but if for some reason you wish to disable\n" "it, run with DUMB_INIT_PROCESS_GROUP=0.\n", argv[0]