Cloudformation Template

akhil mittal - Sep 19 - - Dev Community

AWS CloudFormation is a service that allows you to define and manage your AWS infrastructure as code. With CloudFormation, you can create, update, and delete AWS resources in a predictable and repeatable manner using templates written in JSON or YAML format.

Here's a detailed explanation of CloudFormation, how to use it, and a breakdown of a well-structured CloudFormation template.

What is AWS CloudFormation?

Overview
AWS CloudFormation enables you to automate and manage AWS resources, including EC2 instances, RDS databases, VPCs, IAM roles, and many other services. CloudFormation uses templates that describe the desired state of your infrastructure, and then AWS takes care of provisioning and configuring those resources.

Key Benefits
Infrastructure as Code (IaC): Define your entire AWS infrastructure in code, making it easy to version, replicate, and maintain.
Automation: Automates the creation and management of AWS resources, reducing the need for manual intervention.
Consistency: Ensures consistent setups across environments, such as dev, test, and production.
Rollback Capabilities: Automatically rolls back to the last known good state if stack creation or updates fail.

How to Use CloudFormation

Step 1: Create a CloudFormation Template
Create a template in JSON or YAML that describes the AWS resources you want to create. This file acts as the blueprint for your infrastructure.

Step 2: Upload the Template to AWS CloudFormation
You can create a stack using the AWS Management Console, AWS CLI, or AWS SDKs. A stack is a collection of AWS resources you create and manage as a single unit.

Step 3: CloudFormation Provisions Resources
CloudFormation provisions and configures your AWS resources based on the template.

Step 4: Monitor and Manage the Stack
You can monitor the stack's progress through the AWS Console, review events, and check the status of the resources being provisioned.

Step 5: Update or Delete the Stack
If you need to make changes, update the template and use CloudFormation to apply the changes. When resources are no longer needed, you can delete the stack to remove all associated resources.

Detailed Walkthrough of a Complex CloudFormation Template

Below is a detailed CloudFormation template example with explanations of each section. This template sets up a highly available web application stack, including VPC, subnets, an Auto Scaling group with an Application Load Balancer, and an RDS database.

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Complex CloudFormation template for a highly available web application stack with VPC, ALB, Auto Scaling, and RDS.

Parameters:
  EnvironmentName:
    Type: String
    Default: production
    Description: The environment name (e.g., dev, staging, production).

  VpcCIDR:
    Type: String
    Default: 10.0.0.0/16
    Description: CIDR block for the VPC.

  PublicSubnet1CIDR:
    Type: String
    Default: 10.0.1.0/24
    Description: CIDR block for the first public subnet.

  PublicSubnet2CIDR:
    Type: String
    Default: 10.0.2.0/24
    Description: CIDR block for the second public subnet.

  DBInstanceType:
    Type: String
    Default: db.t3.micro
    AllowedValues: 
      - db.t2.micro
      - db.t3.micro
      - db.t3.small
      - db.t3.medium
    Description: RDS DB instance type.

  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access.
    Type: AWS::EC2::KeyPair::KeyName

  DBUsername:
    NoEcho: true
    Description: The database admin account username.
    Type: String
    MinLength: 1
    MaxLength: 16
    AllowedPattern: "[a-zA-Z][a-zA-Z0-9]*"
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.

  DBPassword:
    NoEcho: true
    Description: The database admin account password.
    Type: String
    MinLength: 8
    MaxLength: 41
    AllowedPattern: "[a-zA-Z0-9]*"
    ConstraintDescription: must contain only alphanumeric characters.

Mappings:
  RegionMap:
    us-east-1:
      AMI: ami-0c55b159cbfafe1f0
    us-west-2:
      AMI: ami-0bdb828fd58c52235

