Announcing pid1 crate

Announces the pid1 crate and describes its usage

Introduction

The FP Complete team is pleased to announce the release of the pid1 crate, a Rust library for proper signal and zombie reaping of the PID 1 process.

When deploying an application in a containerized environment, you typically need to deploy a small init system to forward signals to your application and reap zombies. The pid1 crate provides a simple and easy-to-use solution to this problem.

Why

FP Complete already had a Haskell solution for this problem, which we used when deploying to containerized environments like Kubernetes or Amazon ECS.

Recently, we had a number of different Rust services in production for a client. At some point, we realized that some of these services did not have a proper init process to forward signals and reap zombie processes. In our experience, this is a common oversight when setting up Dockerized services.

We developed this crate because:

Once we had the crate, it was trivial to create the pid1 binary to use as a standalone mini init system if needed. If you are interested in the technical details of how software like pid1 works, you may find the following posts by Michael Snoyman interesting:

Using the crate

You must ensure that the launch method is the first statement in your main function:

use std::time::Duration;
use pid1::Pid1Settings;

fn main() {
    Pid1Settings::new()
        .enable_log(true)
        .launch()
        .expect("Launch failed");
    println!("Hello world");
    // Rest of the logic...
}

By default, logging is disabled, but enabling it can be helpful for the operations team. All logging is done to stderr.

Usage of binary

To use the binary, package it as part of your container and set it as the entrypoint. Example:

FROM alpine:3.14.2

ADD https://github.com/fpco/pid1-rs/releases/download/v0.1.0/pid1-x86_64-unknown-linux-musl /usr/bin/pid1

RUN chmod +x /usr/bin/pid1

ENTRYPOINT [ "pid1" ]

CMD ["/myapp"]

This executable can be used regardless of which language your application is written in, and so is a good option if your application is not written in Rust.

Note that you can also play with it locally. But unless executed with process id 1, it won't function itself as an init system. Example:

$ pid1 --env HELLO=WORLD printenv HELLO
WORLD

Note that we are support binaries for the following architectures which can be downloaded from Github releases:

For our internal and client usage, we're unlikely to use any of these apart from x86_64 and ARM64. We looked at the architectures supported by the tini tool and tried to support the same ones. Please let us know in the issues if we are missing a specific architecture.

It was very easy to cross-compile binaries with Rust using the cross tool.

Comparing to Haskell's pid1

Almost all of the production services at FP Complete have been using the Haskell pid1 binary, and now we have added one more choice to the equation.

Here are the major differences between the Rust and Haskell version:

Overall, we recommend using the Rust version if possible because of its smaller binary size and because Rust as a systems language is better suited for a tiny init system.

Binary size

This section compares the binary size of pid1 with various other implementation. Note that this comparison is not particularly meaningful because different systems have different features. It is simply intended to give a rough indication of the binary size of similar solutions:

Binary nameLanguageBinary sizeArchitectureLinking
pid1Rust658 KBx86Musl, Static
pid1Rust562 KBx86glibc, Dynamic
pid1Rust554 KBARM64Musl, Static
pid1Rust494 KBARM64glibc, Dynamic
pid1Haskell1.5 MBx86Musl, Static
tiniC43 KBx86Musl, Static
tiniC850 KBx86NA, Static
tiniC24 KBx86glibc, Dynamic
tiniC23 KBARM64glibc, Dynamic
tiniC534 KBARM64NA, Static

At a quick glance, the Haskell binary has the largest size, while the C binary tini has the smallest.

The initial Rust implementation used clap_lex to parse arguments to avoid dependencies. However, we later switched to clap to simplify the codebase. We noticed that using clap increased the binary size by around 160 KB, which we believe is justified by the additional features that clap offers.

Future

Please feel free to file issues if you think any feature is missing. One of the things we want to do is automate most of the integration testing using a crate like testcontainers. We have documented our manual integration tests, but it would nice to have them integrated as part of the CI process.