31. 03. 2020 Benjamin Gröber NetEye

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 Research & Development Team of the "IT System & Service Management Solutions" Business Unit of Würth Phoenix. I discovered my passion for Computers and Technology when I was 7 and got my first PC. Just using computers and playing games was never enough for me, so just a few months later, started learning Visual Basic and entered the world of Software Development. Since then, my passion is keeping up with the short-lived, fast-paced, ever-evolving IT world and exploring new technologies, eventually trying to put them to good use. I'm a strong advocate for writing maintainable software, and lately I'm investing most of my free time in the exploration of the emerging Rust programming language.

Author

Benjamin Gröber

Hi, my name is Benjamin, and I'm Software Architect in the Research & Development Team of the "IT System & Service Management Solutions" Business Unit of Würth Phoenix. I discovered my passion for Computers and Technology when I was 7 and got my first PC. Just using computers and playing games was never enough for me, so just a few months later, started learning Visual Basic and entered the world of Software Development. Since then, my passion is keeping up with the short-lived, fast-paced, ever-evolving IT world and exploring new technologies, eventually trying to put them to good use. I'm a strong advocate for writing maintainable software, and lately I'm investing most of my free time in the exploration of the emerging Rust programming language.

Leave a Reply

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

Archive