Skip to main content
โšก Calmops

Infrastructure as Code: Terraform and CloudFormation with Python

Infrastructure as Code: Terraform and CloudFormation with Python

Infrastructure as Code (IaC) enables managing cloud infrastructure through code. This guide covers Terraform and CloudFormation with Python integration.

Infrastructure as Code Fundamentals

Why IaC?

# Benefits of IaC
benefits = {
    'version_control': 'Track infrastructure changes',
    'reproducibility': 'Create identical environments',
    'automation': 'Automate infrastructure provisioning',
    'documentation': 'Infrastructure is self-documenting',
    'testing': 'Test infrastructure changes',
    'collaboration': 'Team can review changes',
    'disaster_recovery': 'Quickly recreate infrastructure'
}

# IaC Tools
tools = {
    'terraform': 'Multi-cloud, declarative',
    'cloudformation': 'AWS-specific, JSON/YAML',
    'ansible': 'Procedural, configuration management',
    'pulumi': 'Python/Go/TypeScript, programmatic',
    'cdk': 'AWS CDK, Python/TypeScript'
}

Terraform with Python

Terraform Basics

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# Create VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "main-vpc"
  }
}

# Create subnet
resource "aws_subnet" "main" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "main-subnet"
  }
}

# Create security group
resource "aws_security_group" "main" {
  name   = "main-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Create EC2 instance
resource "aws_instance" "main" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.main.id

  tags = {
    Name = "main-instance"
  }
}

output "instance_ip" {
  value = aws_instance.main.public_ip
}

Terraform with Python (Pulumi)

import pulumi
import pulumi_aws as aws

# Create VPC
vpc = aws.ec2.Vpc("main",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True,
    tags={"Name": "main-vpc"}
)

# Create subnet
subnet = aws.ec2.Subnet("main",
    vpc_id=vpc.id,
    cidr_block="10.0.1.0/24",
    availability_zone="us-east-1a",
    tags={"Name": "main-subnet"}
)

# Create security group
security_group = aws.ec2.SecurityGroup("main",
    vpc_id=vpc.id,
    description="Main security group",
    ingress=[
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"]
        )
    ],
    egress=[
        aws.ec2.SecurityGroupEgressArgs(
            protocol="-1",
            from_port=0,
            to_port=0,
            cidr_blocks=["0.0.0.0/0"]
        )
    ]
)

# Create EC2 instance
instance = aws.ec2.Instance("main",
    ami="ami-0c55b159cbfafe1f0",
    instance_type="t2.micro",
    subnet_id=subnet.id,
    vpc_security_group_ids=[security_group.id],
    tags={"Name": "main-instance"}
)

# Export outputs
pulumi.export("instance_ip", instance.public_ip)

CloudFormation with Python

CloudFormation Template

import json
import boto3

# CloudFormation template
template = {
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "CloudFormation template for VPC and EC2",
    "Resources": {
        "MainVPC": {
            "Type": "AWS::EC2::VPC",
            "Properties": {
                "CidrBlock": "10.0.0.0/16",
                "EnableDnsHostnames": True,
                "Tags": [{"Key": "Name", "Value": "main-vpc"}]
            }
        },
        "MainSubnet": {
            "Type": "AWS::EC2::Subnet",
            "Properties": {
                "VpcId": {"Ref": "MainVPC"},
                "CidrBlock": "10.0.1.0/24",
                "AvailabilityZone": "us-east-1a",
                "Tags": [{"Key": "Name", "Value": "main-subnet"}]
            }
        },
        "MainSecurityGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "GroupDescription": "Main security group",
                "VpcId": {"Ref": "MainVPC"},
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "FromPort": 80,
                        "ToPort": 80,
                        "CidrIp": "0.0.0.0/0"
                    }
                ]
            }
        },
        "MainInstance": {
            "Type": "AWS::EC2::Instance",
            "Properties": {
                "ImageId": "ami-0c55b159cbfafe1f0",
                "InstanceType": "t2.micro",
                "SubnetId": {"Ref": "MainSubnet"},
                "SecurityGroupIds": [{"Ref": "MainSecurityGroup"}],
                "Tags": [{"Key": "Name", "Value": "main-instance"}]
            }
        }
    },
    "Outputs": {
        "InstanceIP": {
            "Value": {"Fn::GetAtt": ["MainInstance", "PublicIp"]},
            "Description": "Public IP of the instance"
        }
    }
}

