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:
neteye.target
static.service
which will always be part of the targetdynamic.service
which will only be part of the target when a certain condition is metBetween 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.
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 service
s will have no effect whatsoever on the other ones, given that they are not bound by other constraints.