Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FC-40738] systemd: init ScalableService component #196

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/batou_ext/resources/scalable-service.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{ lib, ... }:

let
numRunningInstances = lib.toInt "{{ component.running_instances }}";
baseName = "{{ component.base_name }}";
in {
systemd.targets."${baseName}" = {
wantedBy = [ "multi-user.target" ];
# If this target is reached (i.e. started), also start
# {{ component.running_instances }} messenger services.
wants = lib.genList (n: "${baseName}@${toString n}.service") numRunningInstances;
};

systemd.services."${baseName}@" = {
# If the target stops or gets restarted, also stop/restart this unit.
partOf = [ "${baseName}.target" ];
};
}
83 changes: 83 additions & 0 deletions src/batou_ext/systemd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import pkg_resources
from batou.component import Attribute, Component
from batou.lib.file import File


class ScalableService(Component):
"""
Configure a systemd unit that can have multiple equal instances, such
as multiple consumers for a message queue.

Usage::

self += ScalableService("message-consumer", running_instances=5)
self.unit_identifier = self._.unit_identifier
self += File("/etc/local/nixos/message-consumer-settings.nix")
self += Rebuild()

With a `message-consumer-settings.nix` looking like this:

{
systemd.services."{{ component.unit_identifier }}" = {
serviceConfig = {
/* ExecStart etc. */
};
};
}

This creates a systemd target called `message-consumer.target`
and a template service called `[email protected]`. When
the configuration gets activated, five instances of this service
are started, namely `[email protected]` to
`[email protected]`. When the machine gets rebooted,
these five instances will be started automatically.

The number of instances running by default can be changed with the
`running_instances` attribute.

Please note that the unit is completely empty and thus invalid. A rebuild
must not happen before `message-consumer-settings.nix` in the example above
is added that fully configures the service.

If needed, more services from this template can be started like this:

$ systemctl start message-consumer@{5..23}

This starts 19 additional units.

When running

$ systemctl restart message-consumer.target

all 24 running units will be restarted.

When running

$ systemctl stop message-consumer.target

all 24 running units will be stopped.

However, when running

$ systemctl start message-consumer.target

after that, only 5 units will get back up. The same applies to an
additional deployment or a reboot of the VM.

Hence, starting additional services is a temporary measure to quickly
get more workers up. The change must be persisted in the deployment
after that.
"""

namevar = "base_name"
running_instances = Attribute(int, 4)

def configure(self):
self += File(
f"/etc/local/nixos/scale-{self.base_name}.nix",
content=pkg_resources.resource_string(
"batou_ext", "resources/scalable-service.nix"
),
)

self.unit_identifier = f"{self.base_name}@"
Loading