31. 03. 2020 Benjamin Gröber Uncategorized

How to Couple systemd Services with a systemd Target Dynamically

When designing complex systems with a multitude of services, more often than not some of them logically form loosely coupled units. Usually these services should remain independent to a degree, i.e. free to be started or stopped independently. However, we also wanted a mechanism to command the loosely coupled unit as a whole. Sometimes however the units are not static, such that it is not possible to define such a unit beforehand.

NetEye 4 is one of these systems. Depending on the type of subscription, it will utilize different sets of services. It is based on CentOS 7, which comes with systemd as the init system, which we want to use to start and/or stop one of these units dynamically,

Systemd offers high configurability, which comes with the tradeoff of increased complexity. Here’s what we want to achieve:

  • A systemd target unit called neteye.target
  • A systemd service unit called static.service which will always be part of the target
  • A systemd service unit called dynamic.service which will only be part of the target when a certain condition is met
  • The service should start and stop whenever the target starts and stops, but never vice-versa

Challenge 1: Getting a Service to Start and Stop with Its Target

Between Wants=, Requires=, Requisite=, BindsTo= and PartOf= as possible candidate configurations, we were a little startled at first. The most challenging part to achieve was that the “parent” target should not be stopped along with the “child” service.

After a bit of testing, we hit upon the following combination to achieve exactly that:

[Unit]
Name=neteye.target
Wants=static.service

[Unit]
Name=static.service
PartOf=neteye.target

The Wants= ensures the start of the service without affecting the target on restarts/stops, while the PartOf is responsible for stopping the service whenever the target is stopped explicitly.

Challenge 2: Again, but Dynamically

As you can see above, this is no big deal whenever you can directly edit the systemd configuration files. However we did not want to modify any configuration files for existing services, as this would mean additional maintenance effort, and the risk of confusion between user changes and our changes.

The solution was simple once we found it: systemd supports a “PlugIn” arrangement, where additional properties can be set, or existing ones can be overridden, by placing them in a special ${ServiceName}.service.d/ directory. This is especially nice for “system” services like rsyslog which can be bound to NetEye using this technique.

This is the full example as before, split per file:

# File ./neteye.target
[Unit]
Name=neteye.target
Wants=static.service
# File ./static.service
[Unit]
Name=static.service
PartOf=neteye.target

Running the command systemctl list-dependencies neteye.target will give the following output:

[root@neteye-test ~]# systemctl list-dependencies neteye.target
neteye.target
● └─static.service

Now we want to add the dynamic.service to NetEye, without modifying any of the above files. Assuming that we also do not want to touch the service file, we can do the following:

# File ./dynamic.service
[Unit]
Name=dynamic.service
# File ./neteye.target.d/dynamic.service.conf
[Unit]
Wants=dynamic.service
# File ./dynamic.service.d/neteye.target.conf
[Unit]
PartOf=neteye.target

After reloading the systemd configuration with the systemctl daemon-reload command, systemctl list-dependencies neteye.target shows the following:

[root@neteye-test ~]# systemctl list-dependencies neteye.target
neteye.target
● ├─dynamic.service
● └─static.service

Now whenever we start or stop the neteye.target, both dynamic.service and static.service will follow. However any action on one of the services will have no effect whatsoever on the other ones, given that they are not bound by other constraints.

Benjamin Gröber

Benjamin Gröber

R&D Software Architect at Wuerth Phoenix
Hi, my name is Benjamin and I'm Software Architect in the System Integration Research & Development Team at Wuerth Phoenix. I discovered my passion for Computers and Technology when I got my first PC shortly after my 7th birthday in 1999. Using computers and playing with them soon got boring and so, just a few years later, I taught myself Visual Basic and entered the world of Software Development. Since then I loved trying to keep up with the short lived, fast evolving IT world and exploring new technologies, eventually putting them to good use. Lately I'm investing my free time in the relatively new languages Go and Rust.

Author

Benjamin Gröber

Hi, my name is Benjamin and I'm Software Architect in the System Integration Research & Development Team at Wuerth Phoenix. I discovered my passion for Computers and Technology when I got my first PC shortly after my 7th birthday in 1999. Using computers and playing with them soon got boring and so, just a few years later, I taught myself Visual Basic and entered the world of Software Development. Since then I loved trying to keep up with the short lived, fast evolving IT world and exploring new technologies, eventually putting them to good use. Lately I'm investing my free time in the relatively new languages Go and Rust.

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive