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"