Nested Stacks in AWS CloudFormation are stacks, created from another, a “parent”, stack using AWS::CloudFormation::Stack
.
The main idea behind the Nested Stacks is to avoid writing superfluous code and to make templates reusable.
Instead, a template is created only once, stored in an S3 bucket, and during stacks creation – you just refer to it.
For example, you can use the same template file to create two Load Balancers with different parameters and/or listeners using Conditions.
Documentation is available here>>>, and a good post is here>>>.
In this post we will:
- create a root stack – it will describe our other stacks, it will like a skeleton
- will add another stack with a VPC as a child stack to the root stack
- and one more child stack with AWS SecurityGroups
Also, our nested stack must be able to share their parameters between them to make it possible to use the same template for various environments – Dev/Stage/Prod.
In the end – we will take a brief overview of AWS CloudFormation parameters Import/Export feature between independent stacks.
Resulted templates are available on the Github.
Pitfalls
- do not delete nested stack manually – only via a “root” stack
- use AWS S3 Versioning for templates
The Root stack
Let’s start from writing a root stack’s template – root-stack.json
:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Resources" : {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml"
}
}
}
}
Here we are creating the only one resource – AWS::CloudFormation::Stack
passing a child’s stack template file network-stack.yml
via the root’s Properties
.
In the TemplateURL
will have to set an S3 bucket URL – will update it shortly.
Now, create the network-stack.yml
file:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Nested Network Stack",
"Resources" : {
"VPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : "11.0.0.0/16"
}
}
}
}
Create an S3 bucket to store our templates:
$ aws s3api create-bucket --bucket eks-cloudformation --region eu-west-3 --create-bucket-configuration LocationConstraint=eu-west-3 --profile arseniy --region eu-west-3
{
"Location": "http://eks-cloudformation.s3.amazonaws.com/"
}
Remember the «Location»: «http://eks-cloudformation.s3.amazonaws.com/» – will need it now.
For such a bucket will be a really good idea to enable versioning.
Enable it:
$ aws --region eu-west-3 --profile arseniy s3api put-bucket-versioning --bucket bttrm-eks-cloudformation --versioning-configuration Status=Enabled
Upload your network-stack.yml
to the bucket:
$ aws --profile arseniy --region eu-west-3 s3 cp network-stack.yml s3://bttrm-eks-cloudformationnn
upload: ./network-stack.yml to s3://bttrm-eks-cloudformation/network-stack.yml
Go back to the root-stack.json
, update its TemplateURL
– now set it as an URL to the bucket:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Resources" : {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://bttrm-eks-cloudformation.s3.amazonaws.com/network-stack.yml"
}
}
}
}
Create the root stack:
$ aws cloudformation create-stack --stack-name nested-stacks-root --template-body file://root-stack.json --profile arseniy --region eu-west-3
{
"StackId": "arn:aws:cloudformation:eu-west-3:534****385:stack/nested-stacks-root/6450c320-57ab-11ea-be30-0a9cc8c39c1c"
}
Check it:
CloudFormation created a nested-stacks-root stack and its child stack named nested-stacks-root-VPCStack-1FTY8TI2PR2D2 with VPC, as described in the network-stack.yml
template:
AWS CloudFormation package && deploy
To avoid uploading templates manually we can use AWS CLI CloudFormation package
and deploy
options.
package
package
will copy specified files or a whole directory in an S3 bucket.
Update your root-stack.json
— replace the TemplateURL
of the VPCStack
Resouce to a local path – full or relative to the root stack’s file:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Resources" : {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml"
}
}
}
}
Pack templates and upload them to the S3:
$ aws cloudformation package --template-file root-stack.json --output-template packed-nested-stacks.json --s3-bucket bttrm-eks-cloudformation --profile arseniy --region eu-west-3 --use-json
Uploading to ce12898553365980827b9aa59a99426d.template 187 / 187.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packed-nested-stacks.json.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /home/setevoy/Work/devops/projects/EKS/roles/cloudformation/files/packed-nested-stacks.json --stack-name <YOUR STACK NAME>
Here:
- CLI uploads all files (artifacts) found in the
root-stack.json
to the AWS S3 - will update
TemplateURL
to set the S3 URL instead of the local paths - will return a newly generated template which can be used with the
deploy
option
The template returned by the package
will be saved in the packed-nested-stacks.json
(I’m using the --use-json
as by default YAML will be used, check the What is: YAML – its overview, basic data types, YAML vs JSON, and PyYAML post).
Check its content:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation Root stack",
"Resources": {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3.eu-west-3.amazonaws.com/eks-cloudformation/ce12898553365980827b9aa59a99426d.template"
}
}
}
}
And let’s see the https://s3.eu-west-3.amazonaws.com/eks-cloudformation/ce12898553365980827b9aa59a99426d.template file content:
$ aws --profile arseniy --region eu-west-3 s3 cp --quiet s3://eks-cloudformation/ce12898553365980827b9aa59a99426d.template /dev/stdout
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation Nested Network Stack
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 11.0.0.0/16
deploy
After we performed the package
command – CLI displayed a tip about the following step:
...
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /home/setevoy/Work/devops/projects/EKS/roles/cloudformation/files/packed-nested-stacks.json --stack-name <YOUR STACK NAME>
Apply it to the stack created a few steps ago:
$ aws --profile arseniy --region eu-west-3 cloudformation deploy --template-file packed-nested-stacks.json --stack-name nested-stacks-root
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - nested-stacks-root
deploy
created a ChangeSet and applied it to the root stack:
Nested stack – passing parameters
At this moment we have no any Parameters in the templates.
For example, in the root stack, we can set some global values.
Let’s add a VPC’s CIDR, which will be used by the network-stack then.
Update the root-stack.json
and add the Parameters
block with one parameter VPCCIDRBlock
and its default value:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Parameters": {
"VPCCIDRBlock": {
"Description": "VPC CidrBlock",
"Type": "String",
"Default": "11.0.0.0/16"
}
},
...
In the Resources
block for the VPCStack
resource add Parameters
section with VPCCIDRBlock
parameter where we will pass our VPCCIDRBlock
value from the “global” parameters:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Parameters": {
"VPCCIDRBlock": {
"Description": "VPC CidrBlock",
"Type": "String",
"Default": "11.0.0.0/16"
}
},
"Resources" : {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml",
"Parameters": {
"VPCCIDRBlock" : { "Ref": "VPCCIDRBlock" }
}
}
}
}
}
In the network-stack.yml
template add it to the VPC resource:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Nested Network Stack",
"Parameters": {
"VPCCIDRBlock": {
"Description": "VPC CidrBlock",
"Type": "String"
}
},
"Resources" : {
"VPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : { "Ref": "VPCCIDRBlock" }
}
}
}
}
Pack it to the S3:
$ !518
aws cloudformation package --template-file root-stack.json --output-template packed-nested-stacks.json --s3-bucket bttrm-eks-cloudformation --profile arseniy --region eu-west-3 --use-json
Deploy:
$ aws --profile arseniy --region eu-west-3 cloudformation deploy --template-file packed-nested-stacks.json --stack-name nested-stacks-root --profile arseniy --region eu-west-3 --use-json
Check the nested stack’s Parameters
:
VPCCIDRBlock
was added.
Nested stack Outputs
Also, Nested Stack allows using other stacks Outputs
via the Fn::GetAtt
function.
Let’s add a third stack named SecurityGroupStack where our SecurityGroup will be described.
We will pass the VPC ID to this SecurotyGroup using Outputs
of the network-stack.yml
stack.
Remember, that such parameters can be passed only from the “bottom” to “top” and back.
I.e. you can not pass a parameter directly from the VPCStack to the SecurityGroupStack, but you can return a value to the root stack and then use it as a parameter for a child stack.
To do so:
- in the VPCStack (
network-stack.yml
) stack we will addOutputs
to return an ID of the VPC crated - in the root stack
root-stack.json
we will describe a new stack with the SecurityGroupStack name, whichParameters
will accept a vlue from the VPCStackOutputs
(network-stack.yml
) - will create a new stack SecurityGroupStack, which template will use
Parameters
>VPCID
network-stack.yml
Stack Outputs
Add output for the VPC:
...
"Resources" : {
"VPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : { "Ref": "VPCCIDRBlock" }
}
}
},
"Outputs" : {
"VPCID" : {
"Description" : "EKS VPC ID",
"Value" : { "Ref" : "VPC" }
}
}
}
root-stack.json
Stack
In the root stack add a new stack SecurityGroupStack and add the VPCID
from the Outputs
of the VPCStack stack to Parameters
of the SecurityGroupStack:
...
"Resources" : {
"VPCStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml",
"Parameters": {
"VPCCIDRBlock" : { "Ref": "VPCCIDRBlock" }
}
}
},
"SecurityGroupStack": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "sg-stack.yml",
"Parameters": {
"VPCID" : { "Fn::GetAtt": ["VPCStack", "Outputs.VPCID"] }
}
}
}
}
...
Create a new template file for the SecurityGroups — sg-stack.yml
:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation SecurityGroups stack",
"Parameters" : {
"VPCID": {
"Description": "Network Stack VPC ID",
"Type": "String",
}
},
"Resources" : {
"SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Example SecurityGroup",
"VpcId" : { "Ref": "VPCID" },
"SecurityGroupIngress" : [
{
"Description": "Allow HTTP",
"IpProtocol" : "tcp",
"FromPort" : 80,
"ToPort" : 80,
"CidrIp" : "0.0.0.0/0"
},
{
"Description": "Allow HTTPS",
"IpProtocol" : "tcp",
"FromPort" : 443,
"ToPort" : 443,
"CidrIp" : "0.0.0.0/0"
}
]
}
},
}
}
Here, in the "VpcId" : { "Ref": "VPCID" }
, we are using a VPC ID value to add this SecirtyGroup to the same VPC.
Pack, deploy, check stacks:
A new stack was created.
Check its Parameters:
All good.
Template reuse
As already mentioned at the beginning, the main idea is modularity, when we can use the same template file to create similar resources.
Let’s say, we’d like to have two VPC with a different CIDRs.
We can use CloudFormation Mappins
and set two various network blocs there.
In the root template root-stack.json
remove the "Parameters": "VPCCIDRBlock"
and add Mappings
instead:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Root stack",
"Mappings": {
"VPCCIDRBlock": {
"vpc1": {
"cidr": "11.0.0.0/16"
},
"vpc2": {
"cidr": "12.0.0.0/16"
}
}
},
...
In the Resources
add another stack with a VPC using the same template file, but now in its Parameters
use the Fn::FindInMap
function to get a value for the Property VPCCIDRBlock
:
...
"Resources" : {
"VPCStack1": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml",
"Parameters": {
"VPCCIDRBlock" : { "Fn::FindInMap" : [ "VPCCIDRBlock", "vpc1", "cidr" ] }
}
}
},
"VPCStack2": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "network-stack.yml",
"Parameters": {
"VPCCIDRBlock" : { "Fn::FindInMap" : [ "VPCCIDRBlock", "vpc2", "cidr" ] }
}
}
},
...
Do not forget about SecurityGroup – add another one – again, using the same SG’s template sg-stack.yml
, and attach this second SecurityGroup to the second VPC:
...
"SecurityGroupStack1": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "sg-stack.yml",
"Parameters": {
"VPCID" : { "Fn::GetAtt": ["VPCStack1", "Outputs.VPCID"] }
}
}
},
"SecurityGroupStack2": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "sg-stack.yml",
"Parameters": {
"VPCID" : { "Fn::GetAtt": ["VPCStack2", "Outputs.VPCID"] }
}
}
}
...
Deploy, check:
CloudFormation removed the VPCStack and instead created two new stacks – VPCStack1 и VPCStack2, and in the same way – for the SecurityGroup.
Import/export values vs Nested Stacks
The Outputs
in nested stacks is good to share parameters between affined stacks, but it will work only for current stacks “tree” and can not be shared with a not related stack in this AWS account.
Here we can use another AWS CLoudFormatiuon feature called cross-stack reference – in a first stack you’ll create an Export
, and in an another – their Import
.
Pitfalls
- exported values are accessible within the same AWS region only
- you can’t delete a stack if it importing values used by any other stack
Add an Export for the ID of the SecurityGroups crated to make it available to use later in other, independent, stacks.
To do so, update the SecurityGroup sg-stack.yml
template and add its ID to the Outputs
:
...
"ToPort" : 443,
"CidrIp" : "0.0.0.0/0"
}
]
}
},
},
"Outputs" : {
"SecurityGroupID" : {
"Description" : "The SecurityGroup ID",
"Value" : { "Ref" : "SecurityGroup" }
}
}
}
In the root template – update its Outputs
and add the Export
for both SecurityGroupStack-stacks:
...
"Outputs" : {
"SecurityGroup1" : {
"Description" : "The SecurityGroup-1 ID",
"Value" : { "Fn::GetAtt": [ "SecurityGroupStack1", "Outputs.SecurityGroupID" ] },
"Export": { "Name": { "Fn::Sub": "${AWS::StackName}-SecurityGroupStack1" } }
},
"SecurityGroup2" : {
"Description" : "The SecurityGroup-2 ID",
"Value" : { "Fn::GetAtt": [ "SecurityGroupStack2", "Outputs.SecurityGroupID" ] },
"Export": { "Name": { "Fn::Sub": "${AWS::StackName}-SecurityGroupStack2" } }
}
}
}
Here:
- in the
Value
we are getting a SecurityGroup ID from itsOutputs
- in the
Export: Name
with theFn::Sub
we are generating an uniq name.
Deploy, check the SecurityGroup‘s stack Outputs
:
And Outputs
of the root stack – find the Exported values:
Also, they are available now in the Exports of the whole CloudFormation for this account:
Now, we can use it for other stacks.
To check it, let’s add a stack with the only VPC resource (just because a CloudFormation stack has to have at least one Resource type), and in its Outputs
using Fn::ImportValue
we will display SecurityGroups IDs from the nested-stacks-root stack:
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Nested Network stack",
"Parameters": {
"VPCCIDRBlock": {
"Description": "VPC CidrBlock",
"Type": "String",
"Default": "13.0.0.0/16"
}
},
"Resources" : {
"VPC" : {
"Type" : "AWS::EC2::VPC",
"Properties" : {
"CidrBlock" : { "Ref": "VPCCIDRBlock" }
}
}
},
"Outputs" : {
"SecurityGroup1ID" : {
"Description" : "The SecurityGroup ID",
"Value" : { "Fn::ImportValue" : "nested-stacks-root-SecurityGroupStack1" }
},
"SecurityGroup2ID" : {
"Description" : "The SecurityGroup ID",
"Value" : { "Fn::ImportValue" : "nested-stacks-root-SecurityGroupStack2" }
}
}
}
Deploy it (better was to call the new stack like an “independent-” or “external-” instead of the nested-), and check its Outputs
:
Done.
Useful links
- Working with Nested Stacks
- AWS CloudFormation Best Practices
- CloudFormation Nested Stacks Primer
- Walkthrough with Nested CloudFormation Stacks
- Understanding Nested CloudFormation Stacks
- Use Nested Stacks to Create Reusable Templates and Support Role Specialization
- CloudFormation Best-Practices
- CloudFormation package & deploy
- 7 Awesome CloudFormation Hacks
- Using CloudFormation Cross-Stack References
- How can I reference a resource in another stack from an AWS CloudFormation template?
- How do I use multiple values for individual parameters in an AWS CloudFormation template?
- How do I pass CommaDelimitedList parameters to nested stacks in AWS CloudFormation?
- Two years with CloudFormation: lessons learned
Similar posts
- 10/18/2019 AWS: Redshift – quick start and SQL-workbench connection config>
- 03/07/2018 AWS: CloudFormation – пример создания S3 корзины, IAM пользователя, Glacier Lifecycle и SNS Notification (0)
- 02/01/2018 BASH: скрипт создания AWS CloudFormation стека
- 11/27/2018 AWS: CloudFormation – S3 корзина для логов Application Load Balancer
- 09/06/2018 AWS: CloudFormation – ‘templateBody’ failed to satisfy constraint: Member must have length less than or equal to 51200
Top comments (0)