Dear reader, In this post, I will help you execute EC2 user data scripts using CloudFormation. As you might already know, EC2 user data script, lets you bootstrap your EC2 instance by executing some commands(that you specify) dynamically after your instance is booted.
In my previous post, I talked about doing it via AWS console. In this post, we will learn to do it using CloudFormation. Feel free to add a comment in case you need me to cover using CLI as well.
Suggested Read:
A Bit of Backgroud on EC2 User Data
In a very simple terms if I say, user data is user data/commands that you can specify at the time of launching your instance. These data/command executes after your EC2 instance starts.
You don’t need to SSH into your EC2 instance and run those command one by one. Rather all you need is to specify the whole script in the user data section and they get executed once your instance boots up.
Let’s understand by an example-
You want to create a file log.txt in dev folder as soon as your instance starts. You can specify below user data to achieve that.
#!/bin/bash
touch /dev/log.txt
Note: User data scripts run as root user so you don’t need to specify sudo with your commands
User Data and CloudFormation
When you launch an EC2 instance, you can specify your user data like below.
Resources:
DemoInstance:
Type: AWS::EC2::Instance
Properties:
...
...
UserData: String
...
...
That means all you need is the parameter UserData of AWS::EC2::Instance resource type.
Sounds very easy right?
Well, there is a small catch, which if not known can be a time waster for you.
The property UserData must be a base64-encoded text. Also, the limit on user data size is 16 KB.
Therefore, always remeber to base64-encode your user data script while specifying your user data.
Don’t worry 🙂
CloudFormation provides a real simple way to do it on the go while specifying your user data using function Fn::Base64 ike you can see below. All you need is to prefix this function before your userdata.
Resources:
DemoInstance:
Type: AWS::EC2::Instance
Properties:
...
...
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
....
....
...
...
Usecase that we will implement today
Similar to other posts on this topic, we are going to install apache web server on an EC2 instance using EC2 User Data. I do have script handy for that. Have a look on the script below in case you need that.
EC2 User Data to Install Apache Web Server
#!/bin/bash
yum update -y
yum install -y httpd.x86_64
systemctl start httpd.service
systemctl enable httpd.service
echo ?Hello World from $(hostname -f)? > /var/www/html/index.html
This is how it looks like in a CloudFormation Template
Resources:
DemoInstance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref ImageId
.....
..... other properties
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
yum update -y
yum install -y httpd.x86_64
systemctl start httpd.service
systemctl enable httpd.service
echo ?Hello World from $(hostname -f)? > /var/www/html/index.html
Steps to Execute EC2 User Data Script using CloudFormation
I hope we are clear on the script by now. So let’s go ahead and see the step by step instruction to execute EC2 user data script using CloudFormation
Step 1: Provide proper permission
If you are not an admin user, you should explicitly provide ec2:* permission for your user/role. Additionally, you will also needs cloudformation:* as well to be able to do CloudFormation stack creation, updation etc.
I have specified * for simplicity. But please revise your permission based on uses to follow principal of least priviledge.
Step 2: Prepare a template
You can use YAML or JSON for your template. I prefer YAML for writing my templates. But don’t worry, If you want it in JSON, I will provide JSON template as well.
Template to Execute EC2 User Data Script using CloudFormation : YAML
In this template, we are launching an EC2 instance with user data specified. The user data says to install apache web server on your instance.
AWSTemplateFormatVersion: '2010-09-09'
Description: Template to Create an EC2 instance in a VPC
Parameters:
VpcId:
Type: String
Description: VPC id
Default: vpc-8854eef1
ImageId:
Type: String
Description: 'Linux 2 AMI for Ireland eu-west1 Region'
Default: 'ami-0fc970315c2d38f01'
InstanceType:
Type: String
Description: Choosing t2 micro because it is free
Default: t2.micro
KeyName:
Description: SSH Keypair to login to the instance
Type: AWS::EC2::KeyPair::KeyName
Default: DemoKeyPair
Resources:
DemoInstance:
Type: 'AWS::EC2::Instance'
Properties:
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref DemoSecurityGroup
UserData:
Fn::Base64:
!Sub |
#!/bin/bash
yum update -y
yum install -y httpd.x86_64
systemctl start httpd.service
systemctl enable httpd.service
echo ?Hello World from $(hostname -f)? > /var/www/html/index.html
DemoSecurityGroup:
Type: 'AWS::EC2::SecurityGroup'
Properties:
VpcId: !Ref VpcId
GroupDescription: SG to allow SSH access via port 22
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: '0.0.0.0/0'
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: EC2-SG
Outputs:
DemoInstanceId:
Description: Instance Id
Value: !Ref DemoInstance
Template to Execute EC2 User Data Script using CloudFormation : JSON
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Template to Create an EC2 instance in a VPC",
"Parameters": {
"VpcId": {
"Type": "String",
"Description": "VPC id",
"Default": "vpc-8854eef1"
},
"ImageId": {
"Type": "String",
"Description": "Linux 2 AMI for Ireland eu-west1 Region",
"Default": "ami-0fc970315c2d38f01"
},
"InstanceType": {
"Type": "String",
"Description": "Choosing t2 micro because it is free",
"Default": "t2.micro"
},
"KeyName": {
"Description": "SSH Keypair to login to the instance",
"Type": "AWS::EC2::KeyPair::KeyName",
"Default": "DemoKeyPair"
}
},
"Resources": {
"DemoInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": {
"Ref": "ImageId"
},
"InstanceType": {
"Ref": "InstanceType"
},
"KeyName": {
"Ref": "KeyName"
},
"SecurityGroupIds": [
{
"Ref": "DemoSecurityGroup"
}
],
"UserData": {
"Fn::Base64": {
"Fn::Sub": "#!/bin/bash\nyum update -y\nyum install -y httpd.x86_64\nsystemctl start httpd.service\nsystemctl enable httpd.service\necho ?Hello World from $(hostname -f)? > /var/www/html/index.html\n"
}
}
}
},
"DemoSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"VpcId": {
"Ref": "VpcId"
},
"GroupDescription": "SG to allow SSH access via port 22",
"SecurityGroupIngress": [
{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 80,
"ToPort": 80,
"CidrIp": "0.0.0.0/0"
},
{
"IpProtocol": "tcp",
"FromPort": 443,
"ToPort": 443,
"CidrIp": "0.0.0.0/0"
}
],
"Tags": [
{
"Key": "Name",
"Value": "EC2-SG"
}
]
}
}
},
"Outputs": {
"DemoInstanceId": {
"Description": "Instance Id",
"Value": {
"Ref": "DemoInstance"
}
}
}
}
Step3: Create a Stack using prepared template
Now, we know the basics and we have the template so let’s go and create the stack.
- Grab the YAML or JSON template from above as per your convenience.
- Change parameters like ImageId, InstanceType and KeyName with your own AMI Id, instance type and name of keypair respectivey
- Save the template with .yml or .json as per the choice of template and follow below steps.
- Login to AWS Management Console, navigate to CloudFormation and click on Create stack
- Click on “Upload a template file”, upload your saved .yml or .json file and click Next
- Enter the stack name and click on Next. In configuration, keep everything as default and click on Next.
- In the events tab of stack, you can view the status.
- Once stack is successfully created, you can check “Resources” tab to see all that’s created by this template.
- Navigate to EC2 instance, grab the instance public IP from instance details screen and hit the pubic IP. You should see hello world response fro your server.
Clean Up
If you are creating this EC2 instance just for learning purpose. Don’t forget to delete your CloudFormation stack so that your instance is terminated and you don’t bear any cost.
Happy Learning !!!
Conclusion:
In this post, we learnt to execute EC2 user data script using CloudFormation.
We learnt-
- About user data and how it lets you bootstrap instance
- A catch regarding specifying user data correctly
- Testing your installed server
I hope you found this post helpful. If you find any issue, please fee free to reach me in comment section. I would be more than happy to reply to your comment.
Enjoyed the content?
Subscribe to our newsletter below to get awesome AWS learning materials delivered straight to your inbox.
Don’t forget to motivate me by-
- Adding a comment below on what you liked and what can be improved.
- Follow us on
- Share this post with your friends
4 thoughts on “How to Execute EC2 User Data Script using CloudFormation”
How to update the powershell script in the user data.It will be helpful if you update the same. updating the code below.
{
“AWSTemplateFormatVersion”: “2010-09-09”,
“Description”: “Template to Create an EC2 instance in a VPC”,
“Parameters”: {
“VpcId”: {
“Type”: “String”,
“Description”: “VPC id”,
“Default”: “vpc-58c9a833”
},
“ImageId”: {
“Type”: “String”,
“Description”: “windows machine”,
“Default”: “ami-0c4a11a8d0e503812”
},
“InstanceType”: {
“Type”: “String”,
“Description”: “Choosing t2 micro because it is free”,
“Default”: “t2.micro”
},
“KeyName”: {
“Description”: “SSH Keypair to login to the instance”,
“Type”: “AWS::EC2::KeyPair::KeyName”
}
},
“Resources”: {
“DemoInstance”: {
“Type”: “AWS::EC2::Instance”,
“Properties”: {
“ImageId”: {
“Ref”: “ImageId”
},
“InstanceType”: {
“Ref”: “InstanceType”
},
“KeyName”: {
“Ref”: “KeyName”
},
“SecurityGroupIds”: [
{
“Ref”: “DemoSecurityGroup”
}
],
“UserData”: {
“Fn::Base64”: {
“Fn::Sub”: “if ( Get-Service “AWSXRayDaemon” -ErrorAction SilentlyContinue ) {
sc.exe stop AWSXRayDaemon
sc.exe delete AWSXRayDaemon
}
$targetLocation = “C:\Program Files\Amazon\XRay”
if ((Test-Path $targetLocation) -eq 0) {
mkdir $targetLocation
}
$zipFileName = “aws-xray-daemon-windows-service-3.x.zip”
$zipPath = “$targetLocation\$zipFileName”
$destPath = “$targetLocation\aws-xray-daemon”
if ((Test-Path $destPath) -eq 1) {
Remove-Item -Recurse -Force $destPath
}
$daemonPath = “$destPath\xray.exe”
$daemonLogPath = “$targetLocation\xray-daemon.log”
$url = “https://s3.dualstack.us-west-2.amazonaws.com/aws-xray-assets.us-west-2/xray-daemon/aws-xray-daemon-windows-service-3.x.zip”
Invoke-WebRequest -Uri $url -OutFile $zipPath
Add-Type -Assembly “System.IO.Compression.Filesystem”
[io.compression.zipfile]::ExtractToDirectory($zipPath, $destPath)
New-Service -Name “AWSXRayDaemon” -StartupType Automatic -BinaryPathName “`”$daemonPath`” -f `”$daemonLogPath`””
sc.exe start AWSXRayDaemon”
}
}
}
},
“DemoSecurityGroup”: {
“Type”: “AWS::EC2::SecurityGroup”,
“Properties”: {
“VpcId”: {
“Ref”: “VpcId”
},
“GroupDescription”: “SG to allow SSH access via port 22”,
“SecurityGroupIngress”: [
{
“IpProtocol”: “tcp”,
“FromPort”: “22”,
“ToPort”: “22”,
“CidrIp”: “0.0.0.0/0”
},
{
“IpProtocol”: “tcp”,
“FromPort”: 80,
“ToPort”: 80,
“CidrIp”: “0.0.0.0/0”
},
{
“IpProtocol”: “tcp”,
“FromPort”: 443,
“ToPort”: 443,
“CidrIp”: “0.0.0.0/0”
}
],
“Tags”: [
{
“Key”: “Name”,
“Value”: “EC2-SG”
}
]
}
}
},
“Outputs”: {
“DemoInstanceId”: {
“Description”: “Instance Id”,
“Value”: {
“Ref”: “DemoInstance”
}
}
}
}
Awesome content. The simplicity helped me alot. Thank you.
Most welcome NKiru 🙂
Thank this help me