cfn-init is a daemon that runs on EC2 instances launched by CloudFormation, executing commands defined in the Metadata section of a CloudFormation template.
Here’s a simple UserData script that bootstraps an EC2 instance using cfn-init:
---
AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance bootstrapped with cfn-init
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: ami-0abcdef1234567890 # Replace with a valid AMI ID
InstanceType: t2.micro
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
yum update -y
# Install the CloudFormation helper script
yum install -y aws-cfn-bootstrap
# Execute cfn-init
/opt/aws/bin/cfn-init -v \
--stack ${AWS::StackName} \
--resource MyEC2Instance \
--region ${AWS::Region}
# Execute cfn-signal
/opt/aws/bin/cfn-signal -e $? \
--stack ${AWS::StackName} \
--resource MyEC2Instance \
--region ${AWS::Region}
Metadata:
AWS::CloudFormation::Init:
configSets:
default:
- install
- configure
install:
packages:
yum:
nginx: []
files:
/etc/cfn/cfn-hup.conf:
mode: '000400'
owner: 'root'
group: 'root'
content: !Sub |
[main]
stack=${AWS::StackName}
region=${AWS::Region}
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.MyEC2Instance.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource MyEC2Instance --region ${AWS::Region}
[update-master]
wait-for-remote-user=true
# Uncomment and set the following to enable the cfn-push feature
# cert=/etc/cfn/key.pem
# key=/etc/cfn/cert.pem
/etc/nginx/conf.d/default.conf:
mode: '000644'
owner: 'root'
group: 'root'
content: !Sub |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /cfn-init-test {
return 200 'Hello from cfn-init!\n';
add_header Content-Type text/plain;
}
}
configure:
commands:
01_create_index_html:
command: !Sub |
echo "<h1>Instance bootstrapped by CloudFormation</h1>" > /usr/share/nginx/html/index.html
cwd: /tmp
02_start_nginx:
command: systemctl start nginx
test: !Sub |
systemctl is-active nginx > /dev/null 2>&1 || exit 1
This template defines an EC2 instance that will install Nginx, configure a basic Nginx server block, and create an index.html file. The UserData script orchestrates the execution of cfn-init and cfn-signal.
The cfn-init daemon, when executed, reads the AWS::CloudFormation::Init metadata. It can perform several actions:
packages: Installs specified packages using the instance’s package manager (e.g.,yum,apt-get). In the example,nginxis installed viayum.files: Creates or updates files on the instance. You can specify thecontent,mode,owner, andgroupfor these files. Here, we configurecfn-hup.conffor automatic updates andnginx’s default server block.commands: Executes commands on the instance. These are run in the order specified by a numerical prefix (e.g.,01_,02_). The example creates anindex.htmland starts thenginxservice.groups: Creates groups on the instance.users: Creates users on the instance.sources: Downloads files from S3 or HTTP(S) locations.
The cfn-signal command is crucial for reporting the success or failure of the instance’s initialization back to CloudFormation. This allows CloudFormation to track the stack’s progress and determine if the instance has been successfully provisioned. The -e $? passes the exit code of the preceding command (in this case, cfn-init) to cfn-signal.
The cfn-hup.conf file, when configured with triggers: post.update, enables cfn-init to re-run automatically after a stack update that modifies the AWS::CloudFormation::Init metadata. This is a powerful mechanism for applying configuration changes to running instances without manual intervention.
The most surprising thing about cfn-init is that it’s not just a one-time bootstrap tool; its cfn-hup component can act as a daemon to poll for and apply configuration changes defined in CloudFormation metadata, effectively turning your infrastructure-as-code into a dynamic configuration management system for your instances.
To see this in action, launch the CloudFormation stack. Once the stack is complete, you can SSH into the instance and verify that Nginx is running (systemctl status nginx) and that the index.html file exists. You can also test the Nginx configuration by accessing http://<your_instance_public_ip>/cfn-init-test in your browser, which should return "Hello from cfn-init!".
The next concept you’ll likely encounter is managing the state of your configurations across multiple instances and handling dependencies between resources during the bootstrap process.