Conditions:
  CreateProdResources: !Equals [ !Ref EnvironmentName, "production" ]

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-VPC"

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      AvailabilityZone: !Select [ 0, !GetAZs "" ]
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-PublicSubnet1"

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnet2CIDR
      MapPublicIpOnLaunch: true
      AvailabilityZone: !Select [ 1, !GetAZs "" ]
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-PublicSubnet2"

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-IGW"

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-PublicRT"

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  SubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  SubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub "${EnvironmentName}-ALB"
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Scheme: internet-facing
      SecurityGroups:
        - !Ref ALBSecurityGroup
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '60'

  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP and HTTPS traffic to the ALB
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-ALB-SG"

  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub "${EnvironmentName}-LaunchTemplate"
      LaunchTemplateData:
        InstanceType: t3.micro
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref InstanceSecurityGroup
        ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", AMI ]

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt LaunchTemplate.LatestVersionNumber
      MinSize: '1'
      MaxSize: '3'
      DesiredCapacity: '2'
      TargetGroupARNs:
        - !Ref ALBTargetGroup

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      VpcId: !Ref VPC
      Port: 80
      Protocol: HTTP
      TargetType: instance
      HealthCheckPath: /
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 2

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable SSH access and HTTP from ALB
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-Instance-SG"

  RDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceClass: !Ref DBInstanceType
      Engine: mysql
      EngineVersion: '8.0'
      MasterUsername: !Ref DBUsername
      MasterUserPassword: !Ref DBPassword
      AllocatedStorage: '20'
      BackupRetentionPeriod: 7
      VPCSecurityGroups:
        - !Ref RDSSecurityGroup
      DBSubnetGroupName: !Ref DBSubnetGroup
      MultiAZ: !If [ CreateProdResources, true, false ]
      StorageEncrypted: true

  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow MySQL access from app instances
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref InstanceSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-RDS-SG"

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnets available for the RDS DB Instance
      SubnetIds:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub "${EnvironmentName}-DBSubnetGroup"

Outputs:
  ALBEndpoint:
    Description: The endpoint URL of the Application Load Balancer.
    Value: !GetAtt ALB.DNSName

  DBEndpoint:
    Description: The endpoint address of the RDS instance.
    Value: !GetAtt RDSInstance.Endpoint.Address

Enter fullscreen mode Exit fullscreen mode

Detailed Explanation of the Template Sections
1) AWSTemplateFormatVersion: Specifies the template version. The format version 2010-09-09 is commonly used and supports all modern features.

2) Description: Provides a description of what the template does. This is useful for documentation purposes.

3) Metadata: Defines the UI experience when the template is used in the AWS Management Console. It can include information like grouping parameters for easier input.

4) Parameters: Defines inputs for the template, allowing it to be reusable. For example, VpcCIDR allows users to specify a different IP range for the VPC.

5) Mappings: Defines static values for different environments or regions. Here, RegionMap provides AMI IDs for different AWS regions.

6) Resources: The core of the template where all AWS resources are defined. In this example:
A VPC, subnet, internet gateway, and route table are created.
An EC2 instance is launched with SSH access enabled through a security group.

7) Outputs: Defines values to be returned after the stack is created, such as the instance ID and public IP address, which can be useful for further automation or integration.

Components of a Complex CloudFormation Template
A complex CloudFormation template is typically used to provision sophisticated and large-scale infrastructure, which may include:

1) Multiple VPCs and Subnets: Setting up multiple Virtual Private Clouds with associated subnets for isolated network setups.

2) Application Load Balancers and Auto Scaling: Configuring load balancers and auto-scaling groups to manage incoming traffic and scale resources automatically.

3) RDS Databases: Setting up relational databases with backups, encryption, and multi-AZ deployments.

4) IAM Roles, Policies, and Security Groups: Managing access and permissions with Identity and Access Management (IAM) and defining security groups for resource access control.

5) Nested Stacks: Using nested stacks to break down the main stack into manageable and reusable sub-stacks.

6) Custom Resources and Lambda Functions: Extending CloudFormation capabilities with custom logic executed through AWS Lambda.Custom resources in AWS CloudFormation allow you to extend the capabilities of CloudFormation beyond its built-in resource types. This is particularly useful when you need to manage resources not directly supported by CloudFormation, or when you need to perform custom actions during the stack's lifecycle, such as configuration management, integrations, or provisioning third-party services.
Example Template:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template with a custom resource for creating a DNS record.

Parameters:
  DomainName:
    Type: String
    Description: The domain name to create a DNS record for.

  RecordValue:
    Type: String
    Description: The IP address or DNS value for the record.

