systemd is a wonderful piece of software, that has liberated us from writing ugly bash-based init scripts and has given us features like a structured and unified log and one of the best local DNS resolvers on Linux.
In this post, I'm demonstrating how to integrate a service (also known as daemons on UNIX-like systems) written in Python into systemd to leverage some advanced features of the service manager.
What advanced features?
Classic init systems pretty much did one thing: Start up the system and it's services, and do a ordered shutdown. Apart from that, everything else had to be done by other services, which led to a ton of fragmentation in the Linux world, as every distribution had different ideas on how to do things.
systemd is advanced in the regard, that the entire lifecycle of a service is taken into account: Start, reload, status, monitoring and cleanup. But for all of this to work in sync, applications need to work together with the service manager and inform it about their internal state.
This is especially useful when you have multiple services that have dependencies on each other, and you want to make sure they're started and restarted in the right order.
For this, a C function called sd_notify is provided, but it's functionality is so simple that it can be implemented in any language with just a few lines of code.
The example application
For this example, I've created a simple FastAPI based webservice, that tells you the current time.
This post focuses on the systemd integration, and therefore excludes steps like package management and application deployment, as these vary a lot for each application.
To run this using systemd, you also need a simple service unit file:
In this case we assume that the application code is placed into
/home/server/app/, a virtualenv was created at
/home/server/.venv/, and a user named
server was created. Do not ever run your webservices as root!
Put this file at the place systemd is expecting it (
/etc/systemd/system/webserver.service) and enable and start it using the following commands:
After this is done, you can ask systemd about it's status:
If everything is correct, the output should look like this:
As you can see in the screenshot, modern versions of systemd can track all processes of our services and even the resources used like CPU and Memory.
systemd also has a ton of other useful features to increase the security of services by limiting their capabilities.
Building the basic blocks
Before we begin with the integration into the application, let's implement the communication with the service manager first. To communicate with it's services, systemd is providing a UNIX socket with the path provided in the environment variable
NOTIFY_SOCKET. You can connect to it and send simple text-based commands.
As FastAPI is at it's core an async framework, we also write our integration using asyncio.
While there could be ways to make this more efficient (like caching and re-using the socket), this function is never on a critical path in the application, therefore we don't need to bother with this aspect.
Next, let's implement the commands, which are all one-liners. The comments for each function were taken from the systemd documentation.
Now, we can integrate these functions into our service:
In a real application, you could for example report some basic statistics like requests per second to give a hint about the service health.
As last thing, we need to tell systemd that we want it to expect these commands:
After re-deploying and restarting, you'll see more information in the status output:
After the application has successfully initialized, the state is changed and the status message also displayed:
By adding just a few lines of code, you made the life of administrators managing your applications a bit easier.