Adding a Load Balancer to ECS
Adding a Load Balancer to ECS
π― Why Use a Load Balancer?
When deploying applications to ECS, a load balancer provides numerous benefits over accessing containers directly via their IP addresses:
- Stable endpoint that doesn’t change when containers restart
- Automatic scaling across multiple tasks
- Health checks to route traffic only to healthy containers
- High availability by distributing traffic across multiple Availability Zones
π Load Balancer vs. Direct IP Access
| 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 |
π Architecture with Load Balancer
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
π Understanding Each Component
1. Application Load Balancer (ALB)
A service that distributes incoming application traffic across multiple targets.
- Function: Entry point for all user traffic
- Benefits: Provides a stable DNS name for accessing your application
- Features: Content-based routing, health checks, and security features
2. Target Group
A group of targets (ECS tasks) that the load balancer routes traffic to.
- Function: Defines which tasks receive traffic and how they’re checked for health
- Configuration: Health check settings, protocols, and ports
- Relationship: Connected to your ECS service containers
3. ECS Service with Load Balancer Integration
The ECS service configured to register tasks with the target group automatically.
- Function: Maintains task count and registers/deregisters tasks with the target group
- Benefit: Automatically handles unhealthy tasks
- Configuration: Maps container ports to the load balancer
π Implementation Steps
In the following section, use the VS-Code Server to enter these commands.
1οΈβ£ Create a Target Group
# 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 22οΈβ£ Create an Application Load Balancer
# 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_ARN3οΈβ£ Update ECS Service to Use the Load Balancer
# 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-deployment4οΈβ£ Access Your Application Through the Load Balancer
Using AWS Console:
- Go to the Load Balancers in the Console
- Select your load balancer rent-a-room-alb
- Copy the DNS name
- Access your application:
http://[DNS_NAME] - NOTE: This will take a few minutes to propagate and accept traffic due to the service redeploying.
Using AWS CLI:
# 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."π What Happens When Tasks Get New IPs?
One of the primary benefits of using a load balancer with ECS is handling IP changes seamlessly. Here’s what happens during updates:
The Task Update Process
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
How It Works:
-
Automatic Registration
- When a new task starts, ECS automatically registers its IP with the target group
- Load balancer begins health checks on the new IP
- Once healthy, the new task starts receiving traffic
-
Connection Draining
- When a task stops, ECS deregisters it from the target group
- Load balancer stops sending new requests to that task
- Existing connections complete (default: 300 seconds)
- After draining, the task terminates
-
DNS Stability
- The load balancer’s DNS name remains unchanged
- Clients continue accessing the same endpoint
- Traffic routes automatically to healthy tasks only
Why This Matters
| 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 |
π Verifying Your Setup
After completing the setup, verify everything is working properly:
1. Check Target Health
Navigate to EC2 β Target Groups β Select your target group β Targets tab
- β Status should be “healthy” for your tasks
- β If “unhealthy,” check your security group settings and container configuration
:::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. :::
2. Check Load Balancer Listener
Navigate to EC2 β Load Balancers β Select your ALB β Listeners tab
- β Should have a listener for HTTP:80
- β Rules should forward to your target group
3. Check ECS Service
Navigate to ECS β Clusters β Your cluster β Services β Your service β Events tab
- β Should see successful registration of tasks with target group
π§ Troubleshooting Tips
-
Tasks aren’t registering with target group
- Ensure container port in task definition matches target group port
- Check if security groups allow traffic between ALB and tasks
- Verify that you’ve updated the ECS security group to allow inbound traffic from the ALB security group
-
Health checks failing
- Verify health check path exists in your application
- Ensure container is listening on the correct port
- Check security groups allow health check traffic from the ALB to the ECS tasks
-
Cannot access application via load balancer
- Verify load balancer security group allows inbound traffic
- Check if load balancer is in the same VPC as your tasks
- Ensure target group has healthy targets
- Run the following command to verify security group settings:
# 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
π Scaling Your Application
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 3The load balancer will automatically distribute traffic to all healthy tasks!
π Advanced Configurations
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" } }'
4οΈβ£ Verify Your Auto Scaling Setup
After setting up auto scaling, it’s important to verify that your tasks are running as expected.
Using AWS Console:
- Go to the ECS console
- Select your cluster rent-a-room-cluster
- Select your service rent-a-room-service
- Under the Tasks tab, you should see multiple tasks running
- Check the Events tab for scaling activities
Using AWS CLI:
# 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
fiUnderstanding the Output
The verification commands provide several key pieces of information:
Service Overview
desiredCount: Number of tasks you want runningrunningCount: Current number of tasks actually runningpendingCount: Tasks in the process of starting
Task List
- Shows all task ARNs currently registered with the service
- Helps verify the correct number of tasks are created
Detailed Task Information
TaskId: Unique identifier for each taskStatus: Current state of the task (RUNNING, PENDING, etc.)
π‘ Key Takeaways
- Load balancers are essential for production applications in ECS
- They provide a stable endpoint, health checks, and scaling capabilities
- The setup involves three main components: ALB, target group, and ECS service integration
- When tasks are updated or restarted, the load balancer handles the transition seamlessly
- Users experience no downtime when container IPs change
π Next Steps
Now that you’ve:
- β Added a load balancer to your ECS service
- β Created a stable endpoint for your application
- β Set up health checks and traffic distribution
- β Learned how IP changes are handled seamlessly
- β Set up auto scaling for your ECS service
- β Verified multiple tasks are running
- β Learned how to monitor your scaled service
β¨ Continue to CI/CD Pipeline Setup βΆοΈ