Resources:
  # Lambda Execution Role
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties: 
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - lambda.amazonaws.com
            Action: 
              - sts:AssumeRole
      Policies:
        - PolicyName: DNSUpdatePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"
              - Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                  - route53:ListHostedZones
                Resource: "*"

  # Lambda Function to Handle Custom Resource
  CustomDNSHandlerFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import json
          import boto3
          import urllib3
          import cfnresponse

          def handler(event, context):
              response_data = {}
              try:
                  print("Received event: " + json.dumps(event, indent=2))
                  request_type = event['RequestType']
                  domain_name = event['ResourceProperties']['DomainName']
                  record_value = event['ResourceProperties']['RecordValue']

                  # Example of processing Create, Update, Delete
                  if request_type == 'Create':
                      # Code to create the DNS record
                      print(f"Creating DNS record for {domain_name} with value {record_value}")
                  elif request_type == 'Update':
                      # Code to update the DNS record
                      print(f"Updating DNS record for {domain_name} with value {record_value}")
                  elif request_type == 'Delete':
                      # Code to delete the DNS record
                      print(f"Deleting DNS record for {domain_name}")

                  # Sending success response
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              except Exception as e:
                  print(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, response_data)

      Runtime: python3.9
      Timeout: 60

  # Custom Resource
  CustomDNSRecord:
    Type: Custom::DNSRecord
    Properties:
      ServiceToken: !GetAtt CustomDNSHandlerFunction.Arn
      DomainName: !Ref DomainName
      RecordValue: !Ref RecordValue

Outputs:
  DNSRecordStatus:
    Description: Status of the DNS record creation.
    Value: !GetAtt CustomDNSRecord.Status

Enter fullscreen mode Exit fullscreen mode

7) Conditions, Mappings, and Dynamic References: Controlling resource creation with conditions and dynamically setting resource properties based on input parameters or other AWS data.
Dynamic referencing in AWS CloudFormation allows you to securely and dynamically retrieve values from AWS services, such as AWS Systems Manager Parameter Store, AWS Secrets Manager, or any other resources during stack creation or updates. This approach helps keep sensitive information secure and minimizes the need for hardcoding values in your CloudFormation templates.
Example:

AWSTemplateFormatVersion: '2010-09-09'
Description: Example of dynamic referencing SSM parameters in CloudFormation.

Parameters:
  AppEnvironment:
    Type: String
    Default: dev
    Description: The environment for the application (dev, staging, prod).

Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t2.micro
      ImageId: !Ref AmiId
      KeyName: !Sub "{{resolve:ssm:/my-app/${AppEnvironment}/key-name}}"
      Tags:
        - Key: Name
          Value: !Sub "EC2-${AppEnvironment}"

Outputs:
  InstanceId:
    Description: The ID of the created EC2 instance.
    Value: !Ref MyEC2Instance

Enter fullscreen mode Exit fullscreen mode

Best Practices for Complex CloudFormation Templates

Use Parameters for Reusability: Use parameters to make the template configurable for different environments (e.g., development, testing, production).

Implement Conditions: Use conditions to control resource creation based on environment type or input values, optimizing costs and performance.

Modularize with Nested Stacks: Break down complex templates into nested stacks to manage different parts of the infrastructure separately, improving maintainability and reusability.
Nested stacks in AWS CloudFormation are stacks created as part of other stacks. They enable you to manage complex cloud architectures by breaking down large, monolithic templates into smaller, reusable, and more manageable templates. Nested stacks make CloudFormation templates more modular, easier to maintain, and reusable across different environments or projects.
Example Template:

AWSTemplateFormatVersion: '2010-09-09'
Description: Main template that sets up nested stacks for a web application.

Parameters:
  EnvironmentName:
    Type: String
    Description: Name of the environment (e.g., dev, prod).

Resources:
  # Network Stack
  NetworkStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/network-stack.yaml
      Parameters:
        EnvironmentName: !Ref EnvironmentName

  # Security Stack
  SecurityStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/security-stack.yaml
      Parameters:
        VPCId: !GetAtt NetworkStack.Outputs.VPCId
        PublicSubnetIds: !GetAtt NetworkStack.Outputs.PublicSubnets

  # Application Stack
  ApplicationStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: https://s3.amazonaws.com/my-bucket/application-stack.yaml
      Parameters:
        VPCId: !GetAtt NetworkStack.Outputs.VPCId
        SecurityGroupId: !GetAtt SecurityStack.Outputs.AppSecurityGroupId
        PublicSubnetIds: !GetAtt NetworkStack.Outputs.PublicSubnets
        EnvironmentName: !Ref EnvironmentName

Outputs:
  ApplicationURL:
    Description: URL of the application.
    Value: !GetAtt ApplicationStack.Outputs.AppURL

Enter fullscreen mode Exit fullscreen mode

Use Mappings for Region-specific Data: Use mappings to handle region-specific configurations such as AMI IDs or instance types.

Implement Security Best Practices: Define least privilege security groups and IAM roles, and use NoEcho for sensitive parameters like passwords.

Outputs for Useful Information: Use outputs to provide useful information about the stack, such as endpoints, ARNs, and resource IDs.

This detailed walkthrough covers key aspects of creating a robust CloudFormation template for complex, highly available applications in AWS.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .