Conditionally Starting systemd Services Based on File Existence in CloudFormation Environments


2 views

When working with CloudFormation-initiated EC2 instances, we often need systemd services that should only execute when specific configuration files exist. This becomes particularly important when creating reusable AMIs that might serve different purposes across deployments.

Systemd provides several mechanisms for conditional service execution:

# Example service unit showing condition checks
[Unit]
Description=CloudFormation initialization
ConditionPathExists=/etc/sysconfig/cloudformation
After=network.target

[Service]
Type=oneshot
EnvironmentFile=/etc/sysconfig/cloudformation
ExecStart=/usr/local/bin/cfn-init-wrapper
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

The complete solution involves three components:

#!/usr/local/bin/cfn-init-wrapper
#!/bin/bash

# Source environment variables if they exist
[ -f /etc/sysconfig/cloudformation ] && source /etc/sysconfig/cloudformation

output="$(cfn-init -s ${CFN_STACK_NAME} -r ${CFN_RESOURCE} --region ${CFN_REGION} 2>&1)"
returncode=$?

if [[ $returncode -ne 0 ]]; then
    cfn-signal -e $returncode -r "$output"
    exit $returncode
fi

cfn-signal -s

The cloud-init configuration would look like:

#cloud-config
write_files:
  - path: /etc/sysconfig/cloudformation
    owner: root:root
    permissions: '0644'
    content: |
      CFN_STACK_NAME="production-stack"
      CFN_RESOURCE="WebServer"
      CFN_REGION="us-west-2"
      
  - path: /usr/local/bin/cfn-init-wrapper
    owner: root:root
    permissions: '0755'
    content: |
      #!/bin/bash
      # ... wrapper script content as above ...

You can combine multiple conditions for more complex scenarios:

[Unit]
ConditionPathExists=/etc/sysconfig/cloudformation
ConditionPathExistsGlob=/var/lib/cloud/instance/*-config.json
ConditionVirtualization=vm

To test the conditional execution:

# Check service status
systemctl status cfn-init.service

# Manually test condition evaluation
systemd-analyze verify /etc/systemd/system/cfn-init.service

# Force immediate execution with conditions
systemd-run --property=ConditionPathExists=/etc/sysconfig/cloudformation /usr/local/bin/cfn-init-wrapper

For robust error handling, consider adding these service unit directives:

[Service]
TimeoutStartSec=300
Restart=on-failure
RestartSec=5s
SuccessExitStatus=0 255

When deploying AWS infrastructure with CloudFormation, we often need to execute bootstrap scripts during instance initialization. The standard approach using cfn-init works well, but becomes problematic when we want to:

  1. Maintain reusable AMIs that may or may not need CloudFormation initialization
  2. Avoid hardcoding bootstrap logic in user data
  3. Enable conditional execution based on environment configuration

Systemd provides several ways to implement conditional service execution:

# Example service unit demonstrating multiple condition checks
[Unit]
Description=CloudFormation initialization
After=network.target

# File existence check
ConditionPathExists=/etc/sysconfig/cloudformation

# Environment variable check (alternative approach)
ConditionEnvironment=CFN_STACK_NAME

[Service]
Type=oneshot
EnvironmentFile=/etc/sysconfig/cloudformation
ExecStart=/usr/local/bin/cfn-init-wrapper.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Here's the complete implementation pattern I recommend:

#!/usr/bin/env bash
# /usr/local/bin/cfn-init-wrapper.sh

set -o pipefail

# Source environment if exists
[ -f /etc/sysconfig/cloudformation ] && source /etc/sysconfig/cloudformation

# Execute cfn-init with error handling
output="$(cfn-init -s "${CFN_STACK_NAME}" -r "${CFN_RESOURCE}" --region "${CFN_REGION}" 2>&1)"
returncode=$?

if [[ $returncode -ne 0 ]]; then
    cfn-signal -e $returncode -r "$output" || true
    exit $returncode
fi

cfn-signal -s || true

For more complex scenarios, consider these enhancements:

# Adding restart logic for transient failures
[Service]
Restart=on-failure
RestartSec=30s
StartLimitInterval=5min
StartLimitBurst=3

# Combining multiple conditions
[Unit]
ConditionPathExists=|/etc/cloudformation.conf
ConditionPathExists=|/etc/sysconfig/cloudformation
ConditionKernelCommandLine=|cfn_init

To verify your configuration:

# Check service dependencies
systemd-analyze verify cfn-init.service

# Test condition evaluation
systemd-analyze condition 'ConditionPathExists=/etc/sysconfig/cloudformation'

# View service logs
journalctl -u cfn-init.service -b

The complete cloud-init configuration would look like:

#cloud-config
write_files:
  - path: /etc/sysconfig/cloudformation
    permissions: '0644'
    content: |
      CFN_STACK_NAME="prod-web-app"
      CFN_RESOURCE="WebServerInstance"
      CFN_REGION="us-west-2"
      
  - path: /usr/local/bin/cfn-init-wrapper.sh
    permissions: '0755'
    content: |
      #!/bin/bash
      # ... wrapper script content ...

runcmd:
  - systemctl enable cfn-init.service