« Dockerinit and Dead Code
docker
free software
programming
suse
21 January 2016
The one thing that separates dead code from other types of code is that it doesn’t (or more correctly, shouldn’t) cause bugs. Like vestigial organs, you don’t lose much by having dead code. However, what it does cause is nothing but confusion. Most developers assume the person who wrote the software knew what they were doing at the time (okay, that’s probably a bit too strong, maybe they assume the person at least thought about what they were doing). The point is, a developer that is new to a project won’t assume that any chunk of code is dead code. All code “must have a purpose”, which is why it is dangerous to have dead code lying around in projects. Not to mention that the “developer new to the project” might be you in a few months after you’ve come back from doing something else.
Dead code increases confusion, and dockerinit
is a perfect example of this. It
is such an old piece of code that has survived all of the large changes in Docker,
and has been part of Docker since 0.0.3
(it was introduced in this commit).
If you’re running Docker on any modern distribution, you’ll find this binary
tucked away in somewhere like /usr/lib/docker/dockerinit
. It’s been there since
February 2013, and it hasn’t been used since everyone started using the native
execdriver (known as libcontainer
, or runc
to her
friends).
While technically LXC support hasn’t been removed until 1.10
is released, it’s
a well-known fact that using LXC with Docker should be filed under “exceptionally
bad idea that should never be a thing you do”. The native
execdriver came out
in 0.10
(early 2014) and has been the execdriver of choice since its initial
release. Thus, this code hasn’t really been used for about 2 years, which is
quite a long time for a project that became free software only 3 years ago.
LXC and libcontainer
LXC is allegedly an initialism for “Linux Containers” (hint: it isn’t). At the
time it was the only container runtime, and it was quite rudimentary. You could
define a container, start the container, put more processes in the container, etc.
All of this was managed by a bunch of small C programs (like lxc-start
,
lxc-stop
, etc), and you had to write very complicated configuration files in
order to set up a container properly (it didn’t have safe defaults, or any defaults
at all). It didn’t manage any of the packaging of containers, management of how
container metadata should be stored, or any other higher-level features Docker
offers.
To be fair, that was LXC’s intention. They wanted to create a low-level and very
configurable runtime for containers, and left all of the other stuff up to the
users. Unfortunately, when Docker came along and had to use LXC, this made things
quite difficult. Scattered throughout the git
history are references to
“horrible hacks for LXC”, of which there are a fair number. dockerinit
is a
perfect example of one of these hacks. But we’ll get to that later.
libcontainer
was Docker’s response to LXC, after having wrangled with it for
almost a year at that point. The big issue that Docker had with using LXC was that
it was hard to work with in an automated fashion (such as required by Docker)
without requiring the user to answer a bunch of really detail questions. The
configuration for LXC is just a stream of configuration options which you have
to specify in excruciating detail. Any misconfiguration could cause issues
with security or stability. This was clearly an issue, and the Docker community
resolved to fix it before Docker was declared “production-ready” in 1.0.0
.
In many ways, libcontainer
is a Docker-aware LXC. It is essentially a set of
language bindings that take very minimal JSON
configuration payloads and deal
with all of the nastiness of actually creating a container. This allowed Docker
to have control over what features would be supported and how it would fit into
Docker.
dockerinit
As I mentioned above, the LXC execdriver was well known for the amount of hacks required to get Docker to run containers. After all of the horrid configuration templating and slaying of kittens, Docker had nothing left up its sleeves to do the final configuration steps required for LXC. This includes small things like “setting up networking” and “changing the user”, since LXC couldn’t do it in a way that Docker felt was necessary.
dockerinit
was a hack to solve this problem. Rather than starting the process
the user wanted, Docker would first start dockerinit
in the container which
would “clean the environment” and then actually start the container process. At
first, dockerinit
was just a function inside the docker
binary which would
be executed if the execname was /sbin/init
. Why /sbin/init
? Because Docker
would bind-mount itself into /sbin/init
inside the container so it could execute
itself. This caused quite a few bugs with Ubuntu and Debian, so they resolved to
bind-mount to a path that wasn’t a real binary – like /.dockerinit
.
After a while, people were annoyed with there being a three-fold use to the
docker
binary. It was the client, the server and dockerinit
. So, dockerinit
was separated into a separate binary, but it was vital that the two binaries be
built from the same codebase. So they did a bunch of things to try to fix this
(embedding the sha1sum
of the dockerinit
binary inside the docker
binary
during compile-time and checking it at runtime was the weirdest). In addition,
packagers were warned that you must always package dockerinit
and docker
as one thing.
Over time, the amount of code in dockerinit
dwindled. The transient file
/.dockerinit
stayed in every running container for the next few years, and
some people even used it to figure out whether they were in a container. In
general, the code was dead (except in the rare case when LXC was used, which
hasn’t been common in a long time). But the code lay in the source tree under
the name dockerinit/dockerinit.go
, with references to it scattered around.
The pull request I’ve opened to actually remove dockerinit
, once and for all,
can be found here. Hopefully it’ll be merged for the release
after 1.10
, and we’ll be able to finally say goodbye to this old hack. I’m kinda
annoyed that the patch isn’t all -
s, but I needed to update some comments. I
haven’t actually added any instructions in that pull request, which just feels
great.
The Actual Point
So, why bring up this weirdly specific hack which I’m hoping to get removed soon? Surely if the code is going to be gone, there’s nothing to be said. Well, I think there’s something to be learned here about software and dead code. Software is odd in the fact that running software doesn’t generate heat or attract mass or make a noise. So vestigial components of software usually go unnoticed, and that’s bad because people who don’t know about the inner workings of the code won’t realise that a particular part is dead code and just needs to be removed.
But while dead code doesn’t cause any problems when your software has already been packaged and arrives on a silver platter, the maintainers are really the ones who have to deal with the code. While dead code doesn’t cause bugs, it may cause dependencies or other horrible issues.
So, if you’re maintaining or contributing to a large project, take a step back and ask yourself “is there anything here that we don’t actually need”. Because dead code is much more frustrating to deal with than just removing it.
Unless otherwise stated, all of the opinions in the above post are solely my own and do not necessary represent the views of anyone else. This post is released under the Creative Commons BY-SA 4.0 license.
Want to keep up to date with my posts?
You can subscribe to the Atom Feed.