SystemdSince v1.18.0
Introduction
The systemd provider starts units when a request needs them, and stops them when their session expires. Sablier runs on the same host and talks to the service manager over its D-Bus API. It reads its settings from an [X-Sablier] section in each unit file.
Install Sablier
Run Sablier on the host, as a service of the manager whose units it controls. Sablier reads each unit file at the path systemd reports, so it needs the host’s file system.
Download the binary from the releases page. The command fetches the amd64 build. On ARM, use arm64 or armv7 instead:
curl -fsSL https://github.com/sablierapp/sablier/releases/download/v1.19.0/sablier-1.19.0-linux-amd64.tar.gz \
| sudo tar -xz -C /usr/local/bin sablierSelect the systemd provider in /etc/sablier/sablier.yaml:
provider:
name: systemd
systemd:
# Optional: only consider units whose name matches one of these patterns.
# Remove to consider every loaded unit.
unit-patterns:
- "*.service"
server:
port: 10000Run Sablier as a system service:
[Unit]
Description=Sablier
Documentation=https://sablierapp.dev/providers/systemd/
Wants=network-online.target
After=network-online.target
[Service]
# Runs as root: starting and stopping system units needs that privilege.
ExecStart=/usr/local/bin/sablier start --configFile /etc/sablier/sablier.yaml
Restart=on-failure
[Install]
WantedBy=multi-user.targetsudo install -D -m 0644 sablier.yaml /etc/sablier/sablier.yaml
sudo install -m 0644 sablier.service /etc/systemd/system/sablier.service
sudo systemctl daemon-reload
sudo systemctl enable --now sablier.serviceSablier connects to the system D-Bus. Starting, stopping and throttling units needs root, or a polkit rule that grants org.freedesktop.systemd1.manage-units.
Reverse proxy plugins on the same host reach Sablier at http://localhost:10000.
Usage example
Opt a unit in with an [X-Sablier] section, and give it a group:
[Unit]
Description=Sablier demo service (systemd provider)
[Service]
ExecStart=/usr/bin/sleep infinity
[X-Sablier]
Enable=true
Group=demo
[Install]
WantedBy=default.targetInstall it, reload systemd, and enable the unit without starting it:
sudo install -m 0644 demo.service /etc/systemd/system/demo.service
sudo systemctl daemon-reload
sudo systemctl enable demo.serviceAn enabled unit stays loaded, so Sablier finds it in its group. With the user instance, install it in ~/.config/systemd/user/ and use systemctl --user without sudo.
Then configure your reverse proxy to call Sablier for the demo group in front of your service. On the first request, Sablier starts demo.service and holds the request, or shows a waiting page, until systemd reports the unit active. When the session expires, Sablier stops it.
To try it without a reverse proxy, request the group from the Sablier API:
curl "http://localhost:10000/api/strategies/blocking?group=demo&session_duration=1m"Provider reference
Configuration
| Option | Default | Description |
|---|---|---|
--provider.systemd.user-instance | false | Manage the units of the user manager of the user Sablier runs as, instead of the system manager. |
--provider.systemd.unit-patterns | [] | Only consider units whose name matches one of these glob patterns, such as app-*.service. |
Every --provider.* option that is not provider-specific also applies; see the CLI reference.
unit-patterns narrows which units Sablier lists and watches:
- A matching unit still needs
Enable=true. Patterns do not opt units in. - Patterns make listing cheaper. systemd filters the units, so Sablier reads fewer unit files.
- Patterns do not filter requests by instance name. A request for a unit outside the patterns still starts it if it has
Enable=true.
Workloads
Units
Sablier starts and stops units through the manager’s D-Bus API, as systemctl start and systemctl stop do. Most of the time, the unit is a service.
- Start applies the active resource limits, if any, then starts the unit. Sablier waits until systemd finishes the start, and reports an error if it fails.
- Stop stops the unit and waits until systemd finishes. With
IdleReplicas=1, Sablier applies the idle limits instead and leaves the unit running.
Instance names
A reverse proxy can target one unit by name instead of a group. The unit of the usage example is named:
demo.service- Use the full unit name, with its suffix. Sablier sends the name to systemd as is: unlike
systemctl, it does not add.service. - The name refers to the manager Sablier talks to. With
user-instance, it is a unit of the user manager.
Labels
Systemd units have no labels. Sablier reads its settings from an [X-Sablier] section in the unit file:
[X-Sablier]
Enable=true
Group=demo- Use the exact key names below. Keys are case-sensitive, and Sablier ignores any other key.
- systemd ignores the section. It skips sections whose name starts with
X-, so the unit loads without warnings. - Set each key once. If a key repeats, the last value wins. List several values on one line, separated by commas.
- Put the section in the unit file itself. Sablier reads the file systemd reports as the unit’s
FragmentPath, never its drop-ins. - Sablier picks up
[X-Sablier]changes on its own. It reads the unit file again when the file changes. Runsystemctl daemon-reloadfor changes to the other sections.
The key names do not follow the labels’ casing, so use this table:
| Label | [X-Sablier] key |
|---|---|
sablier.enable | Enable |
sablier.group | Group |
sablier.ready-after | ReadyAfter |
sablier.ready-on-start | ReadyOnStart |
sablier.running-hours | RunningHours |
sablier.running-days | RunningDays |
sablier.anti-affinity | AntiAffinity |
sablier.idle.replicas, sablier.active.replicas | IdleReplicas, ActiveReplicas |
sablier.idle.cpu, sablier.active.cpu | IdleCPU, ActiveCPU |
sablier.idle.memory, sablier.active.memory | IdleMemory, ActiveMemory |
sablier.idle.blkio-weight, sablier.active.blkio-weight | IdleBlkioWeight, ActiveBlkioWeight |
sablier.idle.blkio-weight-device, sablier.active.blkio-weight-device | IdleBlkioWeightDevice, ActiveBlkioWeightDevice |
sablier.idle.blkio-device-read-bps, sablier.active.blkio-device-read-bps | IdleBlkioDeviceReadBps, ActiveBlkioDeviceReadBps |
sablier.idle.blkio-device-write-bps, sablier.active.blkio-device-write-bps | IdleBlkioDeviceWriteBps, ActiveBlkioDeviceWriteBps |
sablier.idle.blkio-device-read-iops, sablier.active.blkio-device-read-iops | IdleBlkioDeviceReadIOps, ActiveBlkioDeviceReadIOps |
sablier.idle.blkio-device-write-iops, sablier.active.blkio-device-write-iops | IdleBlkioDeviceWriteIOps, ActiveBlkioDeviceWriteIOps |
The label reference describes every setting.
Workload settings
Each excerpt below is the [X-Sablier] section of a complete unit file in examples/systemd.
EnableSince v1.18.0
Enable=true opts the unit in. It is required, and any other value is ignored. Sablier refuses to start or stop a unit without it, so a request cannot control unrelated host services. See the usage example.
GroupSince v1.18.0
Group puts the unit in one or more groups, separated by commas. A session for any of them starts it. Without it, the unit is in the default group.
[X-Sablier]
Enable=true
Group=team-a,team-bReady afterSince v1.18.0
ReadyAfter keeps reporting the unit as not ready for a settling delay after systemd first reports it active. The value is a Go duration, such as 30s.
[X-Sablier]
Enable=true
Group=ready-after
ReadyAfter=30sUse it when your service is active before it can serve traffic. With Type=simple or Type=exec, a service is active as soon as its process starts.
Learn more about delaying readiness.
Ready on startSince v1.18.0
ReadyOnStart=true reports the unit ready as soon as Sablier asks systemd to start it, without waiting for it to be active.
[X-Sablier]
Enable=true
Group=ready-on-start
ReadyOnStart=trueLearn more about marking ready on start.
Running hoursSince v1.18.0
RunningHourskeeps the unit running during a daily window.RunningDayslimits that window to some weekdays.
[X-Sablier]
Enable=true
Group=running-hours
RunningHours=09:00-18:00
RunningDays=Mon,Tue,Wed,Thu,FriThe window uses Sablier’s local time: the host’s time zone, unless you set TZ in the Environment= of Sablier’s unit.
Learn more about running hours.
Anti-affinitySince v1.18.0
AntiAffinity lists groups the unit backs off from, separated by commas. Sablier keeps it idle while any of them has an active session.
[X-Sablier]
Enable=true
Group=batch
AntiAffinity=streamingLearn more about anti-affinity.
Scale instead of stopSince v1.18.0
Keep the unit running when idle, with lower limits, instead of stopping it:
IdleReplicas=1keeps the unit running when idle.IdleCPUandIdleMemoryset its CPU and memory limits while idle.ActiveCPUandActiveMemoryset them for the next session.
[X-Sablier]
Enable=true
Group=scale-mode
# Keep the unit running when the session expires.
IdleReplicas=1
# CPU in cores: 0.1 is CPUQuota=10%, 2 is CPUQuota=200%.
IdleCPU=0.1
ActiveCPU=2
# Memory in Docker units, applied as MemoryMax=.
IdleMemory=64m
ActiveMemory=512mSablier sets the limits as resource control properties of the unit:
- CPU is in decimal cores and becomes
CPUQuota=.0.1is 10%,2is 200%. - Memory uses Docker units and becomes
MemoryMax=. For example,64mor1g. - The running unit keeps running. systemd applies the new limits immediately, without a restart.
- Limits are runtime properties. Sablier sets them as
systemctl set-property --runtimedoes: your unit file does not change, and a reboot drops them. - A limit set on one side only is reset on the other. If you set
IdleMemoryalone, the next session setsMemoryMax=infinity, and the next expiry sets it back. If your unit file sets a limit, repeat it in the matchingActivekey. - Active limits apply on every start, even when
IdleReplicasis0and the unit is stopped when idle. - One unit runs at most.
IdleReplicasandActiveReplicasabove1count as1, and Sablier logs a warning.
Learn more about scaling instead of stopping.
Block I/OSince v1.18.0
With IdleReplicas=1, throttle the unit’s block I/O while idle:
[X-Sablier]
Enable=true
Group=blkio
# Keep the unit running when the session expires.
IdleReplicas=1
# Relative weight (10-1000), applied as IOWeight=.
IdleBlkioWeight=100
ActiveBlkioWeight=500
# Per-device limits: path:value, comma-separated for several devices.
IdleBlkioDeviceReadBps=/dev/sda:10m
IdleBlkioDeviceWriteBps=/dev/sda:10m
IdleBlkioDeviceReadIOps=/dev/sda:100
IdleBlkioDeviceWriteIOps=/dev/sda:100Each key pair sets one resource control property:
| Keys | systemd property | Value |
|---|---|---|
IdleBlkioWeight, ActiveBlkioWeight | IOWeight= | Integer from 10 to 1000 |
IdleBlkioWeightDevice, ActiveBlkioWeightDevice | IODeviceWeight= | path:weight, weight from 10 to 1000 |
IdleBlkioDeviceReadBps, ActiveBlkioDeviceReadBps | IOReadBandwidthMax= | path:rate, rate in Docker units |
IdleBlkioDeviceWriteBps, ActiveBlkioDeviceWriteBps | IOWriteBandwidthMax= | path:rate, rate in Docker units |
IdleBlkioDeviceReadIOps, ActiveBlkioDeviceReadIOps | IOReadIOPSMax= | path:iops, iops an integer |
IdleBlkioDeviceWriteIOps, ActiveBlkioDeviceWriteIOps | IOWriteIOPSMax= | path:iops, iops an integer |
- Separate devices with commas. For example,
/dev/sda:10m,/dev/sdb:5m. - Sablier ignores a weight outside
10-1000. systemd accepts up to10000, and its default is100. - Rates use Docker units, in bytes per second.
10mis 10 MiB/s. - Omitted per-device limits are cleared on the switch. Sablier replaces the whole device list, so the idle limits above are removed when a session starts.
- These are runtime properties, like the CPU and memory limits.
Learn more about throttling block I/O.
Readiness
Sablier reports a unit from its ActiveState:
- ready when it is
active. - starting when it is
activating,reloadingorrefreshing. - stopped when it is
inactive,deactivatingormaintenance. - error when it is
failed.
When a service becomes active depends on its Type=. With Type=notify, it is active once it reports ready through sd_notify. With Type=simple, it is active as soon as its process starts: add a ready-after delay if it needs time to serve traffic.
Limitations
- Sablier must run on the host of the units it manages: it reads their unit files from disk.
- One Sablier manages either the system manager or one user manager, not both.
- Groups only contain units systemd has loaded. Enable a unit so it stays loaded.
- Sablier ignores
[X-Sablier]sections in drop-in files. - At most one instance of a unit runs: replica values above
1count as1. - Resource limits need the unified cgroup hierarchy. On a user instance, they only apply for the controllers delegated to the user manager.
- Sablier polls systemd every 10 seconds for units started or stopped outside Sablier.
Examples
examples/systemd: every unit file on this page, with aMakefileto run them on your user instance.