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:
- Maintain reusable AMIs that may or may not need CloudFormation initialization
- Avoid hardcoding bootstrap logic in user data
- 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