BT
x Share your thoughts on trends and content!

Yelp Releases dumb-init, a Minimal init System for Docker Containers

by on Jan 27, 2016 |

Yelp released a minimal init system for Docker containers called dumb-init which acts as a signal handling proxy and performs other init functions like reaping orphaned zombie processes. This tool can be specified in the Dockerfile before the command to be run and works by registering handlers for all signals that can be caught and forwarding them to the actual process running in the container.

Docker containers are usually used to run a single process which runs with process id (PID) of 1. In Unix and Linux based systems, the process with PID 1 - called init - has a special significance. It is spawned by the kernel and all other processes are spawned as its children. When any child terminates, it turns into a "zombie" process, since in Unix, parent processes are designed so that they have to explicitly wait for their children to terminate using the "waitpid" family of system calls. A child process will remain in a zombie state until the parent invokes waitpid (or any of its variants). This mechanism is called “reaping”.

In cases where the parent process itself terminates, the process with PID 1 adopts its children so they are not orphaned and can be reaped correctly. The kernel expects this functionality from the process which is running with PID 1.

In Docker, the process to run can be specified in two different ways in the Dockerfile - run it in a shell or run it directly. Inside the container, the PID 1 process which is either a shell running the process or the process itself, must take on the responsibilities of init. However, neither a shell nor most processes like web servers, databases are designed to do so. When running in a shell, any signals sent using the ‘docker signal’ command won’t be forwarded to the actual process, since the shell would not understand it. When running directly, the process usually won’t handle signals as init is supposed to do.

Due to such behaviour, the process running inside the container would not exit cleanly and behave as expected when a docker stop or a docker signal command is sent and perform the job of reaping orphaned zombie processes. This might lead to problems like

  • Filling up the operating system process table with zombie process entries due to which the OS might not be able to create new processes.
  • Data inconsistencies in the application if it’s using a database.
  • Containers created by CI systems such as Jenkins won’t be removed after the test suite run by Jenkins aborts.

dumb-init aims to solve these problems. It can be specified before the command to be run in the Dockerfile. It runs with PID 1 and handles all possible signals by registering signal handlers. It also takes care of reaping orphaned zombie processes.

There are existing tools that solve the same problem like Baseimage-docker and tini. On being asked about what makes dumb-init unique, Chris Kuehl, software engineer at Yelp said:

The most important difference with other tools  is that dumb-init is designed to be as transparent as possible. At Yelp we run our unit tests inside containers, which is great for ensuring a consistent environment between developer machines and CI. But we still want developers to be able to work with unit tests like any other non-Dockerized process. This means being able to drop into an interactive Python debugger (and give input on stdin), hit ^C to signal the process, etc. Ideally we want dumb-init to do as little as necessary to make the process it's watching behave just like it would outside of a container..

Kuehl also commented on the specific differences between existing tools and dumb-init:

runit (used by Phusion's init) and s6 are both inspired by daemontools, which is a very light-weight init system but is designed for spawning and supervising multiple processes. If you're running unit tests, you don't want your process supervised; if the tests exit with an error code, you want that error code returned to the user, not for the tests to be respawned. tini is very similar to dumb-init; the implementations are somewhat different but the behavior is almost the same.

Could dumb-init have been built on top of something existing like BaseImage? Kuehl said:

Baseimage is a good choice for some, but it doesn't work in all use cases. Again, the unit test suite is a good example - you don't want cron or syslog in that container, but baseimage will run them. You also lose the ability to target specific images, which means we can't easily run the tests against four different versions of Ubuntu.

According to the Yelp article, the primary motivation behind creating dumb-init was to make it easier for developers to move to containers. Kuehl elaborated on this:

dumb-init is really good for interactive use by engineers because it allows you to take a process which doesn't currently run inside Docker and move it into a container without changing its behavior. It's also important for non-interactive server processes because it ensures the process can be terminated cleanly, without, for example, leaving connections open to a database or dying in the middle of serving a request to a user.

Rate this Article

Relevance
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Try dockerfy instead by Mark Riggins

You may find my `dockerfy` utility useful for reaping zombies, handling signals properly, starting services, pre-running initialization commands before the primary command starts, editing configuration files via templates, overlaying content and managing secrets. github.com/markriggins/dockerfy

For example:
dockerfy --secrets-files /secrets/secrets.env
--template /app/nginx.conf.tmpl:/etc/nginx/nginx.conf
--wait 'tcp://{{ .Env.MYSQLSERVER }}:{{ .Env.MYSQLPORT }}' --timeout 60s
--run '/app/bin/migrate_lock' --server='{{ .Env.MYSQLSERVER }}:{{ .Env.MYSQLPORT }}' --password='{{.Secret.MYSQLPASSWORD}}' --
--start /app/bin/cache-cleaner-daemon --
--stderr /var/log/nginx/error.log
--reap
--user nobody --
nginx "-g daemon off;""

Would do the following:
- load secrets from a file,
- create an nginx.conf file from a template,
- wait up to 60 seconds for a mysql database to start accepting connections
- run a database migration against the mysql database, using a secret password
- start a service named 'cache-cleaner-daemon'
- start a go routine to reap zombies
- run nginx as user "nobody"
- tail the /var/log/nginx/error.log to stderr

If the database migration fails, then the container will exit without starting nginx. While nginx is running, if the cache-cleaner-daemon dies, the entire container will shut down so the cloud platform can start up another instance.
Hope this helps -- You can build from source or use a pre-built binary from my latest github release

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

1 Discuss
General Feedback
Bugs
Advertising
Editorial
Marketing
InfoQ.com and all content copyright © 2006-2016 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT

We notice you're using an ad blocker

We understand why you use ad blockers. However to keep InfoQ free we need your support. InfoQ will not provide your data to third parties without individual opt-in consent. We only work with advertisers relevant to our readers. Please consider whitelisting us.