• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Infrastructure as Code: Provisioning Secure Python Clusters on AWS Using Terraform

Infrastructure as Code: Provisioning Secure Python Clusters on AWS Using Terraform

Terraform Provider Configuration for AWS and Security Groups

To provision secure Python clusters on AWS using Terraform, we begin by defining the AWS provider and essential security configurations. This involves setting up the AWS region and configuring security groups to restrict network access to our cluster instances. We’ll create a dedicated security group for the cluster nodes, allowing inbound traffic only on necessary ports (e.g., SSH for management, and potentially application-specific ports).

The following Terraform code snippet illustrates this initial setup:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1" # Replace with your desired AWS region
}

resource "aws_security_group" "python_cluster_sg" {
  name        = "python-cluster-sg"
  description = "Allow SSH and application traffic to Python cluster nodes"
  vpc_id      = aws_vpc.main.id # Assuming a VPC resource named 'main' is defined elsewhere

  ingress {
    description = "SSH access from bastion host"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["<BASTION_HOST_CIDR>"] # Restrict SSH to your bastion host's IP/CIDR
  }

  # Add ingress rules for your application ports here
  # ingress {
  #   description = "Application port 8000"
  #   from_port   = 8000
  #   to_port     = 8000
  #   protocol    = "tcp"
  #   cidr_blocks = ["0.0.0.0/0"] # Example: Allow from anywhere, refine as needed
  # }

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

  tags = {
    Name = "python-cluster-sg"
  }
}

# Placeholder for VPC resource if not already defined
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true

  tags = {
    Name = "python-cluster-vpc"
  }
}

EC2 Instance Configuration for Python Cluster Nodes

Next, we define the EC2 instances that will form our Python cluster. For production environments, it’s crucial to use hardened Amazon Machine Images (AMIs) and configure instances with appropriate instance types based on workload requirements. We’ll associate the previously defined security group and specify an IAM role for enhanced security and access to other AWS services.

The following code defines a single EC2 instance. For a cluster, you would typically use a count or for_each meta-argument to provision multiple instances.

resource "aws_instance" "python_node" {
  ami           = "ami-0c55b159cbfafe1f0" # Example: Amazon Linux 2 AMI (us-east-1). Find the latest for your region.
  instance_type = "t3.medium"           # Choose instance type based on workload
  subnet_id     = aws_subnet.public.id # Assuming a public subnet resource named 'public' is defined

  vpc_security_group_ids = [aws_security_group.python_cluster_sg.id]
  key_name               = "your-ssh-key-pair-name" # Replace with your EC2 key pair name

  iam_instance_profile = aws_iam_instance_profile.python_cluster_profile.name

  user_data = base64encode(templatefile("${path.module}/scripts/setup_python_cluster.sh", {
    cluster_name = "my-python-cluster"
    # Add any other variables needed by your setup script
  }))

  tags = {
    Name        = "python-node-${count.index}" # If using count
    Cluster     = "my-python-cluster"
    Environment = "production"
  }

  # If using count for multiple instances:
  # count = 3
}

# Placeholder for Subnet resource if not already defined
resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "us-east-1a" # Replace with your desired AZ

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

IAM Role and Instance Profile for Secure Access

To enable your Python cluster nodes to interact securely with other AWS services (e.g., S3 for storing logs, RDS for databases), an IAM role and instance profile are essential. This avoids hardcoding AWS credentials on the instances.

resource "aws_iam_role" "python_cluster_role" {
  name = "python-cluster-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "python_cluster_s3_access" {
  role       = aws_iam_role.python_cluster_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" # Example: Grant read-only access to S3. Adjust as needed.
}

# Add more policy attachments for other AWS services as required.
# For example, for CloudWatch Logs:
# resource "aws_iam_role_policy_attachment" "python_cluster_cwl_access" {
#   role       = aws_iam_role.python_cluster_role.name
#   policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess" # Adjust permissions
# }

resource "aws_iam_instance_profile" "python_cluster_profile" {
  name = "python-cluster-instance-profile"
  role = aws_iam_role.python_cluster_role.name
}

User Data Script for Python Environment Setup

The user_data script is executed when an EC2 instance first launches. This script is critical for bootstrapping the Python environment, installing necessary packages, configuring the application, and starting services. For a cluster, this script might also handle node discovery and registration.

Here’s an example of a setup_python_cluster.sh script. This is a simplified example; a production setup would involve more robust configuration management (e.g., Ansible, Chef, Puppet) or containerization.

#!/bin/bash
set -euxo pipefail

# Update system packages
sudo yum update -y

# Install Python 3 and pip
sudo yum install -y python3 python3-pip

# Install virtual environment tools
sudo pip3 install virtualenv

# Create a virtual environment
VENV_DIR="/opt/python_cluster_venv"
sudo mkdir -p $VENV_DIR
sudo chown ec2-user:ec2-user $VENV_DIR
virtualenv $VENV_DIR/bin/activate
source $VENV_DIR/bin/activate

# Install application dependencies
# Replace with your actual requirements.txt or pip install commands
pip install flask gunicorn requests boto3

# Copy application code (assuming it's baked into the AMI or fetched from S3/Git)
# Example: If your application code is in /opt/app on the AMI
# sudo cp -r /opt/app/* $VENV_DIR/app/
# cd $VENV_DIR/app/

# Configure application (e.g., environment variables, config files)
# Example: Set environment variables
# export DATABASE_URL="your_db_connection_string"

# Start the application using Gunicorn
# This is a basic example. For production, consider systemd services.
# Replace 'your_app_module:app' with your actual WSGI application entry point.
# gunicorn --workers 4 --bind 0.0.0.0:8000 your_app_module:app &

# For production, create a systemd service file:
# sudo tee /etc/systemd/system/python-cluster-app.service <<EOF
# [Unit]
# Description=Python Cluster Application
# After=network.target
#
# [Service]
# User=ec2-user
# Group=ec2-user
# WorkingDirectory=$VENV_DIR/app
# ExecStart=$VENV_DIR/bin/python $VENV_DIR/bin/gunicorn --workers 4 --bind 0.0.0.0:8000 your_app_module:app
# Restart=always
#
# [Install]
# WantedBy=multi-user.target
# EOF
#
# sudo systemctl daemon-reload
# sudo systemctl enable python-cluster-app.service
# sudo systemctl start python-cluster-app.service

echo "Python cluster node setup complete."

Load Balancer and Auto Scaling for Scalability and Resilience

To ensure high availability and scalability, we integrate an Application Load Balancer (ALB) and an Auto Scaling Group (ASG). The ALB distributes incoming traffic across healthy instances, while the ASG automatically adjusts the number of instances based on defined metrics (e.g., CPU utilization).

# Application Load Balancer
resource "aws_lb" "python_cluster_alb" {
  name               = "python-cluster-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb_sg.id] # Define a separate SG for ALB
  subnets            = [aws_subnet.public.id, aws_subnet.private.id] # Use public subnets for internet-facing ALB

  tags = {
    Name = "python-cluster-alb"
  }
}

resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.python_cluster_alb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.python_cluster_tg.arn
  }
}

resource "aws_lb_target_group" "python_cluster_tg" {
  name     = "python-cluster-tg"
  port     = 8000 # The port your application listens on
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    path                = "/health" # Define a health check endpoint in your app
    protocol            = "HTTP"
    matcher             = "200"
    interval            = 30
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 2
  }

  tags = {
    Name = "python-cluster-tg"
  }
}

# Security Group for ALB (allowing HTTP/HTTPS from anywhere)
resource "aws_security_group" "alb_sg" {
  name        = "alb-sg"
  description = "Allow HTTP and HTTPS inbound traffic to ALB"
  vpc_id      = aws_vpc.main.id

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

  # Add HTTPS listener if needed
  # ingress {
  #   description = "HTTPS from anywhere"
  #   from_port   = 443
  #   to_port     = 443
  #   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"]
  }

  tags = {
    Name = "alb-sg"
  }
}

