AWS for DevOps — ECS + ALB

3 minute read

7.5 — ECS + Application Load Balancer

An Application Load Balancer (ALB) distributes traffic to your ECS tasks. It provides health checking, path-based routing, and the ability to run multiple services behind a single domain.

Architecture

  Users
    │
    ▼
  ALB (port 443)
  ┌─────────────────────────────────────────┐
  │  Listener Rule: /api/*  → TG: api-tg   │
  │  Listener Rule: /web/*  → TG: web-tg   │
  │  Default                → TG: default-tg│
  └─────────────────────────────────────────┘
       │                │
       ▼                ▼
  api-tg             web-tg
  ┌──────┐          ┌──────┐
  │Task 1│          │Task 1│
  │Task 2│          │Task 2│
  │Task 3│          │Task 3│
  └──────┘          └──────┘
  ECS Service: api   ECS Service: web

Step-by-Step Setup

1. Create the ALB

$ aws elbv2 create-load-balancer \
    --name my-app-alb \
    --subnets subnet-pub-a subnet-pub-b \
    --security-groups sg-alb-123 \
    --scheme internet-facing \
    --type application
{
    "LoadBalancers": [{
        "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-south-1:123456789012:loadbalancer/app/my-app-alb/abc123",
        "DNSName": "my-app-alb-123456789.ap-south-1.elb.amazonaws.com"
    }]
}

2. Create a Target Group

$ aws elbv2 create-target-group \
    --name api-tg \
    --protocol HTTP \
    --port 8080 \
    --vpc-id vpc-abc123 \
    --target-type ip \
    --health-check-path /health \
    --health-check-interval-seconds 30 \
    --healthy-threshold-count 2 \
    --unhealthy-threshold-count 3

Important: For Fargate, target-type must be ip (not instance). Each task gets its own IP.

3. Create a Listener

# HTTP listener (redirect to HTTPS in production)
$ aws elbv2 create-listener \
    --load-balancer-arn arn:aws:elasticloadbalancing:...:loadbalancer/app/my-app-alb/abc123 \
    --protocol HTTP \
    --port 80 \
    --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/api-tg/def456

4. Create ECS Service with ALB

$ aws ecs create-service \
    --cluster production \
    --service-name api \
    --task-definition api:3 \
    --desired-count 3 \
    --launch-type FARGATE \
    --network-configuration \
        "awsvpcConfiguration={subnets=[subnet-priv-a,subnet-priv-b],securityGroups=[sg-tasks-789],assignPublicIp=DISABLED}" \
    --load-balancers \
        "targetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/api-tg/def456,containerName=app,containerPort=8080" \
    --health-check-grace-period-seconds 120
Parameter Why
assignPublicIp=DISABLED Tasks are in private subnets — ALB handles public traffic
containerName Must match the container name in your task definition
containerPort Must match the portMappings in your task definition
health-check-grace-period-seconds Give the app time to start before ALB marks it unhealthy

Path-Based Routing

Route different URL paths to different ECS services:

# Rule 1: /api/* → api service
$ aws elbv2 create-rule \
    --listener-arn arn:aws:elasticloadbalancing:...:listener/app/my-app-alb/abc123/def456 \
    --priority 10 \
    --conditions Field=path-pattern,Values='/api/*' \
    --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/api-tg/...

# Rule 2: /admin/* → admin service
$ aws elbv2 create-rule \
    --listener-arn arn:aws:elasticloadbalancing:...:listener/app/my-app-alb/abc123/def456 \
    --priority 20 \
    --conditions Field=path-pattern,Values='/admin/*' \
    --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/admin-tg/...

Host-Based Routing

Route different domains to different services:

$ aws elbv2 create-rule \
    --listener-arn arn:aws:elasticloadbalancing:...:listener/... \
    --priority 5 \
    --conditions Field=host-header,Values='api.example.com' \
    --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:...:targetgroup/api-tg/...

Health Checks

Setting Recommended Value Why
Path /health or /healthz Dedicated lightweight endpoint
Interval 30 seconds Balance between detection speed and load
Healthy threshold 2 2 consecutive successes = healthy
Unhealthy threshold 3 3 consecutive failures = unhealthy
Timeout 5 seconds Fail fast
Matcher 200 Only 200 OK means healthy

Security Group Rules

  ALB Security Group (sg-alb)          Task Security Group (sg-tasks)
  ┌────────────────────────┐          ┌──────────────────────────┐
  │ Inbound:               │          │ Inbound:                  │
  │   TCP 80 from 0.0.0.0  │          │   TCP 8080 from sg-alb    │
  │   TCP 443 from 0.0.0.0 │          │   (ONLY from ALB!)        │
  │                         │          │                           │
  │ Outbound:               │          │ Outbound:                 │
  │   TCP 8080 to sg-tasks  │          │   All traffic (outbound)  │
  └────────────────────────┘          └──────────────────────────┘

Tasks should only accept traffic from the ALB security group — never directly from the internet.

Scaling Based on ALB Metrics

$ aws application-autoscaling put-scaling-policy \
    --service-namespace ecs \
    --scalable-dimension ecs:service:DesiredCount \
    --resource-id service/production/api \
    --policy-name request-count-scaling \
    --policy-type TargetTrackingScaling \
    --target-tracking-scaling-policy-configuration '{
        "TargetValue": 500.0,
        "PredefinedMetricSpecification": {
            "PredefinedMetricType": "ALBRequestCountPerTarget",
            "ResourceLabel": "app/my-app-alb/abc123/targetgroup/api-tg/def456"
        }
    }'

This scales the service to maintain approximately 500 requests per target.

Common Mistakes

Mistake Why It Hurts Fix
target-type=instance with Fargate Registration fails Use target-type=ip for Fargate
No health-check-grace-period New tasks killed before app starts Set to 60-120 seconds
Tasks in public subnets Unnecessary exposure Put tasks in private subnets; ALB handles public traffic
ALB SG allows all ports to tasks Wide open attack surface Allow only the container port from ALB SG

Quick Reference

Task Command
Create ALB aws elbv2 create-load-balancer --name NAME --subnets ... --type application
Create target group aws elbv2 create-target-group --name NAME --target-type ip --port PORT
Create listener aws elbv2 create-listener --load-balancer-arn ARN --port 80 --default-actions ...
Add routing rule aws elbv2 create-rule --listener-arn ARN --conditions ... --actions ...
Check target health aws elbv2 describe-target-health --target-group-arn ARN

Next → Containers Practice Exercises

Updated: