When deploying applications to ECS, a load balancer provides numerous benefits over accessing containers directly via their IP addresses:
Feature | With Load Balancer | Direct IP Access (Single Task) |
---|---|---|
Access Point Stability | ✅ Stable DNS name that never changes | ❌ IP address changes when tasks restart |
Scaling | ✅ Seamlessly distributes traffic across multiple tasks | ❌ Limited to single task capacity |
High Availability | ✅ Continues functioning if individual tasks fail | ❌ Service unavailable if the single task fails |
Health Checks | ✅ Automatic health monitoring and unhealthy task removal | ❌ No health checking; manual monitoring required |
Traffic Distribution | ✅ Intelligent traffic routing (round-robin, least connections) | ❌ No distribution; all requests hit the same task |
SSL/TLS Termination | ✅ Centralized certificate management and HTTPS support | ❌ Must implement TLS in each container |
Security | ✅ WAF integration, security groups at load balancer level | ❌ Security controls needed on each task |
Advanced Routing | ✅ Path-based, host-based routing, redirects | ❌ No routing capabilities |
Sticky Sessions | ✅ Session affinity for stateful applications | ❌ No session management |
Blue/Green Deployments | ✅ Easy implementation of zero-downtime deployments | ❌ Requires custom implementation |
Monitoring | ✅ Centralized metrics (request counts, latency, errors) | ❌ Limited to container-level metrics |
Cost | ❌ Additional cost for ALB/NLB | ✅ No additional infrastructure costs |
Complexity | ❌ More complex initial setup | ✅ Simpler architecture |
graph TD
U[User] -->|Request| LB[Application Load Balancer]
LB -->|Forwards Request| TG[Target Group]
TG -->|Routes to Healthy Tasks| B[ECS Service]
B -->|Manages| T1[Task 1]
B -->|Manages| T2[Task 2]
B -->|Manages| T3[Task 3]
T1 -->|Runs| C1[Container]
T2 -->|Runs| C2[Container]
T3 -->|Runs| C3[Container]
subgraph "ECS Cluster"
B
T1
T2
T3
C1
C2
C3
end
A service that distributes incoming application traffic across multiple targets.
A group of targets (ECS tasks) that the load balancer routes traffic to.
The ECS service configured to register tasks with the target group automatically.
In the following section, use the VS-Code Server to enter these commands.
# Get default VPC ID
VPC_ID=$(aws ec2 describe-vpcs \
--filters Name=isDefault,Values=true \
--query 'Vpcs[0].VpcId' \
--output text)
# Create target group
aws elbv2 create-target-group \
--name rent-a-room-tg \
--protocol HTTP \
--port 80 \
--vpc-id $VPC_ID \
--target-type ip \
--health-check-path / \
--health-check-interval-seconds 30 \
--health-check-timeout-seconds 5 \
--healthy-threshold-count 2 \
--unhealthy-threshold-count 2
# Create security group for ALB
ALB_SG_ID=$(aws ec2 create-security-group \
--group-name rent-a-room-alb-sg \
--description "Security group for Rent-A-Room ALB" \
--vpc-id $VPC_ID \
--query 'GroupId' \
--output text)
# Allow inbound HTTP from anywhere
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG_ID \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
# Get the ECS service security group ID
ECS_SG_ID=$(aws ec2 describe-security-groups \
--filters Name=group-name,Values=ecs-rent-a-room-sg \
--query 'SecurityGroups[0].GroupId' \
--output text)
# Update ECS security group to allow traffic from ALB
aws ec2 authorize-security-group-ingress \
--group-id $ECS_SG_ID \
--protocol tcp \
--port 80 \
--source-group $ALB_SG_ID
# Get subnet IDs
SUBNET_IDS=$(aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC_ID \
--query 'Subnets[*].SubnetId' \
--output json | tr -d '[:space:]')
# Create the load balancer
aws elbv2 create-load-balancer \
--name rent-a-room-alb \
--subnets $(echo $SUBNET_IDS | sed 's/\[//g' | sed 's/\]//g' | sed 's/,/ /g' | sed 's/"//g') \
--security-groups $ALB_SG_ID
# Store the ALB ARN
ALB_ARN=$(aws elbv2 describe-load-balancers \
--names rent-a-room-alb \
--query 'LoadBalancers[0].LoadBalancerArn' \
--output text)
# Get target group ARN
TG_ARN=$(aws elbv2 describe-target-groups \
--names rent-a-room-tg \
--query 'TargetGroups[0].TargetGroupArn' \
--output text)
# Create listener
aws elbv2 create-listener \
--load-balancer-arn $ALB_ARN \
--protocol HTTP \
--port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN
# Get target group ARN
TG_ARN=$(aws elbv2 describe-target-groups \
--names rent-a-room-tg \
--query 'TargetGroups[0].TargetGroupArn' \
--output text)
# Get the ECS service security group ID
ECS_SG_ID=$(aws ec2 describe-security-groups \
--filters Name=group-name,Values=ecs-rent-a-room-sg \
--query 'SecurityGroups[0].GroupId' \
--output text)
# Update ECS security group to allow traffic from ALB if not already done
aws ec2 authorize-security-group-ingress \
--group-id $ECS_SG_ID \
--protocol tcp \
--port 80 \
--source-group $ALB_SG_ID 2>/dev/null || echo "Rule already exists"
# Update the service to use the load balancer
aws ecs update-service \
--cluster rent-a-room-cluster \
--service rent-a-room-service \
--load-balancers "targetGroupArn=$TG_ARN,containerName=rent-a-room,containerPort=80" \
--force-new-deployment
http://[DNS_NAME]
# Get the load balancer DNS name
ALB_DNS=$(aws elbv2 describe-load-balancers \
--names rent-a-room-alb \
--query 'LoadBalancers[0].DNSName' \
--output text)
echo "Your application is available at: http://$ALB_DNS"
echo "NOTE: This will take a few minutes to propgate and accept traffic."
One of the primary benefits of using a load balancer with ECS is handling IP changes seamlessly. Here’s what happens during updates:
sequenceDiagram
participant User
participant ALB as Application Load Balancer
participant ECS as ECS Service
participant OldTask as Old Task (Old IP)
participant NewTask as New Task (New IP)
User->>ALB: Request
ALB->>OldTask: Forward request
OldTask->>ALB: Response
ALB->>User: Return response
Note over ECS,NewTask: Service update triggered
ECS->>NewTask: Start new task
NewTask-->>ECS: Task started (new IP address)
ECS->>ALB: Register new task IP with target group
NewTask-->>ALB: Health check passes
User->>ALB: New request
ALB->>NewTask: Forward to new task
NewTask->>ALB: Response
ALB->>User: Return response
ECS->>ALB: Deregister old task
ALB-->>OldTask: Complete in-flight requests (connection draining)
ECS->>OldTask: Terminate task
Automatic Registration
Connection Draining
DNS Stability
Without Load Balancer | With Load Balancer |
---|---|
🔴 IP changes require DNS updates | ✅ DNS endpoint remains stable |
🔴 Clients see connection errors during updates | ✅ Transitions are seamless to users |
🔴 No health checking before sending traffic | ✅ Only healthy tasks receive traffic |
🔴 Manual intervention needed for task failures | ✅ Automatic failover to healthy tasks |
After completing the setup, verify everything is working properly:
Navigate to EC2 → Target Groups → Select your target group → Targets tab
:::alert{header=“Important” type=“warning”} If your targets remain unhealthy, verify that you’ve updated the ECS security group to allow traffic from the load balancer. The security group created in the previous section only allows traffic from your IP address, but the load balancer needs permission to reach your tasks. :::
Navigate to EC2 → Load Balancers → Select your ALB → Listeners tab
Navigate to ECS → Clusters → Your cluster → Services → Your service → Events tab
Tasks aren’t registering with target group
Health checks failing
Cannot access application via load balancer
# Check ECS security group inbound rules
ECS_SG_ID=$(aws ec2 describe-security-groups \
--filters Name=group-name,Values=ecs-rent-a-room-sg \
--query 'SecurityGroups[0].GroupId' \
--output text)
echo "ECS Security Group inbound rules:"
aws ec2 describe-security-groups \
--group-ids $ECS_SG_ID \
--query 'SecurityGroups[0].IpPermissions' \
--output table
With your load balancer in place, you can now scale your application:
# Update service to increase task count
aws ecs update-service \
--cluster rent-a-room-cluster \
--service rent-a-room-service \
--desired-count 3
The load balancer will automatically distribute traffic to all healthy tasks!
Once your basic setup is working, consider these enhancements:
HTTPS Support: Add a certificate in AWS Certificate Manager and configure HTTPS listener
Path-Based Routing: Route different URLs to different services (e.g., /api to backend, / to frontend)
Auto Scaling: Configure your service to scale based on CPU/memory metrics or request count:
# Check if the ECS service-linked role already exists
if ! aws iam get-role --role-name AWSServiceRoleForECS &> /dev/null; then
echo "Creating ECS service-linked role..."
aws iam create-service-linked-role --aws-service-name ecs.amazonaws.com
# Wait for role to propagate
echo "Waiting for role to propagate..."
sleep 20
else
echo "✅ ECS service-linked role already exists"
fi
# Enable service auto scaling
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/rent-a-room-cluster/rent-a-room-service \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 1 \
--max-capacity 5
sleep 20
# Create scaling policy based on CPU utilization
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/rent-a-room-cluster/rent-a-room-service \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-scaling-policy \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
}
}'
After setting up auto scaling, it’s important to verify that your tasks are running as expected.
# Verify the number of running tasks
echo "Checking running tasks..."
aws ecs describe-services \
--cluster rent-a-room-cluster \
--services rent-a-room-service \
--query 'services[0].{desiredCount:desiredCount,runningCount:runningCount,pendingCount:pendingCount}' \
--output table
# List all running tasks and their status
echo -e "\nListing all tasks:"
aws ecs list-tasks \
--cluster rent-a-room-cluster \
--service-name rent-a-room-service \
--query 'taskArns[*]' \
--output table
# Get detailed task information including IP addresses
echo -e "\nDetailed task information:"
TASK_ARNS=$(aws ecs list-tasks \
--cluster rent-a-room-cluster \
--service-name rent-a-room-service \
--query 'taskArns[*]' \
--output text)
if [ ! -z "$TASK_ARNS" ]; then
aws ecs describe-tasks \
--cluster rent-a-room-cluster \
--tasks $TASK_ARNS \
--query 'tasks[*].{TaskId:taskArn,Status:lastStatus,IP:attachments[0].details[?name==`privateIPv4Address`].value[0]}' \
--output table
fi
The verification commands provide several key pieces of information:
desiredCount
: Number of tasks you want runningrunningCount
: Current number of tasks actually runningpendingCount
: Tasks in the process of startingTaskId
: Unique identifier for each taskStatus
: Current state of the task (RUNNING, PENDING, etc.)Now that you’ve:
✨ Continue to CI/CD Pipeline Setup ▶️