# Auto Scaling Group
resource "aws_launch_configuration" "python_cluster_lc" {
  name_prefix                 = "python-cluster-lc-"
  image_id                    = "ami-0c55b159cbfafe1f0" # Same AMI as EC2 instance
  instance_type               = "t3.medium"
  security_groups             = [aws_security_group.python_cluster_sg.id]
  key_name                    = "your-ssh-key-pair-name" # Replace with your EC2 key pair name
  iam_instance_profile        = aws_iam_instance_profile.python_cluster_profile.name

  user_data = base64encode(templatefile("${path.module}/scripts/setup_python_cluster.sh", {
    cluster_name = "my-python-cluster"
  }))

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "python_cluster_asg" {
  name                      = "python-cluster-asg"
  launch_configuration      = aws_launch_configuration.python_cluster_lc.name
  min_size                  = 2
  max_size                  = 5
  desired_capacity          = 3
  vpc_zone_identifier       = [aws_subnet.public.id, aws_subnet.private.id] # Distribute across subnets

  target_group_arns         = [aws_lb_target_group.python_cluster_tg.arn]
  health_check_type         = "ELB"
  health_check_grace_period = 300 # Give instances time to start up

  tags = [
    {
      key                 = "Name"
      value               = "python-node"
      propagate_at_launch = true
    },
    {
      key                 = "Cluster"
      value               = "my-python-cluster"
      propagate_at_launch = true
    }
  ]
}

# Example Scaling Policy (Scale out when CPU is high)
resource "aws_autoscaling_policy" "scale_out_cpu" {
  name                   = "scale-out-cpu"
  scaling_adjustment     = 1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.python_cluster_asg.name
}

resource "aws_cloudwatch_metric_alarm" "cpu_high_alarm" {
  alarm_name          = "python-cluster-cpu-high"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "70"
  alarm_description   = "This metric monitors cpu utilization for python cluster."
  alarm_actions       = [aws_autoscaling_policy.scale_out_cpu.id]

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.python_cluster_asg.name
  }
}

# Example Scaling Policy (Scale in when CPU is low)
resource "aws_autoscaling_policy" "scale_in_cpu" {
  name                   = "scale-in-cpu"
  scaling_adjustment     = -1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.python_cluster_asg.name
}

resource "aws_cloudwatch_metric_alarm" "cpu_low_alarm" {
  alarm_name          = "python-cluster-cpu-low"
  comparison_operator = "LessThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "30"
  alarm_description   = "This metric monitors cpu utilization for python cluster."
  alarm_actions       = [aws_autoscaling_policy.scale_in_cpu.id]

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.python_cluster_asg.name
  }
}

Deployment and Management Workflow

The typical workflow for deploying and managing this infrastructure involves the following steps:

  • Initialize Terraform: Run terraform init in your project directory to download the AWS provider and any other necessary modules.
  • Plan Changes: Execute terraform plan to review the infrastructure changes Terraform will make. This is a crucial step for verifying that the planned actions align with your expectations and security policies.
  • Apply Changes: Run terraform apply to provision the AWS resources as defined in your Terraform configuration. You will be prompted to confirm the changes.
  • Accessing the Cluster: Once deployed, you can access your cluster nodes via SSH through a bastion host (if configured) using the key pair specified. The ALB’s DNS name will be the entry point for your application.
  • Updates and Modifications: To update the infrastructure (e.g., change instance types, modify security groups, update AMIs), modify your Terraform files and re-run terraform plan and terraform apply.
  • Destroying Resources: To tear down the entire infrastructure, run terraform destroy. This will deprovision all AWS resources created by Terraform, preventing orphaned resources and unexpected costs.

This comprehensive Terraform setup provides a robust, secure, and scalable foundation for deploying Python applications on AWS. Remember to adapt AMIs, instance types, security group rules, IAM policies, and user data scripts to your specific application requirements and security best practices.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala