Infrastructure as a Code – Cloudformation

Cloud formation

AWS CloudFormation offers a unified language for describing and provisioning all the infrastructure resources within your environment in a secure and reproducible manner.

AWS CloudFormation enables you to define the resources required for an application or solution in a text file, formatted in JSON or YAML. This template, which can be manually uploaded or retrieved from an S3 bucket, articulates the set of resources as a cohesive unit referred to as a stack. CloudFormation subsequently interprets the template’s parameters into API calls and establishes the stack, provisioning its resources to form a functional environment. Stacks can be created, updated, and deleted through the AWS CloudFormation console, AWS API, or AWS command-line interface (CLI).

Benefits of Cloudformation

  • Simplify infrastructure management: A CloudFormation template provides a comprehensive description of all your resources and their attributes. By deploying this template as a CloudFormation stack, the service takes care of provisioning the Auto Scaling group, load balancer, database, etc on your behalf. Once the stack is successfully generated, your AWS resources are operational. Deleting the stack is just as straightforward, resulting in the removal of all resources associated with it.
  • Quickly replicate your infrastructure: If your application requires additional availability, you might replicate it in multiple regions so that if one region becomes unavailable, your users can still use your application in other regions.
  • Easily control and track changes to your infrastructure: Because templates are text files, you simply track differences in your templates to track changes to your infrastructure, similar to the way developers control revisions to source code.

How does cloud formation Work?

When creating a stack, AWS CloudFormation makes underlying service calls to AWS to provision and configure your resources. CloudFormation can only perform actions that you have permission to do. 

The calls that CloudFormation makes are all declared by your template. For example, suppose you have a template that describes an EC2 instance with a t2.micro instance type. When you use that template to create a stack, CloudFormation calls the Amazon EC2 create instance API and specifies the instance type as t2.micro.

Example YAML

AWSTemplateFormatVersion: 2010-09-09
Description: A simple EC2 instance
Resources:
  MyEC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: ami-0ff8a91507f77f867
      InstanceType: t2.micro

CloudFormation provisions and configures resources by making calls to the AWS services that are described in your template. After all the resources have been created, CloudFormation reports that your stack has been created. You can then start using the resources in your stack. If stack creation fails, CloudFormation rolls back your changes by deleting the resources that it created.

Template Basics

  • Resources:

The Resources object contains a list of resource objects. A resource declaration contains the resource’s attributes, which are themselves declared as child objects. A resource must have a Type attribute, which defines the kind of AWS resource you want to create. 

Resources:
  HelloBucket:
    Type: 'AWS::S3::Bucket'

  • Resource properties

Usually, a property for a resource is simply a string value.  Some resources can have multiple properties, and some properties can have one or more subproperties. For example, the AWS::S3::Bucket resource has two properties: AccessControl and WebsiteConfiguration. The WebsiteConfiguration property has two subproperties: IndexDocument and ErrorDocument

Resources:
  HelloBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      AccessControl: PublicRead
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html
  • Input parameters

You declare parameters in a template’s Parameters object. A parameter contains a list of attributes that define its value and constraints against its value. The only required attribute is Type, which can be String, Number, or an AWS-specific type. You can also add a Description attribute that tells a user more about what kind of value they should specify. The Fn::Ref function can be leveraged to reference parameters

Parameters:
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instance
    Type: String
Mappings:
  RegionMap:
    us-east-1:
      AMI: ami-76f0061f
    us-west-1:
      AMI: ami-655a0a20
Resources:
  Ec2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      KeyName: !Ref KeyName
      ImageId: !FindInMap 
        - RegionMap
        - !Ref 'AWS::Region'
        - AMI
      UserData: !Base64 '80'
  • conditional values

We can specify value based on a conditional input. Conditions are used to control the creation of resources or outputs
based on a condition.
The intrinsic function (logical) can be any of the following:
Fn::Equals, Fn::If, Fn::Not, Fn::Or

  • Constructed values and output values

The Outputs section declares optional outputs values that we can import into other stacks(if you export them first). They’re very useful for example if you define a network CloudFormation, and output the variables such as VPC ID and your Subnet IDs

Fn::ImportValue for cross stack references.

  • Intrensic Functions :

    Ref : The Fn::Ref function can be leveraged to reference
    Fn::GetAtt : To get the attributes of an resource
    Fn::FindInMap
    Fn::ImportValue
    Fn::Join
    Fn::Sub
    Condition Functions

Resource Documentation

  • We can’t go through all of the 224 resources, but we can learn how to use them.
  • All the resources can be found here:
    http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aw
    s-template-resource-type-ref.html
  • Then, we just read the docs

Example

We are going to create the cloud formation stack for our EKS cluster using the cloudformation template

Step1: Create VPC

---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS Sample VPC - Private and Public subnets'

Parameters:

  VpcBlock:
    Type: String
    Default: 192.168.0.0/16
    Description: The CIDR range for the VPC. This should be a valid private (RFC 1918) CIDR range.

  PublicSubnet01Block:
    Type: String
    Default: 192.168.0.0/18
    Description: CidrBlock for public subnet 01 within the VPC

  PublicSubnet02Block:
    Type: String
    Default: 192.168.64.0/18
    Description: CidrBlock for public subnet 02 within the VPC

  PrivateSubnet01Block:
    Type: String
    Default: 192.168.128.0/18
    Description: CidrBlock for private subnet 01 within the VPC

  PrivateSubnet02Block:
    Type: String
    Default: 192.168.192.0/18
    Description: CidrBlock for private subnet 02 within the VPC

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "Worker Network Configuration"
        Parameters:
          - VpcBlock
          - PublicSubnet01Block
          - PublicSubnet02Block
          - PrivateSubnet01Block
          - PrivateSubnet02Block

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:  !Ref VpcBlock
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-VPC'

  InternetGateway:
    Type: "AWS::EC2::InternetGateway"

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

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Public Subnets
        - Key: Network
          Value: Public

  PrivateRouteTable01:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Private Subnet AZ1
        - Key: Network
          Value: Private01

  PrivateRouteTable02:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Private Subnet AZ2
        - Key: Network
          Value: Private02

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

  PrivateRoute01:
    DependsOn:
      - VPCGatewayAttachment
      - NatGateway01
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable01
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway01

  PrivateRoute02:
    DependsOn:
      - VPCGatewayAttachment
      - NatGateway02
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable02
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway02

  NatGateway01:
    DependsOn:
      - NatGatewayEIP1
      - PublicSubnet01
      - VPCGatewayAttachment
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt 'NatGatewayEIP1.AllocationId'
      SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-NatGatewayAZ1'

  NatGateway02:
    DependsOn:
      - NatGatewayEIP2
      - PublicSubnet02
      - VPCGatewayAttachment
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt 'NatGatewayEIP2.AllocationId'
      SubnetId: !Ref PublicSubnet02
      Tags:
        - Key: Name
          Value: !Sub '${AWS::StackName}-NatGatewayAZ2'

  NatGatewayEIP1:
    DependsOn:
      - VPCGatewayAttachment
    Type: 'AWS::EC2::EIP'
    Properties:
      Domain: vpc

  NatGatewayEIP2:
    DependsOn:
      - VPCGatewayAttachment
    Type: 'AWS::EC2::EIP'
    Properties:
      Domain: vpc

  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Metadata:
      Comment: Subnet 01
    Properties:
      MapPublicIpOnLaunch: true
      AvailabilityZone:
        Fn::Select:
          - '0'
          - Fn::GetAZs:
              Ref: AWS::Region
      CidrBlock:
        Ref: PublicSubnet01Block
      VpcId:
        Ref: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnet01"
        - Key: kubernetes.io/role/elb
          Value: 1

  PublicSubnet02:
    Type: AWS::EC2::Subnet
    Metadata:
      Comment: Subnet 02
    Properties:
      MapPublicIpOnLaunch: true
      AvailabilityZone:
        Fn::Select:
          - '1'
          - Fn::GetAZs:
              Ref: AWS::Region
      CidrBlock:
        Ref: PublicSubnet02Block
      VpcId:
        Ref: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnet02"
        - Key: kubernetes.io/role/elb
          Value: 1

  PrivateSubnet01:
    Type: AWS::EC2::Subnet
    Metadata:
      Comment: Subnet 03
    Properties:
      AvailabilityZone:
        Fn::Select:
          - '0'
          - Fn::GetAZs:
              Ref: AWS::Region
      CidrBlock:
        Ref: PrivateSubnet01Block
      VpcId:
        Ref: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnet01"
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  PrivateSubnet02:
    Type: AWS::EC2::Subnet
    Metadata:
      Comment: Private Subnet 02
    Properties:
      AvailabilityZone:
        Fn::Select:
          - '1'
          - Fn::GetAZs:
              Ref: AWS::Region
      CidrBlock:
        Ref: PrivateSubnet02Block
      VpcId:
        Ref: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnet02"
        - Key: kubernetes.io/role/internal-elb
          Value: 1

  PublicSubnet01RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet01
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet02RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet02
      RouteTableId: !Ref PublicRouteTable

  PrivateSubnet01RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet01
      RouteTableId: !Ref PrivateRouteTable01

  PrivateSubnet02RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet02
      RouteTableId: !Ref PrivateRouteTable02

  ControlPlaneSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Cluster communication with worker nodes
      VpcId: !Ref VPC

Outputs:

  PublicSubnetIds:
    Description: Public Subnets IDs in the VPC
    Value: !Join [ ",", [ !Ref PublicSubnet01, !Ref PublicSubnet02 ] ]
    Export:
      Name: PublicSubnetIds

  PrivateSubnetIds:
    Description: Private Subnets IDs in the VPC
    Value: !Join [ ",", [ !Ref PrivateSubnet01, !Ref PrivateSubnet02 ] ]
    Export:
      Name: PrivateSubnetIds

  ControlPlaneSecurityGroupId:
    Description: Security group for the cluster control plane communication with worker nodes
    Value: !Ref ControlPlaneSecurityGroup
    Export:
      Name: ControlPlaneSecurityGroupId

  VpcId:
    Description: The VPC Id
    Value: !Ref VPC
    Export:
      Name: VpcId

aws cloudformation create-stack \
–region us-east-2 \
–stack-name tradesman-vpc \
–template-body file://vpc.yaml

Step3: Create EKS

---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Amazon EKS Cluster'

Parameters:
  ClusterName:
    Type: String
    Default: tradesman
  NumberOfWorkerNodes:
    Type: Number
    Default: 1
  WorkerNodesInstanceType:
    Type: String
    Default: t2.micro
  KubernetesVersion:
    Type: String
    Default: 1.22

Resources:

  ###########################################
  ## Roles
  ###########################################
  EksRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: tradesman.cluster.role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - eks.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  EksNodeRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: tradesman.node.role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
        - "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
        - "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
        - "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
        - "arn:aws:iam::aws:policy/AmazonRDSFullAccess"

  ###########################################
  ## Eks Cluster
  ###########################################

  EksCluster:
    Type: AWS::EKS::Cluster
    Properties:
      Name: !Ref ClusterName
      Version: !Ref KubernetesVersion
      RoleArn: !GetAtt EksRole.Arn
      ResourcesVpcConfig:
        SecurityGroupIds:
          - !ImportValue ControlPlaneSecurityGroupId
        SubnetIds: !Split [ ',', !ImportValue PrivateSubnetIds ]

  EksNodegroup:
    Type: AWS::EKS::Nodegroup
    DependsOn: EksCluster
    Properties:
      ClusterName: !Ref ClusterName
      NodeRole: !GetAtt EksNodeRole.Arn
      ScalingConfig:
        MinSize:
          Ref: NumberOfWorkerNodes
        DesiredSize:
          Ref: NumberOfWorkerNodes
        MaxSize:
          Ref: NumberOfWorkerNodes
      Subnets: !Split [ ',', !ImportValue PrivateSubnetIds ]
aws cloudformation create-stack \
  --region us-east-2 \
  --stack-name tradesman-cluster \
  --capabilities CAPABILITY_NAMED_IAM \
  --template-body file://eks.yaml

Output

error: Content is protected !!
Scroll to Top