I plan to host several instances of the same web app for customers using systemd. I would like to be able to stop and start each customer instance using systemd, as well as treating the whole collection of customer instances as a single service that can be stopped and started together.

systemd seems to provide the building blocks I need using PartOf, and template unit files, but went I stop the parent service, the child customer service is not stopped. How can I make this work with systemd? Here's what I have so far.

The parent unit file, app.service:

[Unit]
Description=App Web Service

[Service]
# Don't run as a deamon (because we've got nothing to do directly)
Type=oneshot
# Just print something, because ExecStart is required
ExecStart=/bin/echo "App Service exists only to collectively start and stop App instances"
# Keep running after Exit start finished, because we want the instances that depend on this to keep running
RemainAfterExit=yes
StandardOutput=journal

A unit template file named app@.service, used to create customer instances:

[Unit]
Description=%I Instance of App Web Service

[Service]
PartOf=app.service
ExecStart=/home/mark/bin/app-poc.sh %i
StandardOutput=journal

My app-poc.sh script (Proof of concept that just prints to log file in a loop):

#!/bin/bash
# Just a temporary code to fake a full daemon.
while :
do
  echo "The App PoC loop for $@"
  sleep 2;
done

For the proof of concept, I've got the systemd unit files in ~/.config/systemd/user.

I then start up the parent and an instance based on the template (after systemctl --user daemon-reload ):

systemctl --user start app
systemctl --user start app@customer.service

From using journalctl -f I can see that both started and that the customer instance continues to run. Now I I expect shutting down the parent will stop the child (because I used PartOf), but it doesn't. Also, starting the parent isn't starting the child as expected either.

systemctl --user stop app

Thanks!

(I'm using Ubuntu 16.04 with systemd 229).

  • 1
    "PartOf= Configures dependencies similar to Requires=, but limited to stopping and restarting of units." If you want starting to work, don't you need to use Requires= instead? – sourcejedi May 17 '16 at 16:57
up vote 7 down vote accepted

You need to move the line

PartOf=app.service

out of [Service] and into the [Unit] section, and add to the [Unit] of app.service the list of customers to start, eg

Wants=app@customer1.service app@customer2.service

or as sourcejedi said in the comments, Requires= the same thing. You can keep the PartOf to stop services you start by hand that are not in the above list, like systemctl --user start app@customer3.service.

  • I confirmed you were right about PartOf. Thanks. I'm going to handle the "Wants" through a symlink, which becomes the single action I need to take to activate a new customer with systemd. For my test case: ` ln -s /home/mark/.config/systemd/user/app@.service /home/mark/.config/systemd/user/app.service.wants/unity@foo.service` – Mark Stosberg May 17 '16 at 18:16

I learned that this what systemd "Target Units" are for. By using a Target Unit, I get the benefits I want without needing to create the fake [Service] section I had above. A working example "Target Unit" file looks like this:

# named like app.target
[Unit]
Description=App Web Service

# This collection of apps should be started at boot time.
[Install]
WantedBy=multi-user.target

Then each customer instance should include PartOf in the [Unit] section (as pointed out by @meuh), and should also have an [Install] section so that enable and disable will work on the specific service:

# In a file name like app@.service
[Unit]
Description=%I Instance of App Web Service
PartOf=app.target

[Service]
ExecStart=/home/mark/bin/app-poc.sh %i
Restart=on-failure
StandardOutput=journal

# When the service runs globally, make it run as a particular user for added security
#User=myapp
#Group=myapp

# When systemctl enable is used, make this start when the App service starts
[Install]
WantedBy=app.target

To bring up the customer instance and have it start when the target is started, this one-time enable command is used:

 systemctl enable app

Now at this point I can use stop and start on app@customer to for a specific instance, or I can use start app and stop app to stop all the apps together.

  • How about status? I can't find a simple way to get a status of all services that App wanted. I know how I can script that, but ... – Tommi Kyntola Jun 4 at 12:52
  • 1
    I mean like getting the status of apps in that target group without listing all that are part of it, wild cards or not, preferably using that group name and not even caring what it's made of. – Tommi Kyntola Jun 6 at 16:22
  • 1
    It's not that simple. To which packet would that script belong to? It would have to be modified every time a new component is added. Forget that and deployment/maintenance goes haywire. What I'd obviously want is to just add a new packet with partOf setting indicating it's presence in that group and not then modify some script lingering about. And then stop and start of that target works like before. This works, but status seems to fall out of that scope. I can't even find a way to get a listing of units that are runtime present in a target. This use case is not covered by systemd. – Tommi Kyntola Jun 12 at 8:38
  • 1
    @TommiKyntola Here's a bash one-liner that you don't need to update as the target dependencies change: systemctl status $(systemctl list-dependencies --plain otp.target) – Mark Stosberg Jun 12 at 14:14
  • 2
    @TommiKyntola I agree that systemd could improve the usability here. I've opened a feature request to suggest improved status for targets. – Mark Stosberg Jun 12 at 14:37

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.