Skip to content

[New Pattern] SQS - ECS - Target tracking autoscaling #2756

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions sqs-ecs-autoscaling/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3-alpine3.22

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY consumer/app.py .

CMD ["python", "app.py"]
117 changes: 117 additions & 0 deletions sqs-ecs-autoscaling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Implementing Target tracking scaling policies for an Amazon ECS Fargate application

This pattern creates a serverless architecture with an SQS queue consumed by an ECS Fargate service that automatically scales based on queue depth. It includes a VPC with private subnets, VPC endpoints for ECR, S3, CloudWatch Logs, and SQS, enabling the Fargate tasks to process messages without internet access. The target tracking scaling policy adjusts the number of tasks (1-10) based on the number of messages visible in the queue `ApproximateNumberOfMessagesVisible`, targeting 5 messages.

This pattern demonstrates how to effectively decouple your application components and tune your application scalability on the basis of incoming traffic flow.

Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/sqs-ecs-autoscaling](https://serverlessland.com/patterns/sqs-ecs-autoscaling)

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Docker](https://docs.docker.com/engine/install/)
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed


## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
1. Change directory to the pattern directory:
```
cd sqs-ecs-autoscaling
```

1. First of all, execute the build-image.sh script, that will create an ECR repository (if not existing), a docker image of your application. It will also push the built image to the newly created registry:
```
chmod +x build-image.sh
sh build-image.sh
```

1. Once the above build is successful, from the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
```
sam deploy --guided
```

1. During the prompts:
* Enter a stack name
* Enter the desired AWS Region
* Allow SAM CLI to create IAM roles with the required permissions.

Once you have run `sam deploy -guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults.

1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.

## Testing

1. Once the deployment is completed successfully, you need to test out how your application scales on the basis of incoming traffic flow. There is a stress testing script `stress-test-app.sh` which upon execution pushes messages in parallel and in bulk to your SQS queue:
```
chmod +x stress-test-app.sh
STACK_NAME=${SAM_STACK_NAME} ./stress-test-app.sh
```
Replace the `SAM_STACK_NAME` with the name of the stack you have used during sam deployment.
While you perform the testing, observe the scaling activities in the ECS console.

![auto-scaling](./images/auto-scaling.png)

2. Observe the CloudWatch logs of the application and the metrics of your cluster using [CloudWatch Container Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights.html). Use the following command to turn on Container Insights with enhanced observability.

Set the containerInsights account setting to enhanced
```
aws ecs update-cluster-settings --cluster CLUSTER_NAME --settings name=containerInsights,value=enhanced
```

By default, the put-account-setting applies only to the currently authenticated user. To enable the setting account-wide for all users and roles, use the root user as in the following example.
```
aws ecs put-account-setting --name containerInsights --value enhanced --principal-arn arn:aws:iam::accountID:root
```
After you set this account setting, all new clusters automatically use Container Insights with enhanced observability. Use the update-cluster-settings command to add Container Insights with enhanced observability to existing cluster, or to upgrade clusters that currently use Container Insights to Container Insights with enhanced observability.

```
aws ecs update-cluster-settings --cluster CLUSTER_NAME --settings name=containerInsights,value=enhanced
```

Replace CLUSTER_NAME with your ECS cluster name. Please refer to [this](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/deploy-container-insights-ECS-cluster.html) document to know how to enable it from AWS Console.

3. Once you have enabled container insights, re-run the stress testing script. In order to view the Container Insights, click the link as shown your ECS Cluster console.

![Insights-1](./images/insights-1.png)

Now, you can observe your Cluster summary and performance metrics at Cluster, Service, Task as well as at Container-level.

![Insights-2](./images/insights-2.png)

Observe the container level metrics below:

![Insights-3](./images/Insights-3.png)

Once the testing is completing, observe how the task count varies with the `ApproximateNumberOfMessagesVisible` metric of your SQS queue:

![Task-Count](./images/task-count.png)


## Cleanup

1. For deleting the stack you can use sam delete from SAM CLI -
```
sam delete
```

2. The delete the repostory:
```
aws ecr delete-repository --repository-name ${ECR_REPOSITORY_NAME} --force --region ${AWS_REGION}
```

Replace `ECR_REPOSITORY_NAME` with the one that you have created. Also, use the correct value for `AWS_REGION`.


----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
50 changes: 50 additions & 0 deletions sqs-ecs-autoscaling/build-image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/bin/bash
set -e

# Configuration
AWS_REGION=${AWS_REGION:-us-east-1}
ECR_REPOSITORY_NAME=${ECR_REPOSITORY_NAME:-sqs-ecs-app}
IMAGE_TAG=${IMAGE_TAG:-1.0}

# Check if running on macOS
if [[ "$(uname)" == "Darwin" ]]; then
echo "Running on macOS..."
# Check for Apple Silicon
if [[ "$(uname -m)" == "arm64" ]]; then
echo "Apple Silicon detected, using platform flag for Docker build..."
PLATFORM_FLAG="--platform linux/amd64"
else
PLATFORM_FLAG=""
fi
else
PLATFORM_FLAG=""
fi

# Create ECR repository if it doesn't exist
echo "Creating ECR repository if it doesn't exist..."
aws ecr describe-repositories --repository-names ${ECR_REPOSITORY_NAME} --region ${AWS_REGION} || \
aws ecr create-repository --repository-name ${ECR_REPOSITORY_NAME} --region ${AWS_REGION}

# Get ECR login
echo "Logging in to ECR..."
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin $(aws sts get-caller-identity --query Account --output text).dkr.ecr.${AWS_REGION}.amazonaws.com

# Build the Docker image
echo "Building Docker image..."
docker build ${PLATFORM_FLAG} -t ${ECR_REPOSITORY_NAME}:${IMAGE_TAG} .

# Tag the image for ECR
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_REPO_URI=${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}
docker tag ${ECR_REPOSITORY_NAME}:${IMAGE_TAG} ${ECR_REPO_URI}:${IMAGE_TAG}

# Push the image to ECR
echo "Pushing image to ECR..."
docker push ${ECR_REPO_URI}:${IMAGE_TAG}

echo "Image URI: ${ECR_REPO_URI}:${IMAGE_TAG}"

sed -i '' "s|ECR_REPO_URI|$ECR_REPO_URI|g" template.yaml
sed -i '' "s|IMAGE_TAG|$IMAGE_TAG|g" template.yaml

echo "Done!"
87 changes: 87 additions & 0 deletions sqs-ecs-autoscaling/consumer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# SPDX-License-Identifier: MIT-0

import os
import boto3
import json
import logging
import time
from botocore.exceptions import ClientError

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize AWS SQS client
sqs = boto3.client('sqs')
queue_url = os.environ.get('QUEUE_URL')

if not queue_url:
raise ValueError("QUEUE_URL environment variable is required")

def process_message(message_body):
"""
Process the message from the queue.
Replace this with your actual message processing logic.
"""
try:
data = json.loads(message_body)
logger.info(f"Processing message: {data}")
# Add your processing logic here

# Simulate some work
time.sleep(1)

return True
except Exception as e:
logger.error(f"Error processing message: {e}")
return False

def main():
logger.info(f"Starting SQS consumer for queue: {queue_url}")

while True:
try:
# Receive messages from SQS queue
response = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10, # Fetch up to 10 messages at once
WaitTimeSeconds=20, # Long polling
VisibilityTimeout=30 # 30 seconds to process each message
)

messages = response.get('Messages', [])

if not messages:
logger.debug("No messages received")
continue

logger.info(f"Received {len(messages)} messages")

for message in messages:
message_body = message['Body']
receipt_handle = message['ReceiptHandle']

if process_message(message_body):
# Delete message after successful processing
try:
sqs.delete_message(
QueueUrl=queue_url,
ReceiptHandle=receipt_handle
)
logger.info("Message processed and deleted successfully")
except ClientError as e:
logger.error(f"Error deleting message: {e}")
else:
logger.error("Failed to process message")

except ClientError as e:
logger.error(f"Error receiving messages: {e}")
time.sleep(5) # Wait before retrying
except Exception as e:
logger.error(f"Unexpected error: {e}")
time.sleep(5) # Wait before retrying

if __name__ == "__main__":
main()
63 changes: 63 additions & 0 deletions sqs-ecs-autoscaling/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"title": "SQS - Amazon ECS Fargate - Target Tracking Scaling Policy",
"description": "This pattern creates an ECS Fargate application that consumes an SQS queue and it scales on the basis of queue size",
"language": "Python",
"level": "400",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This sample project demonstrates how target tracking scaling policy works with an ECS Fargate application",
"We have a python application under consumer/app.py that consumes messages from an SQS queue. The application is containerised and the image is pushed to an ECR registry.",
"On the basis of a target value of the SQS metric - ApproximateNumberOfMessagesVisible, the count of tasks of the cluster will increase.",
"Again, when the actual value drops below the target value, the count of tasks comes down to the initial value."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sqs-ecs-autoscaling",
"templateURL": "serverless-patterns/sqs-ecs-autoscaling",
"projectFolder": "sqs-ecs-autoscaling",
"templateFile": "sqs-ecs-autoscaling/template.yaml"
}
},
"resources": {
"bullets": [
{
"text": "Target tracking scaling policies for Amazon EC2 Auto Scaling",
"link": "https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scaling-target-tracking.html"
},
{
"text": "Target tracking scaling policies for Application Auto Scaling",
"link": "https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-target-tracking.html"
},
{
"text": "Container Insights with enhanced observability now available in Amazon ECS",
"link": "https://aws.amazon.com/blogs/aws/container-insights-with-enhanced-observability-now-available-in-amazon-ecs/"
}
]
},
"deploy": {
"text": [
"sam deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>sam delete</code>."
]
},
"authors": [
{
"name": "Saborni Bhattacharya",
"image": "https://drive.google.com/file/d/1AZFquOkafEQRUlrT4hKOtIbt4Cq66SHd/view?usp=sharing",
"bio": "Specialist SA at AWS EMEA",
"linkedin": "https://www.linkedin.com/in/saborni-bhattacharya-5b523812a/"
}
]
}
Binary file added sqs-ecs-autoscaling/images/Insights-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sqs-ecs-autoscaling/images/auto-scaling.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sqs-ecs-autoscaling/images/insights-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sqs-ecs-autoscaling/images/insights-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added sqs-ecs-autoscaling/images/task-count.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions sqs-ecs-autoscaling/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
boto3==1.26.137
botocore==1.29.137
Loading