#4 user-data, cloud-init and CloudFormation
Show Notes
Instance meta-data can be accessed by hitting this URL from within an EC2 instance:
http://169.254.169.254/
On Ubuntu, you can use the ec2metadata
script to list out one or all of the fields. On Amazon Linux, use the ec2-metadata
script.
$ ec2metadata ami-id: ami-a73264ce ami-launch-index: 0 ami-manifest-path: (unknown) ancestor-ami-ids: unavailable availability-zone: us-east-1a block-device-mapping: ami root instance-action: none instance-id: i-91d66fe8 instance-type: t1.micro local-hostname: ip-10-73-174-101.ec2.internal local-ipv4: 10.73.174.101 kernel-id: aki-88aa75e1 mac: unavailable profile: default-paravirtual product-codes: unavailable public-hostname: ec2-54-205-181-234.compute-1.amazonaws.com public-ipv4: 54.205.181.234 public-keys: ['ssh-rsa AAAAB...'] ramdisk-id: unavailable reserveration-id: unavailable security-groups: mysg user-data: unavailable
Both Amazon Linux and Ubuntu come with a package called cloud-init
that checks the meta-data for user-data
, and performs various actions including running scripts and chef recipes on boot.
An example user-data
script to update Ubuntu packages and install Apache is:
#!/bin/bash apt-get update apt-get upgrade -y apt-get install apache2 -y echo "<html><body><h1>Welcome</h1>" > /var/www/index.html echo "I was generated from user-data and cloud-init" >> /var/www/index.html echo "</body></html>" >> /var/www/index.html
You can pass CloudFormation parameters to instances via user-data
by using either the AWS::EC2::Instance
or AWS::AutoScaling::LaunchConfiguration
resources in your template. Strings inside the template can use the Fn::Join
intrinsic function. An example template is here:
{ "Description": "Episode 4 example of user-data and cloud-init", "Parameters": { "KeyPair": { "Description": "Name of the keypair to use for SSH access", "Type": "String" }, "Environment": { "Description": "Name of this environment", "Type": "String", "Default": "Production" }, "Role": { "Description": "The role this server should be", "Type": "String", "Default": "Web" } }, "Resources": { "MyElasticLoadBalancer": { "Properties": { "AvailabilityZones": { "Fn::GetAZs": "" }, "HealthCheck": { "HealthyThreshold": 3, "Interval": 30, "Target": "HTTP:80/", "Timeout": 5, "UnhealthyThreshold": 5 }, "Listeners": [ { "InstancePort": 80, "LoadBalancerPort": 80, "Protocol": "HTTP" } ] }, "Type": "AWS::ElasticLoadBalancing::LoadBalancer" }, "MySecurityGroup": { "Properties": { "GroupDescription": "Allow access to MyInstance", "SecurityGroupIngress": [ { "CidrIp": "0.0.0.0/0", "FromPort": 22, "IpProtocol": "tcp", "ToPort": 22 }, { "FromPort": 80, "IpProtocol": "tcp", "SourceSecurityGroupName": { "Fn::GetAtt": [ "MyElasticLoadBalancer", "SourceSecurityGroup.GroupName" ] }, "SourceSecurityGroupOwnerId": { "Fn::GetAtt": [ "MyElasticLoadBalancer", "SourceSecurityGroup.OwnerAlias" ] }, "ToPort": 80 } ] }, "Type": "AWS::EC2::SecurityGroup" }, "MyASG": { "Properties": { "AvailabilityZones": { "Fn::GetAZs": "" }, "Cooldown": 120, "LaunchConfigurationName": { "Ref": "MyLaunchConfig" }, "LoadBalancerNames": [ { "Ref": "MyElasticLoadBalancer" } ], "MaxSize": 1, "MinSize": 1, "Tags": [ { "Key": "Name", "PropagateAtLaunch": "true", "Value": "Episode 4" } ] }, "Type": "AWS::AutoScaling::AutoScalingGroup" }, "MyLaunchConfig": { "Properties": { "ImageId": "ami-ef277b86", "InstanceType": "t1.micro", "KeyName": { "Ref": "KeyPair" }, "SecurityGroups": [ { "Ref": "MySecurityGroup" } ], "UserData": { "Fn::Base64": { "Fn::Join": [ "\n", [ "#!/bin/bash", "apt-get update", "apt-get upgrade -y", "apt-get install apache2 -y", "echo \"<html><body><h1>Welcome</h1>\" > /var/www/index.html", { "Fn::Join": [ "", [ "echo \"<h2>Environment: ", { "Ref": "Environment" }, "</h2>\" >> /var/www/index.html" ] ] }, { "Fn::Join": [ "", [ "echo \"<h2>Role: ", { "Ref": "Role" }, "</h2>\" >> /var/www/index.html" ] ] }, "echo \"</body></html>\" >> /var/www/index.html" ] ] } } }, "Type": "AWS::AutoScaling::LaunchConfiguration" } }, "Outputs": { "URL": { "Description": "URL of the sample website", "Value": { "Fn::Join": [ "", [ "http://", { "Fn::GetAtt": [ "MyElasticLoadBalancer", "DNSName" ] } ] ] } } } }
The same template and user-data
script is easier to write using troposphere.