htadev.me

Stack For Beginner In CDK AWS

December 29, 2024 | by [email protected]

stack

Stack

In AWS CDK (Cloud Development Kit), a Stack is a unit of deployment that contains one or more AWS resources defined as part of your infrastructure. A stack corresponds to an AWS CloudFormation stack and is the primary way of organizing and deploying your AWS resources using AWS CDK.

Example

An AWS CDK application can have one or more Stacks, for example:

from aws_cdk import App

app = App()

MyFirstStack(app, "stack1")
MySecondStack(app, "stack2")

app.synth()

List add stacks. you can use the command: cdk ls

stack1
stack2

When we want to run the synth command to create an AWS CloudFormation for a Stack application, pass in the name of the Stack:

cdk synth stack1

Key Concepts of a Stack in AWS CDK

  1. Logical Grouping of Resources:
    • A stack is a group-related resource logically.
    • For example, all resources for a web application backend (like Lambda functions, DynamoDB tables, and API Gateway endpoints) can be grouped into a single stack.
  2. Unit of Deployment:
    • When you deploy a CDK application, each stack in your application is first synthesized into a CloudFormation template. Then, we will deploy it as an independent CloudFormation stack. It ensures a clear separation of resources for efficient management.
  3. Scope and Isolation:
    • Each stack operates within its scope and remains isolated from other stacks unless you explicitly link them. This helps in managing resources and avoiding unintended changes.
  4. Supports Cross-Stack References:
    • You can reference resources in one stack from another stack by using cross-stack outputs and imports.
  5. Defined in Code:
    • A stack is a class that extends the Stack class in AWS CDK. Within the class, you define resources as constructs.

When to Use Multiple Stacks

  • Separation of Concerns: Divide resources into logical groups (e.g., frontend and backend).
  • Resource Limits: CloudFormation has a limit of 500 resources per stack. Use multiple stacks if you exceed this limit.
  • Ease of Deployment: Update specific parts of your infrastructure without affecting others.

Benefits of Using Stacks

  1. Reusable: You can parameterize stacks and reuse them across multiple environments, such as dev, test, and prod.
  2. Modular: Simplifies management by dividing infrastructure into smaller, manageable units.
  3. Efficient Deployment: The system updates only the modified stacks during deployment, ensuring faster and more efficient changes.

Example

Create multiple environments(Dev, Prod)

With AWS CDK and Stacks, you can easily structure resources for different environments. For example, consider an application requiring infrastructure deployment for both development and production environments. The infrastructure consists of three Stacks:

  1. Application Stack
  2. Monitoring Stack
  3. CI/CD Stack

In the development environment, you might omit the Monitoring Stack to save costs. CDK makes it straightforward to create infrastructure for both environments with these requirements. You can define three functions to handle the three Stacks, as shown below:

from aws_cdk import (
    App,
    Stack,
    Environment
)
from constructs import Construct

class MonitoringStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

class AppStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

class CICDStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

def get_env() -> Environment:
    return None

app = App()

app.synth()

Next, we write another function to combine the 3 Stacks above and use the if statement to compare if it is prod then we will create the Monitoring Stack:

@dataclass
class ServiceProps:
    prod: bool = False

class Service(Stage):
    def __init__(self, scope: Construct, construct_id: str, props: ServiceProps = None, **kwargs):
        super().__init__(scope, construct_id, 
            env=get_env(),
            **kwargs
        )

        # Create monitoring stack only in production
        if props and props.prod:
            MonitoringStack(self, "monitoring")

        # Create app and CICD stacks always
        AppStack(self, "app")
        CICDStack(self, "cicd")

Update main function

from aws_cdk import (
    App,
    Stack,
    Stage,
    Environment
)
from constructs import Construct
from dataclasses import dataclass

@dataclass
class ServiceProps:
    prod: bool = False

class MonitoringStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

class AppStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

class CICDStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

class Service(Stage):
    def __init__(self, scope: Construct, construct_id: str, props: ServiceProps = None, **kwargs) -> None:
        super().__init__(scope, construct_id, 
            env=get_env(),
            **kwargs
        )

        if props and props.prod:
            MonitoringStack(self, "monitoring")

        AppStack(self, "app")
        CICDStack(self, "cicd")

def get_env() -> Environment:
    return None

