Before we try to introduce too many new ideas, we should acknowledge the fact that a container is foremost a Linux process. Therefore we should apply common standards and best practices for writing Unix tools, that now happen to be running in containers. These are React to signals, Use standard streams, Handle arguments:
A container should react to the signals which are sent to it. So our applications in the container should run in the foreground and catch signals and react appropriately.
Important signals to catch are:
SIGINT
: E.g. send by Ctrl-C to interrupt / stop a running container.SIGTERM
: Signals process termination e.g. send bydocker stop
. If necessary the container can do some final steps. After an usual grace period of 10 seconds the container is killed if it hasn't returned anymore
Example:
Catching SIGTERM
in Node.JS:
process.on('SIGTERM', function () {
console.log("Received SIGTERM. Exiting.")
server.close(function () {
process.exit(0);
});
});
https://github.com/luebken/currentweather/blob/master/server.js#L49
Docker best practices:
- Use the exec form to start you processes. Since it doesn't invoke a shell.
When a process exits it should return a proper exit code. The same is true for our container. This gives us a better overview of the context and the scheduler / init process better means of scheduling. E.g. in Kubernetes you can define that only failed containers should be restarted.
We generally just differ between the exit code 0
as a successful termination and something >0
as a failure. But other exit codes are also conceivable. For some inspiration, check out glibc or bash.
Example for exit codes: Return a non-failure exit code in Node.JS:
process.exit(0);
https://github.com/luebken/currentweather/blob/master/server.js#L52
Linux processes use standard streams as a means of communication. There are stdin
: standard input, stdout
: standard output and stderr
standard error:
-
stdout
: For all logging activities use stdout and let the infrastructure take care of handling this stream or forwarding it to some log aggregator. -
stdin
: If our container can be be conceived as a Unix tool we should accept data from stdin. This would allow piping between containers.
General best practices:
-
If you do have specific logging requirements you can use a side-car container to adapt your logs. See the composite patterns for details.
-
If your app writes to a file you could link that file to the device file:
RUN ln -sf /dev/stdout /var/log/nginx/access.log
https://github.com/nginxinc/docker-nginx/blob/master/stable/jessie/Dockerfile#L14
Arguments are a straightforward way to configure and send options to a container. We propose that you should handle these in a prescribed manner. And get your team to discuss and agree on a standard.
Luckily CLI arguments are a well defined topic so we refer to existing standards and libraries instead of re-inventing the wheel:
- POSIX.1-2008 Utility conventions
- In conjunction with getopt as utility to parse / validate.
- Libcs Program Argument Syntax Conventions
- The man page contains a good overview of signals.
- What makes an awesome CLI Application gives some inspiration.
- Signal handlers must be reentrant What happens when another signal arrives while this handler is still running?
- Self pipe trick Maintain a pipe for signals.
- In 12 factor apps: Treat logs as event streams.
- Also be aware of the PID 1 zombie reaping problem in Docker