Network exec in systemd
Today I’m investigating an odd circular dependency in systemd units in openstack-ansible. One of the units under investigation implements a really interesting way to run database checks. I decided to dig deeper to better understand that technique.
Tl;dr; systemd.socket
activates unit of unit@.service
template which in turn run a binary pass active tcp stream from a socket to stdin/stdout of that unit, implementing something like inetd/cgi-bin.
Side note: I’ve known that systemd can create speculative services, and I’ve known, it can run stuff like inetd
can, but I never paid attention to it.
Exercise: we will create a network service reporting current date by running date
(/usr/bin/date
) command.
Template
/etc/systemd/system/date@.service
[Service]
Type = oneshot
ExecStart = -/usr/bin/date
StandardOutput = socket
It’s really trimmed down to essentials, which is ‘what to run’, ‘use socket’ and ‘type’. Note, there is no ‘[install]’ section.
Magic is in the StandardOutput
option
socket
connects standard output to a socket acquired via socket activation.
Note, we don’t need stdin, because date
does not read from it anything.
Socket
/etc/systemd/system/date.socket
[Unit]
After=network.target
[Socket]
ListenStream=127.0.0.1:4242
Accept=yes
[Install]
WantedBy=sockets.target
The magic is in Accept
stanza.
If yes, a service instance is spawned for each incoming connection and only the connection socket is passed to it
Enablement
# systemctl enable date.socket
Created symlink /etc/systemd/system/sockets.target.wants/date.socket → /etc/systemd/system/date.socket.
# systemctl start date.socket
Verification
# ss -lnpt | grep 4242
LISTEN 0 4096 127.0.0.1:4242 0.0.0.0:* users:(("systemd",pid=1,fd=66))
# echo | nc 127.0.0.1 4242
Wed 11 Jan 2023 12:27:39 PM UTC
(echo
before nc
is needed to close connection, it’s netcat-specific thing).
Applications
Outside of creating silly http servers, the main application, as I see it, is to use to for periodic remote execution. Nagios has a dedicated NRPE server for that.
If you have some rarely scraped exporter (like domain expiration), running it as stdin/stdout unit under systemd supervision may be efficient way to reduce memory/process footprint. You still need to have a good http implementation on stdin/stdout (or can we just mock it with prints? Interesting thinking…).