# Initialize the CDK app
app = App()

# Create dev and prod services
Service(app, "dev")  # No props means prod=False by default
Service(app, "prod", ServiceProps(prod=True))

app.synth()

Run cdk ls to view all stacks

dev/app
dev/cicd
prod/app
prod/cicd
prod/monitoring

Use Construct between Stacks

One common organizational challenge when using Infrastructure as Code (IaC) tools is defining shared resources that can be used across different infrastructures.

For example, consider the following scenario: your project is built on AWS with a microservices architecture, and you decide to use Terraform for infrastructure management. You create a source code repository for each service and write the necessary infrastructure code. Typically, you would create an AWS VPC (Virtual Private Cloud) first, and then other resources within that VPC.

As the project grows, you add a new service. You create another source code repository and write the infrastructure code for the new service, but you realize that the new service needs to reside in the same VPC as the previous service. At this point, the problem arises: how can you reuse the existing VPC?

Solution

Firstly, If you don’t set up CDK AWS. You can go to the link

Create a stack folder with 3 files as follows:

└── stack
    ├── globalStack.py
    ├── userService.py
    └── postService.py

Code in globalStack.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2
)
from constructs import Construct
from dataclasses import dataclass


@dataclass
class GlobalStackResource:
    vpc: ec2.IVpc


class GlobalStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        # Create Vpc
        vpc = ec2.Vpc(self, "MyVpc", max_azs=2)
        # Store resources that need to be accessed by other stacks
        self.resource = GlobalStackResource(vpc=vpc)

    def get_resource(self):
        return self.resource

Code in userService.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2
)
from constructs import Construct
from dataclasses import dataclass


@dataclass
class UserServiceStackProps:
    vpc: ec2.IVpc


class UserServiceStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, props: UserServiceStackProps, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # EC2 Instance
        ec2.Instance(
            self, "Server",
            instance_type=ec2.InstanceType("t3.micro"),
            machine_image=ec2.AmazonLinuxImage(
                generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
            ),
            vpc=props.vpc
        )

Code in postService.py

from aws_cdk import (
    Stack,
    aws_ec2 as ec2
)
from constructs import Construct
from dataclasses import dataclass


@dataclass
class PostServiceStackProps:
    vpc: ec2.IVpc


class PostServiceStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, props: PostServiceStackProps, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Ec2 Instance
        ec2.Instance(
            self, "Server",
            instance_type=ec2.InstanceType("t3.micro"),
            machine_image=ec2.AmazonLinuxImage(
                generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
            ),
            vpc=props.vpc
        )

Code in App.py

# #!/usr/bin/env python3
from aws_cdk import (
    App,
    Environment
)
from stacks.globalStack import GlobalStack
from stacks.userService import UserServiceStack, UserServiceStackProps
from stacks.postService import PostServiceStack, PostServiceStackProps


def get_env() -> Environment:
    return None


# Initialize the CDK app
app = App()

# Create Global Stack and get its resources
global_stack = GlobalStack(
    app, "GlobalStack",
    env=get_env()
)

global_resources = global_stack.get_resource()

# Create User Service Stack
UserServiceStack(
    app, "UserServiceStack",
    props=UserServiceStackProps(
        vpc=global_resources.vpc
    ),
    env=get_env()
)

# Create Post Service Stack
PostServiceStack(
    app, "PostServiceStack",
    props=PostServiceStackProps(
        vpc=global_resources.vpc
    ),
    env=get_env()
)

app.synth()

Run cdk ls

GlobalStack
UserServiceStack
PostServiceStack

Finally, run the deploy command to create the resources. Note that since both UserServiceStack and PostServiceStack depend on the resources in GlobalStack, you need to create GlobalStack first.

You can run cdk deploy --all to let CDK automatically create the infrastructure in the correct order. However, in real-world scenarios, it’s recommended to deploy each Stack individually for greater control and to ensure a reliable deployment process.

Summary

AWS CDK Stacks logically group and deploy AWS resources, corresponding to CloudFormation stacks. They enable modular, reusable, and efficient infrastructure management, supporting resource isolation and cross-stack references.

Use multiple stacks to separate concerns, manage resource limits, and streamline deployments across environments like dev and prod.

References

https://github.com/aws-samples/aws-cdk-examples

RELATED POSTS

View all

view all