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:
- Part I: IAM EC2 & Auto Scaling (Current Article)
- Part II: RDS Aurora & DynamoDB
- Part III: SQS SNS & Kinesis
- Part IV: Lambda API Gateway
- Part V: ECS Fargate & IaC
- Part VI: Cognito KMS Security
- 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.
Key Principles:
- Never use root account for daily operations—only for account setup and billing
- One physical person = One IAM user (no shared credentials)
- Users inherit permissions from groups (easier management)
- Policies define permissions using JSON documents
- 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:
AlloworDeny(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.
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
- Enable MFA for root account (mandatory)
- Create IAM users with groups (never attach policies directly to users)
- Rotate credentials regularly (access keys every 90 days)
- Use IAM roles instead of access keys (for EC2, Lambda, ECS)
- Enable CloudTrail for audit logging
- Set password policy (minimum length, complexity, rotation)
- 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.
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:
- Reference other security groups instead of IP ranges (dynamic scaling)
- Deny by default (no inbound rules unless explicitly added)
- Use descriptive names and tags
- Separate security groups by tier (web, app, database)
- Never open 0.0.0.0/0 for SSH (use bastion hosts or SSM Session Manager)
EC2 Purchasing Options
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.
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
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
- Right-size instances using CloudWatch metrics
- Use Savings Plans or Reserved Instances for baseline capacity
- Mix instance types in ASG (Spot + On-Demand for fault tolerance)
- Enable ALB access logs only when troubleshooting (S3 storage costs)
- Delete unused EBS volumes and AMIs
Security Hardening
-
Enforce IMDSv2 (prevents SSRF attacks):
aws ec2 modify-instance-metadata-options \ --instance-id i-1234567890abcdef0 \ --http-tokens required -
Enable EBS encryption by default:
aws ec2 enable-ebs-encryption-by-default --region us-east-1 -
Use Systems Manager Session Manager instead of SSH:
- No bastion hosts needed
- Centralized audit logging
- No inbound security group rules
-
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:
- IAM policies use least privilege - start with deny all, add specific allows
- Security Groups are stateful - return traffic automatically allowed
- ALB supports SNI - multiple SSL certificates on single listener
- Target Tracking is simplest scaling policy - AWS manages CloudWatch alarms
- Health check grace period - allows time for instance bootstrapping
- 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.