AWS IAM EC2 Load Balancing: Build Scalable Systems

AWS IAM, EC2, Load Balancing, and Auto Scaling: Complete Guide for Developers

Introduction

Building scalable, secure applications on AWS requires mastering four foundational services: Identity and Access Management (IAM), Elastic Compute Cloud (EC2), Application Load Balancer (ALB), and Auto Scaling Groups (ASG). Together, these services form the backbone of cloud infrastructure, enabling secure access control, flexible compute resources, intelligent traffic distribution, and automatic scaling.

This guide provides a comprehensive deep dive into each service with practical architecture diagrams, real-world code examples, and production-proven best practices. Whether you're preparing for the AWS Certified Developer Associate exam or building production systems, understanding how these services interact is essential for creating resilient, cost-effective cloud applications.

We'll explore IAM policies with JSON examples, EC2 instance configuration with user data scripts, load balancer routing strategies, and auto-scaling policies that adapt to real-world traffic patterns. By the end, you'll have a complete mental model of how to architect secure, scalable AWS applications.


AWS Developer Certification Series

📚 View Complete AWS Developer Certification Guide - Master all 7 parts with our comprehensive learning path.

This is Part I (Current Article) of our comprehensive 7-part AWS developer guide:

  1. Part I: IAM EC2 & Auto Scaling (Current Article)
  2. Part II: RDS Aurora & DynamoDB
  3. Part III: SQS SNS & Kinesis
  4. Part IV: Lambda API Gateway
  5. Part V: ECS Fargate & IaC
  6. Part VI: Cognito KMS Security
  7. Part VII: CodePipeline & Monitoring

Part II: RDS Aurora & DynamoDB →


AWS Identity and Access Management (IAM)

Understanding IAM Architecture

IAM controls who can access what in your AWS account. Instead of sharing root credentials or creating multiple AWS accounts, IAM enables fine-grained access control through users, groups, roles, and policies.

   IAM Architecture   

   create   

   create   

   create   

   belongs to   

   belongs to   

   belongs to   

   attached   

   attached   

   attached   

Root:

Full Access  

Group:

Admin  

Group:

Developer  

Group:

Operations  

User:

Alice  

User:

Bob  

User:

Carol  

Policy:

EC2 Full Access  

Policy:

S3 Read Only  

Policy:

CloudWatch Logs  

Key Principles:

  1. Never use root account for daily operations—only for account setup and billing
  2. One physical person = One IAM user (no shared credentials)
  3. Users inherit permissions from groups (easier management)
  4. Policies define permissions using JSON documents
  5. Principle of least privilege (grant minimum necessary permissions)

IAM Policy Structure

IAM policies are JSON documents that define permissions. Understanding policy anatomy is critical for security.

Example: S3 Read-Only Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3ListBuckets",
      "Effect": "Allow",
      "Action": ["s3:ListAllMyBuckets", "s3:GetBucketLocation"],
      "Resource": "arn:aws:s3:::*"
    },
    {
      "Sid": "AllowS3ReadObjects",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-app-bucket", "arn:aws:s3:::my-app-bucket/*"]
    }
  ]
}

Policy Components:

  • Version: Policy language version (always use 2012-10-17)
  • Statement: Array of permission rules
  • Sid: Optional statement identifier (for documentation)
  • Effect: Allow or Deny (explicit deny always wins)
  • Action: API calls permitted (e.g., s3:GetObject, ec2:RunInstances)
  • Resource: ARNs where permissions apply
  • Condition: Optional constraints (IP ranges, MFA, time windows)

Example with Conditions (MFA Required):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2:TerminateInstances",
      "Resource": "*",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        },
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

This policy requires MFA and requests from specific IP range to terminate EC2 instances.

IAM Roles for AWS Services

Roles allow AWS services to act on your behalf without storing credentials in code.

   Application Architecture   

   assumes   

   grants   

   grants   

EC2 Instance  

IAM Role:

EC2-S3-Access  

S3 Bucket  

DynamoDB Table  

Creating an EC2 Role (AWS CLI):

# Create trust policy document
cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

# Create the role
aws iam create-role \
  --role-name EC2-S3-ReadOnly \
  --assume-role-policy-document file://trust-policy.json

# Attach AWS managed policy
aws iam attach-role-policy \
  --role-name EC2-S3-ReadOnly \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Create instance profile (required for EC2)
aws iam create-instance-profile \
  --instance-profile-name EC2-S3-ReadOnly-Profile

# Add role to instance profile
aws iam add-role-to-instance-profile \
  --instance-profile-name EC2-S3-ReadOnly-Profile \
  --role-name EC2-S3-ReadOnly

IAM Security Best Practices

  1. Enable MFA for root account (mandatory)
  2. Create IAM users with groups (never attach policies directly to users)
  3. Rotate credentials regularly (access keys every 90 days)
  4. Use IAM roles instead of access keys (for EC2, Lambda, ECS)
  5. Enable CloudTrail for audit logging
  6. Set password policy (minimum length, complexity, rotation)
  7. Use IAM Access Analyzer to detect overly permissive policies

Password Policy Example (AWS CLI):

aws iam update-account-password-policy \
  --minimum-password-length 14 \
  --require-symbols \
  --require-numbers \
  --require-uppercase-characters \
  --require-lowercase-characters \
  --allow-users-to-change-password \
  --max-password-age 90 \
  --password-reuse-prevention 5

AWS Elastic Compute Cloud (EC2)

EC2 Architecture and Instance Types

EC2 provides resizable compute capacity in the cloud. Understanding instance families is crucial for cost optimization.

   VPC: 10.0.0.0/16   

   Private Subnet: 10.0.2.0/24   

   Public Subnet: 10.0.1.0/24   

   HTTP/HTTPS   

   API calls   

   queries   

   outbound   

EC2: t3.medium

Web Server

Public IP  

EC2: c5.large

App Server

Private IP  

EC2: r5.xlarge

Database

Private IP  

Internet Gateway  

NAT Gateway  

Internet  

Instance Type Selection:

Family Use Case Example
t3/t4g Burstable, general purpose Web servers, dev/test
c5/c6i Compute-optimized Batch processing, HPC
r5/r6i Memory-optimized Databases, caching
m5/m6i Balanced compute/memory Application servers
g4/p4 GPU-accelerated ML training, rendering

EC2 User Data for Automation

User Data scripts run once at instance launch. Use for bootstrapping.

Example: Web Server Setup

#!/bin/bash
# Update system packages
yum update -y

# Install and start Apache
yum install -y httpd
systemctl start httpd
systemctl enable httpd

# Create simple health check endpoint
cat > /var/www/html/health.html <<EOF
<!DOCTYPE html>
<html>
<head><title>Health Check</title></head>
<body>
    <h1>OK</h1>
    <p>Instance ID: $(ec2-metadata --instance-id | cut -d " " -f 2)</p>
    <p>Availability Zone: $(ec2-metadata --availability-zone | cut -d " " -f 2)</p>
</body>
</html>
EOF

# Install CloudWatch agent for monitoring
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm

# Configure CloudWatch agent
cat > /opt/aws/amazon-cloudwatch-agent/etc/config.json <<EOF
{
  "metrics": {
    "namespace": "MyApp",
    "metrics_collected": {
      "mem": {
        "measurement": [{"name": "mem_used_percent"}]
      },
      "disk": {
        "measurement": [{"name": "disk_used_percent"}]
      }
    }
  }
}
EOF

# Start CloudWatch agent
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -s \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json

EC2 Security Groups

Security Groups act as virtual firewalls controlling inbound/outbound traffic.

Example: Web Server Security Group (Terraform)

resource "aws_security_group" "web_server" {
  name        = "web-server-sg"
  description = "Allow HTTP/HTTPS from ALB, SSH from office"
  vpc_id      = aws_vpc.main.id

  # Inbound rules
  ingress {
    description     = "HTTP from ALB"
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }

  ingress {
    description     = "HTTPS from ALB"
    from_port       = 443
    to_port         = 443
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }

  ingress {
    description = "SSH from office IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.0/24"]
  }

  # Outbound rules (allow all)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name        = "web-server-sg"
    Environment = "production"
  }
}

Security Group Best Practices:

  1. Reference other security groups instead of IP ranges (dynamic scaling)
  2. Deny by default (no inbound rules unless explicitly added)
  3. Use descriptive names and tags
  4. Separate security groups by tier (web, app, database)
  5. Never open 0.0.0.0/0 for SSH (use bastion hosts or SSM Session Manager)

EC2 Purchasing Options

   EC2 Pricing Models   

On-Demand

$0.0832/hr

No commitment  

Reserved

$0.0499/hr

1-3 year term  

Spot Instance

$0.0250/hr

Can be terminated  

Savings Plan

$0.0583/hr

Flexible usage  

When to Use Each:

  • On-Demand: Unpredictable workloads, short-term projects, testing
  • Reserved Instances: Steady-state workloads (databases, always-on apps)
  • Spot Instances: Batch processing, CI/CD workers, fault-tolerant apps
  • Savings Plans: Flexible workloads with predictable compute usage

Application Load Balancer (ALB)

ALB Architecture

ALBs operate at Layer 7 (HTTP/HTTPS), enabling advanced routing based on request content.

   Target Groups   

   Application Load Balancer   

   Internet   

   HTTP/HTTPS   

   301 redirect   

Users  

ALB

*.example.com

SSL Termination  

Listener :80

Redirect to HTTPS  

Listener :443

SSL Certificate  

Rule 1:

api.example.com/*  

Rule 2:

.example.com/admin/  

Rule 3:

Default  

API Servers

Port 8080  

Admin Portal

Port 3000  

Web Servers

Port 80  

ALB Routing Rules

Path-Based Routing (Terraform):

resource "aws_lb_listener_rule" "api_routing" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 100

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api_servers.arn
  }

  condition {
    path_pattern {
      values = ["/api/*"]
    }
  }
}

resource "aws_lb_listener_rule" "host_based_routing" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 90

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.admin_portal.arn
  }

  condition {
    host_header {
      values = ["admin.example.com"]
    }
  }
}

resource "aws_lb_listener_rule" "header_based_routing" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 80

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.mobile_api.arn
  }

  condition {
    http_header {
      http_header_name = "User-Agent"
      values           = ["*Mobile*", "*Android*", "*iOS*"]
    }
  }
}

ALB Health Checks

Health checks determine target availability. Failed checks remove instances from rotation.

resource "aws_lb_target_group" "web_servers" {
  name     = "web-servers-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    enabled             = true
    path                = "/health"
    protocol            = "HTTP"
    port                = "traffic-port"
    healthy_threshold   = 2
    unhealthy_threshold = 3
    timeout             = 5
    interval            = 30
    matcher             = "200-299"
  }

  deregistration_delay = 30

  stickiness {
    type            = "lb_cookie"
    cookie_duration = 86400
    enabled         = true
  }

  tags = {
    Name = "web-servers-target-group"
  }
}

Health Check Parameters:

  • healthy_threshold: Consecutive successes required (2-10)
  • unhealthy_threshold: Consecutive failures before marking unhealthy (2-10)
  • timeout: Max response time (2-120 seconds)
  • interval: Time between checks (5-300 seconds)
  • matcher: Expected HTTP status codes (200, 200-299, 200,301)

Auto Scaling Groups (ASG)

ASG Architecture with ALB

   Auto Scaling Group   

   AZ-1b   

   AZ-1a   

   launches   

   launches   

   launches   

   distributes traffic   

   distributes traffic   

   distributes traffic   

   triggers   

   triggers   

Launch Template

AMI, Instance Type,

User Data, Security Groups  

EC2 Instance  

EC2 Instance  

EC2 Instance  

Scaling Policy

Target: CPU 70%  

CloudWatch Alarm

CPU > 70%

Add 2 instances  

CloudWatch Alarm

CPU < 30%

Remove 1 instance  

Application

Load Balancer  

Launch Template

Launch Templates define instance configuration (successor to Launch Configurations).

resource "aws_launch_template" "web_server" {
  name_prefix   = "web-server-"
  image_id      = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"

  iam_instance_profile {
    name = aws_iam_instance_profile.ec2_s3_access.name
  }

  vpc_security_group_ids = [aws_security_group.web_server.id]

  user_data = base64encode(<<-EOF
    #!/bin/bash
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "<h1>Hello from $(hostname -f)</h1>" > /var/www/html/index.html
  EOF
  )

  block_device_mappings {
    device_name = "/dev/xvda"

    ebs {
      volume_size           = 20
      volume_type           = "gp3"
      delete_on_termination = true
      encrypted             = true
    }
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_tokens                 = "required"
    http_put_response_hop_limit = 1
  }

  monitoring {
    enabled = true
  }

  tag_specifications {
    resource_type = "instance"

    tags = {
      Name        = "web-server-asg"
      Environment = "production"
    }
  }
}

Auto Scaling Policies

Target Tracking Scaling (Recommended):

resource "aws_autoscaling_group" "web_servers" {
  name                = "web-servers-asg"
  vpc_zone_identifier = [aws_subnet.private_a.id, aws_subnet.private_b.id]
  target_group_arns   = [aws_lb_target_group.web_servers.arn]
  health_check_type   = "ELB"
  health_check_grace_period = 300

  min_size         = 2
  max_size         = 10
  desired_capacity = 4

  launch_template {
    id      = aws_launch_template.web_server.id
    version = "$Latest"
  }

  enabled_metrics = [
    "GroupDesiredCapacity",
    "GroupInServiceInstances",
    "GroupMinSize",
    "GroupMaxSize"
  ]

  tag {
    key                 = "Name"
    value               = "web-server-asg"
    propagate_at_launch = true
  }
}

# Target Tracking Policy - CPU
resource "aws_autoscaling_policy" "cpu_tracking" {
  name                   = "cpu-target-tracking"
  autoscaling_group_name = aws_autoscaling_group.web_servers.name
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ASGAverageCPUUtilization"
    }
    target_value = 70.0
  }
}

# Target Tracking Policy - Request Count
resource "aws_autoscaling_policy" "request_count_tracking" {
  name                   = "request-count-tracking"
  autoscaling_group_name = aws_autoscaling_group.web_servers.name
  policy_type            = "TargetTrackingScaling"

  target_tracking_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      resource_label         = "${aws_lb.main.arn_suffix}/${aws_lb_target_group.web_servers.arn_suffix}"
    }
    target_value = 1000.0
  }
}

Step Scaling Policy (Advanced Control):

resource "aws_autoscaling_policy" "scale_up" {
  name                   = "scale-up-policy"
  autoscaling_group_name = aws_autoscaling_group.web_servers.name
  adjustment_type        = "ChangeInCapacity"
  policy_type            = "StepScaling"

  step_adjustment {
    scaling_adjustment          = 1
    metric_interval_lower_bound = 0
    metric_interval_upper_bound = 10
  }

  step_adjustment {
    scaling_adjustment          = 2
    metric_interval_lower_bound = 10
    metric_interval_upper_bound = 20
  }

  step_adjustment {
    scaling_adjustment          = 3
    metric_interval_lower_bound = 20
  }
}

resource "aws_cloudwatch_metric_alarm" "high_cpu" {
  alarm_name          = "web-servers-high-cpu"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "60"
  statistic           = "Average"
  threshold           = "70"
  alarm_description   = "Triggers when CPU exceeds 70%"
  alarm_actions       = [aws_autoscaling_policy.scale_up.arn]

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

Production Best Practices

Cost Optimization

  1. Right-size instances using CloudWatch metrics
  2. Use Savings Plans or Reserved Instances for baseline capacity
  3. Mix instance types in ASG (Spot + On-Demand for fault tolerance)
  4. Enable ALB access logs only when troubleshooting (S3 storage costs)
  5. Delete unused EBS volumes and AMIs

Security Hardening

  1. Enforce IMDSv2 (prevents SSRF attacks):

    aws ec2 modify-instance-metadata-options \
      --instance-id i-1234567890abcdef0 \
      --http-tokens required
    
  2. Enable EBS encryption by default:

    aws ec2 enable-ebs-encryption-by-default --region us-east-1
    
  3. Use Systems Manager Session Manager instead of SSH:

    • No bastion hosts needed
    • Centralized audit logging
    • No inbound security group rules
  4. Rotate IAM access keys every 90 days

Monitoring and Troubleshooting

Essential CloudWatch Metrics:

  • EC2: CPUUtilization, StatusCheckFailed, NetworkIn/Out
  • ALB: TargetResponseTime, HTTPCode_Target_5XX_Count, RequestCount
  • ASG: GroupDesiredCapacity, GroupInServiceInstances

Common Issues:

Problem Symptom Solution
Instances failing health checks ALB draining instances Check security group allows health check path
ASG not scaling CloudWatch alarm not triggering Verify metric threshold and evaluation periods
502 Bad Gateway Targets returning errors Check application logs, increase timeout
Uneven traffic distribution Some instances overloaded Enable cross-zone load balancing

Exam Tips

Key Concepts:

  1. IAM policies use least privilege - start with deny all, add specific allows
  2. Security Groups are stateful - return traffic automatically allowed
  3. ALB supports SNI - multiple SSL certificates on single listener
  4. Target Tracking is simplest scaling policy - AWS manages CloudWatch alarms
  5. Health check grace period - allows time for instance bootstrapping
  6. Connection draining - completes in-flight requests before deregistration

Common Exam Scenarios:

  • "Instances keep terminating immediately" → Health check grace period too short
  • "Need different routing for mobile vs desktop" → Use header-based routing rules
  • "Reduce costs for steady workload" → Reserved Instances or Savings Plans
  • "Share files between instances" → Use EFS, not instance store
  • "EC2 needs S3 access without credentials" → Use IAM roles, not access keys

Tricky Edge Cases:

  • Default security groups allow all outbound, deny all inbound
  • ALB requires at least 2 subnets in different AZs
  • User Data runs as root user by default
  • Spot instance termination gives 2-minute warning (interruption notice)
  • Launch Templates support versioning, Launch Configurations don't

Frequently Asked Questions

Q: What is AWS IAM and why is it important?

AWS IAM (Identity and Access Management) is a service that controls access to AWS resources through users, groups, and roles. It enables you to manage permissions using policies, implement MFA for security, and follows the principle of least privilege. IAM is critical for preventing unauthorized access to your cloud infrastructure.

Q: How do I create an IAM role for EC2 instances?

Create an IAM role by navigating to IAM console, selecting "Roles", clicking "Create role", choosing EC2 as the trusted entity, and attaching policies. Attach the role to EC2 instances during launch or modify existing instances. This provides secure, credential-free access to AWS services without hardcoding keys.

Q: What is the difference between Application Load Balancer and Network Load Balancer?

Application Load Balancer (ALB) operates at Layer 7 (HTTP/HTTPS) and supports advanced routing based on URL paths, hostnames, and headers. Network Load Balancer (NLB) operates at Layer 4 (TCP/UDP) and handles millions of requests per second with ultra-low latency. Choose ALB for web applications, NLB for extreme performance.

Q: How does Auto Scaling Group determine when to scale?

Auto Scaling Groups use CloudWatch alarms and scaling policies to determine when to add or remove instances. Target tracking policies automatically adjust capacity to maintain a metric like CPU utilization at 70%. Step scaling provides more granular control with multiple thresholds for different scaling actions.

Q: What are EC2 instance types and how do I choose one?

EC2 instance types are categorized by families: T3/T4g for general purpose, C5/C6i for compute-intensive, R5/R6i for memory-intensive, and M5/M6i for balanced workloads. Choose based on your application's CPU, memory, and network requirements. Use CloudWatch metrics to right-size instances and optimize costs.

Q: How do ALB health checks work?

ALB health checks send periodic requests to registered targets using a specified path, protocol, and port. Targets must respond with HTTP 200 status codes within the timeout period. After consecutive failures (unhealthy threshold), ALB stops routing traffic to that instance. After consecutive successes, it's marked healthy again.

Q: What is the difference between Reserved Instances and Spot Instances?

Reserved Instances offer up to 72% savings for 1-3 year commitments on steady-state workloads like databases. Spot Instances provide up to 90% discounts but can be terminated with 2-minute notice when AWS needs capacity. Use Reserved for production, Spot for fault-tolerant batch processing and CI/CD workloads.


Conclusion

Mastering IAM, EC2, ALB, and ASG provides the foundation for building secure, scalable AWS applications. IAM controls access, EC2 provides compute, ALB intelligently routes traffic, and ASG ensures availability through automatic scaling.

The architecture patterns and configurations in this guide reflect production-proven practices. Start with conservative scaling policies, monitor CloudWatch metrics closely, and iterate based on real traffic patterns. Always apply least-privilege IAM policies, enable MFA, and use roles instead of access keys.

For the AWS Certified Developer Associate exam, focus on understanding how these services interact, the JSON policy structure, health check mechanics, and scaling policy types. Practice creating resources using both AWS Console and Infrastructure as Code (Terraform/CloudFormation) to build muscle memory for exam scenarios.