December 14, 2016·6 min read·engineeringdockercontainerdruntimesgovernance

Docker separates containerd from its application platform

The containerd announcement separates reusable container execution from Docker's platform, with an expanded runtime and neutral governance still to come.

Container platforms need a dependable process supervisor even when they disagree about how applications should be deployed. Docker's decision to expand containerd into an independent project addresses that shared requirement. A platform builder should be able to reuse container execution without adopting every decision made by the Docker platform around it. The difficult part is defining enough common behavior to make that reuse useful.

In today's announcement, Solomon Hykes says Docker is spinning out its core runtime functionality and plans to donate containerd to a neutral foundation early next year. The announcement also describes work still required for a broader runtime. It does not mark a completed foundation transfer or the release of containerd 1.0. That distinction matters when deciding which responsibilities a system can delegate today.

I value Docker principally for making application environments shippable through images and a repeatable build description. Separating the runtime is consistent with that preference. The tools that describe and distribute an application can remain useful while the machinery that executes it becomes available to other platforms.

The existing daemon already has a defined job

Containerd predates this announcement. Docker's April explanation of Engine 1.11 described its integration with runC and the separation of container execution from the rest of the engine. That architecture introduced distinct components beneath the familiar Docker commands. Today's proposal extends a division already present in released software.

The README for the containerd revision used by Docker 1.12.3 describes containerd as a daemon controlling runC. Its example starts a container using an identifier and the path of an OCI bundle. It then lists the resulting container and process. The input is already prepared for execution, which leaves image preparation outside that example's runtime call.

Docker's dependency manifest for 1.12.3 pins that containerd revision explicitly. This gives the existing implementation a concrete reference point. Reading its documentation avoids treating the newly announced scope as though every part had already shipped. A pinned runtime dependency is also a reminder that a product integrates a particular implementation, with particular behavior, rather than an architectural diagram.

The separation makes runtime behavior available for inspection on its own terms. A failure to prepare an application image and a failure to start an already prepared container are different failures. An API centered on the latter lets callers distinguish them before adding the broader image management responsibilities proposed today.

A bundle is an execution input

The existing bundle preparation instructions show the work that precedes execution. The example creates a directory containing a root filesystem and a configuration file. Docker pulls an image and creates a temporary container, then exports that container's filesystem into the bundle. The instructions use runc spec to produce an initial configuration and edit it for the desired application.

That preparation explains why a container execution API is not automatically an image distribution API. An image obtained from a registry still needs to become a local filesystem and process configuration. The existing example performs those steps outside containerd. The proposal to add image distribution therefore changes the amount of preparation that a higher-level platform must implement itself.

The bundle documentation also makes networking responsibility visible. Its Redis example removes the network namespace so the server can be reached through the host's network. That is an explicit configuration choice in a demonstration, not a general networking policy for an application platform. A runtime can execute the supplied configuration without deciding whether sharing the host network is appropriate for a particular service.

The useful abstraction is therefore quite concrete. A caller supplies a prepared directory and configuration that define what should run. Containerd manages execution through the configured runtime. Application deployment policy has to produce those inputs before execution can begin, even when the eventual image management implementation makes preparation more convenient.

The API exposes local process management

The existing protocol definition shows the responsibilities already represented in the interface. CreateContainerRequest includes a container identifier and bundlePath. It accepts paths for standard input, output and error, as well as runtime selection and arguments. The service also exposes operations for process creation, signals and resource updates.

State inspection returns containers with process information and status. Events use a streaming RPC and include a timestamp, container identifier and process identifier. A request can specify a timestamp and filter by container. Those details matter to a caller that must reconnect its view of execution with what is happening on a host.

The distinction between container identifiers and process identifiers is deliberate in the protocol. A request to add a process names the existing container and supplies a separate process identifier. A signal request can identify the process it targets. Higher-level software therefore has an interface for operations within an existing container, rather than only a single create-and-forget operation.

These observations describe the checked-in interface used by an existing Docker release. They do not establish the compatibility guarantees of the planned 1.0 API. They do show the kind of local execution work that competing platforms can sensibly share, with application-level decisions supplied by their callers.

Logging remains a caller decision

The standard input and output documentation explains a practical consequence of the local-host API. Clients provide filesystem paths for process I/O. Without explicit configuration, process streams go to /dev/null. A caller that wants durable logs must arrange where output goes.

The same mechanism supports attachment through named pipes. The command-line client creates FIFOs for the streams when attachment is requested, allowing it to read and write process I/O. Terminal attachment similarly requires the client to prepare a pseudo-terminal. The documentation explicitly assigns these choices to the consumer because logging requirements vary.

This is a useful boundary for an embeddable runtime. A platform can choose its log destination and forwarding behavior without requiring the execution daemon to implement that platform's logging service. The cost is that an integrator has actual work to do. Merely starting a process through the API does not supply the operational behavior that a complete Docker installation provides around it.

I prefer this explicit allocation of responsibility. A component is easier to assess when its caller must name the output paths than when the integration silently assumes a logging arrangement that the rest of the system does not provide.

The announcement explicitly includes parity between Linux and Windows hosts in its proposed scope. That requirement puts a limit on how narrowly a shared runtime can reflect one operating system. A common interface still needs implementations that accommodate the host underneath it. Image distribution and local storage must also connect to execution without requiring every consuming platform to copy the engine code that supplies them today. These are substantial additions to the existing daemon, which is why the distinction between the current interface and the announced roadmap affects actual integration work.

Neutral governance serves competing consumers

IBM's Phil Estes, writing as a seed maintainer in his announcement-day account, describes the governance concern directly. Docker is both an open source steward and a commercial competitor to other platforms. Independent governance is intended to let those platforms depend on the runtime without making its direction subordinate to one competing product.

Estes also commits IBM maintainer and contributor support. That gives the governance discussion an engineering implication. Shared ownership needs contributors who can help maintain the common implementation and evaluate changes against more than one consumer's requirements. A foundation destination alone would not supply that work.

For integrators, neutral governance and a reusable API solve related but distinct problems. The API determines what software can call. Governance determines how the project decides what that software will be able to call in subsequent revisions. An implementation can be technically reusable while its prospective consumers remain concerned about who decides its scope and schedule.

Today's proposal is attractive because it recognizes both dependencies. The existing runtime already demonstrates a separable execution component. Docker plans an expansion that aims to include more of the image and local storage work that callers currently arrange around it. Donation remains a planned step, and the broader implementation remains work to be delivered. For a platform builder assessing containerd today, the released execution interface and the proposed expanded runtime are two different things to evaluate.