Intro
CloudFormation Nested Stacks are great when you want to separate logical resources in your stack and reference those resources in other nested stacks.
An example of this would be creating the base resources like the VPC, Subnets, Security Groups in one nested stack and application resources like EC2 instances and Lambda functions in another. We can then reference the resources like the VPC in the EC2 and Lambda stack.
Prerequisites
This post assumes a little bit of familiarity with the CDK.
Also it's required to install the aws-cli and creating a default
profile by running aws configure
.
To follow with this post, you can clone the repo and also deploy it to see it in action!
ryands17 / cdk-nested-stacks
A basic example of how Nested Stacks work in AWS CDK
I am skipping the imports, to make the snippets shorter but they can be easily viewed in the file mentioned in the snippet.
Constructs
Base Resources
Let's start by creating the stack which will contain the base resources like the VPC and Security Group.
// lib/nested-stacks.ts
class BaseResources extends cdk.NestedStack {
vpc: ec2.Vpc
applicationSg: ec2.SecurityGroup
constructor(scope: cdk.Construct, id: string, props?: cdk.NestedStackProps) {
super(scope, id, props)
this.vpc = new ec2.Vpc(this, 'app-vpc', {
cidr: '10.0.0.0/20',
natGateways: 0,
maxAzs: 2,
enableDnsHostnames: true,
enableDnsSupport: true,
subnetConfiguration: [
{
cidrMask: 22,
name: 'public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 22,
name: 'private',
subnetType: ec2.SubnetType.ISOLATED,
},
],
})
this.applicationSg = new ec2.SecurityGroup(this, 'application-sg', {
vpc: this.vpc,
securityGroupName: 'application-sg',
})
this.applicationSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80))
}
}
Here, we have a class named BaseResources
that extends from cdk.NestedStack
as this will be the Nested Stack for our main resources. We have created a couple of properties such as vpc
and applicationSg
which will be used by the other Nested Stack i.e. our application stack.
-
The first resource that we create is the VPC. We have specified the CIDR
10.0.0.0/20
and deployed two Public and two Isolated subnets.- The advantage of using this construct is that the public subnets will automatically be configured with a route to the Internet Gateway (IGW) and be associated to the VPC which we would have need to specified if using plain CloudFormation YAML.
The next resource we create is the Security Group (SG). This SG has Ingress allowed on port 80 as this will be required by our application that created in another nested stack.
Application
Now, we shall create another nested stack that contains our EC2 instance that displays a webpage via Apache.
// lib/nested-stacks.ts
interface AppResourcesProps extends cdk.NestedStackProps {
vpc: ec2.Vpc
applicationSg: ec2.SecurityGroup
}
class AppResources extends cdk.NestedStack {
constructor(scope: cdk.Construct, id: string, props: AppResourcesProps) {
super(scope, id, props)
// The EC2 instance using Amazon Linux 2
const instance = new ec2.Instance(this, 'simple-server', {
vpc: props.vpc,
instanceName: 'simple-server',
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T2,
ec2.InstanceSize.MICRO
),
machineImage: ec2.MachineImage.latestAmazonLinux({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
}),
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
securityGroup: props.applicationSg,
})
// Display a simple webpage
instance.addUserData(
'yum install -y httpd',
'systemctl start httpd',
'systemctl enable httpd',
'echo "<h1>Hello World from $(hostname -f)</h1>" > /var/www/html/index.html'
)
// Add the policy to access EC2 without SSH
instance.role.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore')
)
}
}
We create another class AppResources
that extends cdk.NestedStack
and we also specify an interface of the values that we will be receiving from the BaseResources
stack.
-
The first snippet creates an EC2 instance from an Amazon Linux 2 image that it automatically selects. For this instance, we pass the VPC it belongs to and we will see in the final part how we connected these nested stacks.
- We have then specified it to be a type of
t2.micro
and mentioned the Security Group created in the base stack. - Lastly, we make sure that this instance is deployed in a public subnet by specifying the
vpcSubnets
property as we need to view the application we will install on this next.
- We have then specified it to be a type of
The next snippet adds some User Data to this instance. This installs
httpd
and echoes some text in anh1
tag that will be displayed when we visit the public IP of this instance.The final snippet adds the
AmazonSSMManagedInstanceCore
managed policy that will allow us to SSH into the instance without any KeyPair or allowing inbound SSH access.
Final resource
We need to combine these nested stacks and for that we need a parent stack. Let's create the final resource needed to make this work.
// lib/nested-stacks.ts
export class MainApp extends cdk.Stack {
baseResources: BaseResources
appResources: AppResources
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
this.baseResources = new BaseResources(this, 'base-resources')
const { vpc, applicationSg } = this.baseResources
this.appResources = new AppResources(this, 'app-resources', {
vpc,
applicationSg,
})
this.appResources.addDependency(this.baseResources)
}
}
Here, we create instances of both the BaseResources
and AppResources
nested stack respectively.
In the
AppResources
stack, we pass thevpc
andapplicationSg
from thebaseResource
instance to theappResources
props so in this way, the EC2 instance will be able to access the VPC and Security Group.We have also stated that
appResources
has a dependency onbaseResources
as we need the VPC and Security Group fromBaseResources
for the instance to reference.
Deploying the stack
And we're done! Now let's deploy this stack with the following command to see it in action.
yarn cdk deploy
During deployment, if you open the CloudFormation console, you will see three stacks, one would be the main stack and the other two would be the base and application stack respectively.
Also note that the BaseResources
stack will be created first due to the dependency we created via the AppResources
stack.
After deployment, let's go to our instance and open it via its public IP. We will be greeted with the following text that we added via the User Data.
Conclusion
In this post, we saw how we can use Nested Stacks to separate our resources and how we can pass resources from one nested stack to another. Here's the repo again for anybody who hasn't viewed it yet!
ryands17 / cdk-nested-stacks
A basic example of how Nested Stacks work in AWS CDK
Do not forget to destroy this stack after you're done via:
yarn cdk destroy
Thank you all for reading and let me know your thoughts in the comments :)
Top comments (2)
Thanks for the nice example!
This saved me so much time thank you!!!