# Deploy stack
cf_client = boto3.client('cloudformation', region_name='us-east-1')

def create_stack(stack_name, template):
    """Create CloudFormation stack"""
    try:
        response = cf_client.create_stack(
            StackName=stack_name,
            TemplateBody=json.dumps(template)
        )
        print(f"Stack created: {response['StackId']}")
    except Exception as e:
        print(f"Error: {e}")

def update_stack(stack_name, template):
    """Update CloudFormation stack"""
    try:
        response = cf_client.update_stack(
            StackName=stack_name,
            TemplateBody=json.dumps(template)
        )
        print(f"Stack updated: {response['StackId']}")
    except Exception as e:
        print(f"Error: {e}")

def delete_stack(stack_name):
    """Delete CloudFormation stack"""
    try:
        cf_client.delete_stack(StackName=stack_name)
        print(f"Stack deleted: {stack_name}")
    except Exception as e:
        print(f"Error: {e}")

def describe_stack(stack_name):
    """Get stack information"""
    try:
        response = cf_client.describe_stacks(StackName=stack_name)
        stack = response['Stacks'][0]
        print(f"Stack Status: {stack['StackStatus']}")
        print(f"Outputs: {stack.get('Outputs', [])}")
    except Exception as e:
        print(f"Error: {e}")

# Usage
create_stack("my-stack", template)
describe_stack("my-stack")

AWS CDK with Python

from aws_cdk import (
    aws_ec2 as ec2,
    aws_iam as iam,
    core
)

class MyStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs):
        super().__init__(scope, id, **kwargs)
        
        # Create VPC
        vpc = ec2.Vpc(self, "MainVPC",
            cidr="10.0.0.0/16",
            max_azs=2,
            nat_gateways=1
        )
        
        # Create security group
        sg = ec2.SecurityGroup(self, "MainSG",
            vpc=vpc,
            description="Main security group",
            allow_all_outbound=True
        )
        
        sg.add_ingress_rule(
            peer=ec2.Peer.any_ipv4(),
            connection=ec2.Port.tcp(80),
            description="Allow HTTP"
        )
        
        # Create EC2 instance
        instance = ec2.Instance(self, "MainInstance",
            vpc=vpc,
            instance_type=ec2.InstanceType("t2.micro"),
            machine_image=ec2.AmazonLinuxImage(
                generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
            ),
            security_group=sg,
            key_name="my-key-pair"
        )
        
        # Output
        core.CfnOutput(self, "InstanceIP",
            value=instance.instance_public_ip,
            description="Public IP of the instance"
        )

# Deploy
app = core.App()
MyStack(app, "my-stack")
app.synth()

Best Practices

  1. Version control: Store IaC in Git
  2. Modular design: Use modules/stacks for reusability
  3. State management: Manage Terraform state securely
  4. Testing: Test infrastructure changes
  5. Documentation: Document infrastructure
  6. Naming conventions: Use consistent naming
  7. Tagging: Tag all resources for organization

Common Pitfalls

Bad Practice:

# Don't: Hardcode values
template = {
    "Resources": {
        "Instance": {
            "Properties": {
                "InstanceType": "t2.micro",
                "ImageId": "ami-12345678"
            }
        }
    }
}

# Don't: No state management
# Store Terraform state locally

# Don't: Manual changes
# Modify infrastructure outside IaC

Good Practice:

# Do: Use variables
variables = {
    "instance_type": "t2.micro",
    "ami_id": "ami-0c55b159cbfafe1f0"
}

# Do: Remote state
# Store Terraform state in S3/Terraform Cloud

# Do: All changes through IaC
# Modify infrastructure only through code

Conclusion

Infrastructure as Code enables managing cloud infrastructure reliably and repeatably. Choose appropriate tools (Terraform, CloudFormation, CDK) based on requirements. Follow best practices for version control, modularity, and testing to build maintainable infrastructure.

Comments