Can I reuse the same bootstrap configuration from AWS :: CloudFormation :: Init (and / or user data) for multiple EC2 :: Instances in the template?
No, this is not possible with the AWS :: EC2 :: Instance resource in the same template. However, there is an AWS :: AutoScaling :: LaunchConfiguration resource type, but today this resource is applicable only to auto-scaling groups. Ideally, AWS will provide a similar resource type that can be applied to multiple AWS :: EC2 :: Instance resources. This means that when using autoscale groups, there is a value .
Here is a simple example that will allow you to do this with a single start configuration and several autoscale groups in a single template. Auto-scaling is usually configured with multiple instances, but I use MinSize and MaxSize 1 to mirror the configuration you came up with with the resource type AWS :: EC2 :: Instance. Although we use the same instance with the Auto Scaling group, we still get the benefits of Auto Scaling with the fault tolerance of one instance. If an instance becomes unhealthy, Auto Scaling will automatically replace the instance.
{ "AWSTemplateFormatVersion": "2010-09-09", "Resources" : { "LaunchConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Properties" : { "InstanceType" : { "Ref" : "InstanceType" }, "ImageId" : "ami-84a333be", "KeyName" : { "Ref" : "KeyName" }, "SecurityGroupIds" : [{"Ref" : "SecurityGroupId"}], "UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [ "#!/bin/bash -v\n", "# Run cfn-init\n", "/opt/aws/bin/cfn-init -v ", " -stack ", { "Ref": "AWS::StackName" }, " -resource LaunchConfig ", " --region ", { "Ref" : "AWS::Region" }, "\n", "# Signal success\n", "/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "WaitConditionHandle" }, "'\n" ]]}} }, "Metadata" : { "AWS::CloudFormation::Init" : { "config" : { "files" : { "/etc/apt/apt.conf.d/99auth" : { "content" : "APT::Get::AllowUnauthenticated yes;" }, "/etc/apt/sources.list.d/my-repo.list" : { "content" : "deb http://my-repo/repo apt/" } }, "commands" : { "01-apt-get update" : { "command" : "apt-get update" }, "02-apt-get install puppet" : { "command" : "apt-get install puppet my-puppet-config" }, "03-puppet apply" : { "command" : "puppet apply" } } } } } }, "ASG1" : { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : [ { "Ref" : "AZ" } ], "VPCZoneIdentifier" : [ { "Ref" : "SubnetId" } ], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MaxSize" : "1", "MinSize" : "1", "Tags" : [ { "Key" : "Name", "Value": "Server1", "PropagateAtLaunch" : "true" }, { "Key" : "Version", "Value": "1.0", "PropagateAtLaunch" : "true" } ] } }, "ASG2" : { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : [ { "Ref" : "AZ" } ], "VPCZoneIdentifier" : [ { "Ref" : "SubnetId" } ], "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, "MaxSize" : "1", "MinSize" : "1", "Tags" : [ { "Key" : "Name", "Value": "Server2", "PropagateAtLaunch" : "true" }, { "Key" : "Version", "Value": "1.0", "PropagateAtLaunch" : "true" } ] } }, "WaitConditionHandle" : { "Type" : "AWS::CloudFormation::WaitConditionHandle" }, "WaitCondition" : { "Type" : "AWS::CloudFormation::WaitCondition", "Properties" : { "Handle" : { "Ref" : "WaitConditionHandle" }, "Timeout" : "300" } } } }
This is not a complete example, and much more can be done with Auto Scaling, but I just wanted to give you a simple example of sharing a launch configuration with multiple instances. Each Auto Scaling group defines its own set of tags.
I'm currently thinking of using AWS :: CloudFormation :: Stack to import a shared instance template, but I need to somehow pass the parameter tags for each resource in the stack template to the instance template. The question is - if this is the best approach, what should I do? Enter in the 3 locations that currently have
I would recommend the solution described above, but would also like to answer your questions on how to use tags with a nested stack.
stack.template
{ "AWSTemplateFormatVersion": "2010-09-09", "Resources" : { "Server1" : { "Type": "AWS::CloudFormation::Stack", "Properties": { "TemplateURL": "https://s3.amazonaws.com/mybucket/instance.template", "Parameters": { "InstanceType": "m1.medium", "TagName": "Server1" "TagVersion": "1.0" } } }, "Server2" : { "Type": "AWS::CloudFormation::Stack", "Properties": { "TemplateURL": "https://s3.amazonaws.com/mybucket/instance.template", "Parameters": { "InstanceType": "m1.medium", "TagName": "Server2" "TagVersion": "1.0" } } } } }
instance.template
{ "AWSTemplateFormatVersion": "2010-09-09", "Parameters" : { "InstanceType" : {...}, "KeyName": {...} "TagName": { "Description" : "The name tag to be applied to each instance", "Type" : "String" }, "TagVersion": { "Description" : "The version tag to be applied to each instance", "Type" : "String" } }, "Resources" : { "Instance" : { "Type" : "AWS::EC2::Instance", "Metadata" : { "AWS::CloudFormation::Init" : { ... } }, "Properties" : { "InstanceType" : { "Ref" : "InstanceType" }, "ImageId" : "ami-84a333be", "KeyName" : { "Ref" : "KeyName" }, "SubnetId" : { "Ref" : "SubnetId" }, "SecurityGroupIds" : [ { "Ref" : "SecurityGroupId"] } ], "Tags" : [ { "Key" : "Name", "Value": { "Ref" : "TagName" } }, { "Key" : "Version", "Value": { "Ref" : "TagVersion" } }, ] } } } }
There is no standard for passing the entire array of tags as a parameter, so you can see that I just broke each tag into its own parameter and passed them to nested stacks.
(Bonus credit - what's the difference between userdata and this init block?)
UserData allows you to pass arbitrary data to the instance on first boot. This is often a shell script that can automate tasks when the instance starts. For example, you can simply run the yum update.
"UserData" : { "Fn::Base64" : { "Fn::Join" : [ "", [ "#!/bin/bash\n" "yum update -y", "\n" ]]}}
UserData becomes even more useful when combined with AWS :: CloudFormation :: Init metadata, which allows you to structure your bootstrap configuration. In this case, UserData is simply used to invoke the cfn-init script that runs the AWS :: CloudFormation :: Init metadata. I included this template in my first example above using the launch configuration. It is important to note that the UserData section is executed only once during the first load of the instance. This is important to keep in mind when you think about how you want to handle updates for your instances.