Start service after AWS user-data has run

Use

[Unit]
…
After=cloud-final.service
…
[Install]
WantedBy=cloud-init.target

I ran into this as well. journalctl showed user data stuff running after multi-user and before the target cloud-init was reached.

<timestamp> <ip_addr> systemd[1]: Reached target Multi-User System.
[snip]
<timestamp> <ip_addr> systemd[1]: Starting Execute cloud user/final scripts...
<timestamp> <ip_addr> user-data[1592]: my user data stuff
[snip]
<timestamp> <ip_addr> systemd[1]: Started Execute cloud user/final scripts.
[snip]
<timestamp> <ip_addr> systemd[1]: Reached target Cloud-init target.

So I thought to have it be required/wanted by cloud-init instead of the usual multi-user target (which may be what op had), and it just worked.


The solution proposed by vlfig works. But, I think that it can be improved.

In my example, I'm trying to run the Puppet agent after cloud-init is finished.

In my case, we wanted Puppet to only run after Cloud Init is done with its set up. In order to achieve that, we need to modify the setup of its systemd unit.

The default execution order for the services and targets that interest us is the following: 1. cloud-init-local.service 2. cloud-init.service 3. cloud-config.target 4. basic.target 5. cloud-config.service 6. puppet.service 7. multi-user.target 8. cloud-final.service 9. cloud-init.target

(stag) dki@appbackend-i-0f0d3f6abd8520f12:~$ systemd-analyze --no-pager critical-chain puppet.service
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.

puppet.service @17.737s
└─basic.target @17.291s
  └─paths.target @17.290s
    └─resolvconf-pull-resolved.path @17.290s
      └─sysinit.target @17.282s
        └─cloud-init.service @14.709s +2.572s
          └─systemd-networkd-wait-online.service @13.872s +832ms
            └─systemd-networkd.service @13.487s +377ms
              └─network-pre.target @13.482s
                └─cloud-init-local.service @6.983s +6.495s
                  └─var-lib.mount @11.888s
                    └─local-fs-pre.target @10.584s
                      └─keyboard-setup.service @6.506s +4.072s
                        └─systemd-journald.socket @6.430s
                          └─system.slice @6.416s
                            └─-.slice @3.772s

Dependency graph:

Systemd services dependency graph
Note: The green arrows mean After= and the gray arrows mean Wants=

By having puppet.service in /etc/systemd/system/multi-user.target.wants, systemd automatically creates a dependency order of the type After=puppet.service according to https://www.freedesktop.org/software/systemd/man/systemd.target.html#Default%20Dependencies

Dependency graph:

Puppet service dependency graph original
Note: The green arrows mean After= and the gray arrows mean Wants=

As a consequence, if we try to make it run After=cloud-init.target, it creates a dependency cycle and doesn't start at all.

[Mon Jul  9 12:49:01 2019] systemd[1]: multi-user.target: Found ordering cycle on puppet.service/start
[Mon Jul  9 12:49:01 2019] systemd[1]: multi-user.target: Found dependency on cloud-init.target/start
[Mon Jul  9 12:49:01 2019] systemd[1]: multi-user.target: Found dependency on cloud-final.service/start
[Mon Jul  9 12:49:01 2019] systemd[1]: multi-user.target: Found dependency on multi-user.target/start
[Mon Jul  9 12:49:01 2019] systemd[1]: multi-user.target: Job puppet.service/start deleted to break ordering cycle starting with multi-user.target/start

In order to break this cycle, we need to disable the DefaultDependencies directive. By doing that, we are able to add the After=cloud-init.target to the Puppet unit in order to ensure that it waits for Cloud-Init to be done before it starts running. Both directives are done by creating the /etc/systemd/system/puppet.service.d/override.conf file, which overrides the package's original configurations without changing its original content. The advantage of it is that we can upgrade the package keeping the changes to only the directives we actually need.

Dependency graph:

Puppet service dependency graph afterwards
Note: The green arrows mean After= and the gray arrows mean Wants=

The resulting execution order is: 1. cloud-init-local.service 2. cloud-init.service 3. cloud-config.target 4. basic.target 5. cloud-config.service 6. multi-user.target 7. cloud-final.service 8. cloud-init.target 9. puppet.service

(sand) dki@supplier-integrations-i-035c65e75762aaabd:~$ systemd-analyze --no-pager critical-chain puppet.service
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.

puppet.service @43.300s
└─cloud-init.target @43.298s
  └─cloud-final.service @33.272s +10.025s
    └─multi-user.target @33.246s
      └─splunk.service @13.815s +19.429s
        └─basic.target @13.463s
          └─sockets.target @13.463s
            └─docker.socket @13.453s +10ms
              └─sysinit.target @13.436s
                └─cloud-init.service @10.873s +2.557s
                  └─systemd-networkd-wait-online.service @9.182s +1.689s
                    └─systemd-networkd.service @9.034s +143ms
                      └─network-pre.target @9.032s
                        └─cloud-init-local.service @854ms +8.177s
                          └─var-lib.mount @7.587s
                            └─local-fs-pre.target @1.391s
                              └─keyboard-setup.service @569ms +822ms
                                └─systemd-journald.socket @564ms
                                  └─system.slice @564ms
                                    └─-.slice @423ms