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.
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:
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.
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.
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.
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 name | Language | Binary size | Architecture | Linking |
---|---|---|---|---|
pid1 | Rust | 658 KB | x86 | Musl, Static |
pid1 | Rust | 562 KB | x86 | glibc, Dynamic |
pid1 | Rust | 554 KB | ARM64 | Musl, Static |
pid1 | Rust | 494 KB | ARM64 | glibc, Dynamic |
pid1 | Haskell | 1.5 MB | x86 | Musl, Static |
tini | C | 43 KB | x86 | Musl, Static |
tini | C | 850 KB | x86 | NA, Static |
tini | C | 24 KB | x86 | glibc, Dynamic |
tini | C | 23 KB | ARM64 | glibc, Dynamic |
tini | C | 534 KB | ARM64 | NA, 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.
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.
Subscribe to our blog via email
Email subscriptions come from our Atom feed and are handled by Blogtrottr. You will only receive notifications of blog posts, and can unsubscribe any time.