DEV Community

loading...

Using Nested Stacks with AWS CDK

ryands17 profile image Ryan Dsouza ・5 min read

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!

GitHub logo 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))
  }
}
Enter fullscreen mode Exit fullscreen mode

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')
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
  • The next snippet adds some User Data to this instance. This installs httpd and echoes some text in an h1 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)
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, we create instances of both the BaseResources and AppResources nested stack respectively.

  • In the AppResources stack, we pass the vpc and applicationSg from the baseResource instance to the appResources 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 on baseResources as we need the VPC and Security Group from BaseResources 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
Enter fullscreen mode Exit fullscreen mode

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.

Creating of stacks in the CloudFormation UI

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.

Webpage result by viewing the EC2 public IP

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!

GitHub logo 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
Enter fullscreen mode Exit fullscreen mode

Thank you all for reading and let me know your thoughts in the comments :)

Discussion (0)

pic
Editor guide