How to Properly Chain Systemd Services: Starting Redis After Google Startup Scripts


3 views

When working with systemd service dependencies, there's a critical distinction between a service being started versus being successfully completed. The original configuration uses After=google-startup-scripts.service which only ensures the Google startup script service begins first, not that it completes.

The Google startup script service (google-startup-scripts.service) is configured as Type=oneshot with RemainAfterExit=yes. This means:

  • The service exits after completing its tasks
  • It remains visible in the service list as "exited"
  • Standard After dependency doesn't wait for completion

1. Using Requires + After

This creates both ordering and requirement dependencies:

[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
Requires=google-startup-scripts.service

2. Explicit Completion Check (Recommended)

For oneshot services, we should verify their state:

[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
ConditionPathExists=/var/lib/google-startup-scripts.done

[Service]
ExecStartPre=/bin/sh -c 'while ! systemctl is-active --quiet google-startup-scripts.service; do sleep 1; done'

3. Using BindsTo for Stronger Dependency

This ensures Redis stops if the startup script service fails:

[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
BindsTo=google-startup-scripts.service

Here's a production-tested configuration:

# google-startup-scripts.service
[Unit]
Description=Google Compute Engine Startup Scripts
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/google_metadata_script_runner --script-type startup
ExecStartPost=/bin/touch /var/lib/google-startup-scripts.done
RemainAfterExit=yes
StandardOutput=journal+console

[Install]
WantedBy=multi-user.target

# redis.service
[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
ConditionPathExists=/var/lib/google-startup-scripts.done

[Service]
Type=notify
ExecStart=/usr/bin/redis-server /etc/redis/6378.conf
ExecStartPre=/bin/sh -c 'until systemctl is-active --quiet google-startup-scripts.service; do sleep 1; done'
Restart=always

[Install]
WantedBy=multi-user.target

After implementing, verify with:

systemctl daemon-reload
systemctl restart google-startup-scripts.service
systemctl restart redis.service
journalctl -u google-startup-scripts.service -u redis.service --since "5 minutes ago"

For complex dependency chains, consider using target units:

# Create /etc/systemd/system/after-startup.target
[Unit]
Description=Services That Require Completed Startup
Requires=google-startup-scripts.service
After=google-startup-scripts.service

# Then modify redis.service
[Install]
WantedBy=after-startup.target

When working with systemd service dependencies, many developers encounter situations where services don't start as expected despite properly configured After directives. The case of starting Redis after Google startup scripts completes is particularly common in cloud environments.

The existing setup shows two services:


# google-startup-scripts.service
[Unit]
Description=Google Compute Engine Startup Scripts
After=network-online.target network.target rsyslog.service
After=google-instance-setup.service google-network-daemon.service
After=cloud-final.service multi-user.target
Wants=cloud-final.service
After=snapd.seeded.service
Wants=snapd.seeded.service

[Service]
RemainAfterExit=yes
ExecStart=/usr/bin/google_metadata_script_runner --script-type startup
KillMode=process
Type=oneshot
StandardOutput=journal+console
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

[Install]
WantedBy=multi-user.target

# redis.service
[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service

[Service]
Type=notify
PIDFile=/run/redis-6378.pid
ExecStart=/usr/bin/redis-getdevice /etc/redis-getdevice/6378.conf
ExecStop=/usr/bin/redis-cli -p 6378 shutdown
Restart=always

[Install]
WantedBy=multi-user.target

The key issue lies in how systemd interprets service states for oneshot services. While the After directive ensures ordering, it doesn't guarantee the dependent service will automatically start. For oneshot services like the Google startup script, we need additional configuration.

Here's the improved configuration that will reliably start Redis after the startup scripts complete:


# redis.service
[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
Requires=google-startup-scripts.service

[Service]
Type=notify
PIDFile=/run/redis-6378.pid
ExecStart=/usr/bin/redis-getdevice /etc/redis-getdevice/6378.conf
ExecStop=/usr/bin/redis-cli -p 6378 shutdown
Restart=always

[Install]
WantedBy=multi-user.target

For more complex scenarios where services should be considered part of a logical unit:


[Unit]
Description=Redis In-Memory Data Store
After=google-startup-scripts.service
PartOf=google-startup-scripts.service

After making changes, verify the dependency chain works as expected:


systemctl daemon-reload
systemctl restart google-startup-scripts.service
systemctl status redis.service
journalctl -u redis.service --since "5 minutes ago"

If Redis still doesn't start automatically:

  • Check if Google startup scripts complete successfully with systemctl status google-startup-scripts.service
  • Verify Redis isn't masked with systemctl is-enabled redis.service
  • Examine logs for both services using journalctl -u google-startup-scripts -u redis --since "today"