2

In systemd, I need to setup some services, which depend on one common "initialization" service, that must finish before they are started. Also, I would like to ensure, that the worker services and the "initialization" service can never run simultaneously at any time:

                                    manual
    BOOT --.                     "restart svc" ---.
           V                                      V
svc-init   |-----|                                |---------|
svc-a            |--------------------------------|         |----- - - -
svc-b            |--------------------------------|         |----- - - -

I want to be sure that the services are "enabled" (run at system boot); also, I would like to be able to restart the whole thing manually at any time I need.

How can I do that?

I tried a setup with important fields like below:

/etc/systemd/system/svc-init.service:

[Service]
Type=oneshot
ExecStart=/opt/svc/init-svc.sh

[Install]
WantedBy=multi-user.target

.../svc-a.service, .../svc-b.service:

[Unit]
Wants=svc-init.service
After=svc-init.service
Conflicts=svc-init.service

[Service]
Restart=always
ExecStart=/opt/svc/svc.sh

[Install]
WantedBy=multi-user.target

But when I try to run them, it fails to do what I want:

  1. sudo systemctl start svc-init
    • it correctly stops svc-a & svc-b
    • but it doesn't start svc-a & svc-b again after svc-init is done
  2. sudo systemctl start svc-a
    • it doesn't force svc-init to run beforehand
  3. sudo systemctl start svc-a svc-b svc-init
    • the effect seems the same as in (1) above (that is, ... start svc-init)
  4. sudo systemctl start svc-init svc-a svc-b
    • doesn't run svc-init
    • prints: Job for svc-init.service canceled.

What are the magic systemd incantations to make the system behave as I need it to? Or should I structure the units differently, somehow?

edit: Per suggestion in comments: to clarify, the svc-a & svc-b are in my case actually instances of a single service, and may need to be dynamically started/stopped (they're actually: svc@1, svc@2, svc@3, etc.).

  • Why are you putting these two scripts in separate unit files? What is actually in the scripts? It's usually a bad idea to call out to scripts; it's better to let systemd handle environment setup and the like. – Michael Hampton Nov 5 '18 at 14:21
  • @MichaelHampton The svc-init does some initial setup of directories, the scripts then do some processing on them in parallel. They're non-trivial, and do some work coordination and filesystem-based locking (which needs to be bootstrapped by the "svc-init" script). The svc-a & svc-b are actually instances (svc@1 up to svc@5), that can be started/stopped dynamically, but I didn't want to muddle an already complex question with extra detail which I thought was not relevant – akavel Nov 5 '18 at 14:43
  • The instantiation really is relevant, as its presence changes the possible solutions a bit. You definitely should include this in your question. – Michael Hampton Nov 5 '18 at 15:15
2

Systemd can be hard to reason about, but this seems like a good experiment to try:

svc-init.service

[Unit]
Before=svc@.service

Before tells systemd that svc-init needs to be all the way done before any named service will be started. This should give you your explicit must-complete ordering.

svc@.service

[Unit]
After=svc-init.service
BindsTo=svc-init.service

After mimics the Before in the svc-init service, and may be more convenient for you. Declaring it in both places is not an error, nor is it required. It merely makes your intent explicit.

BindsTo tells systemd that if the named service is stopped for any reason, this one is too. Using it with an After or Before declaration ensures that this stopping happens in the correct order. Depending on your needs, you may want to use Requires instead of BindsTo, since Requires indicates that only stop this service when the named service is explicitly stopped. If BindsTo is used, whenever svc-init is restarted, this service will be restarted as well. Either of these is a stronger version of the Wants you list in your examples.

Configured like this, when svc-init starts from a stopped state, it will wait to trigger any svc instances until after svc-init finishes launching (Before/After) and will only do so when svc-init finishes launching successfully (BindsTo). Once that is done, then any svc instances are started. If svc-init is given a stop command, all svc instances will be stopped at the same time as svc-init (BindsTo). If svc-init is given a restart command, svc-init and any depending services will be stopped and then started.

If svc-init is intended to launch-then-exit, you might need a SuccessExitStatus= declaration in its Unit section to tell systemd which exit-codes are expected on a successful launch.

  • Whoa, cool, thanks! In svc@.service, I added BindsTo=svc-init.service, kept After=svc-init.service, and removed Conflicts. In svc-init.service, I added RemainAfterExit=yes. From what I managed to test, seems to work perfectly as I needed, huge thanks! :) ❤️ – akavel Nov 5 '18 at 16:26
-1

Assuming you can't get rid of the scripts and have systemd do all the setup itself (which you almost certainly can, but whoever wrote the scripts might not know how; yell at the vendor/developer until they learn) I think you should be using only one unit file for this.

In a single unit file, you will run the init script in an ExecStartPre= option. The unit will fail to start if the program called here fails to exit successfully. For example, in svc@.service:

[Service]
Restart=always
ExecStartPre=/opt/svc/init-svc.sh %I
ExecStart=/opt/svc/svc.sh %I

[Install]
WantedBy=multi-user.target
  • The vendor/developer is me, and I still don't know how to do the setup magically with systemd after this answer. What exactly would you suggest I should yell at myself? As to ExecStartPre: init-svc must not be called concurrently with any svc@%i that is already running. There's no init-svc.sh %I, there's just a common init-svc.sh, and it's job is to level the field for coordination between svc@%i instances, globally. IIUC, ExecStartPre would be called per each svc@%i, which makes this answer, in its current shape, invalid for my use case. – akavel Nov 5 '18 at 16:11
  • And that's why instantiation is important, and also why this stuff, whatever it is, should be done directly in systemd rather than in an external script. But you didn't mention anything specific so it's not really possible to give further advice. – Michael Hampton Nov 5 '18 at 16:13
  • Ok: init-svc.sh is roughly: rm -rf /var/opt/svc/initial-work-tokens && cp -r /etc/opt/svc/initial-work-tokens /var/opt/svc/ – akavel Nov 5 '18 at 16:39
  • @akavel Hm. Easy enough, but how do you determine when the files should be replaced? What is the criteria? – Michael Hampton Nov 5 '18 at 16:40
  • The criterion is: "when there are no svc@%i (a.k.a. svc.sh) running" – akavel Nov 6 '18 at 8:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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