Recently, we transitioned our application architecture from API Gateway + Lambda to an ELB (ALB) + ECS on Fargate setup. The reason for this shift was that the incoming traffic had increased to the point where it exceeded the concurrent execution limits of Lambda, making a more stable scaling solution necessary.
While the move to ECS on Fargate improved our system's ability to handle increased load, the always-on pricing of ELB and Fargate, compared to Lambda's pay-per-invocation model, became a new concern for our overall costs.
In this article, I’ll summarize the cost-saving measures we implemented to mitigate these increased expenses.
1. Minimizing Resource Allocation for Development Environments
Fargate's pricing is based on the size of vCPUs and memory allocated to tasks. Therefore, in development environments, where traffic is typically low, minimizing resource allocation can lead to significant cost savings. We set the minimum configuration of 256 (.25 vCPU), 512 MiB
for our development environment.
Configuration Example:
You can configure the cpu
and memory
settings in the task definition as follows:
"cpu": 256,
"memory": 512,
This approach is particularly effective when your development environment doesn’t require high computational resources, leading to significant cost reductions.
2. Utilizing Fargate Spot
Fargate Spot instances are up to 70% cheaper than standard Fargate instances. We implemented Fargate Spot for our development environments to reduce costs. In production environments, however, we opted to continue using standard Fargate due to the need for stable performance.
The computing settings for ECS services can be configured as follows:
Configuration Example:
- Capacity Provider:
FARGATE_SPOT
- Base:
1
- Weight:
2
- Base:
- Capacity Provider:
FARGATE
- Base:
0
- Weight:
1
- Base:
The Intention Behind This Configuration
Fargate Spot is significantly cheaper than standard Fargate, but Spot instances can be interrupted by AWS when capacity is needed elsewhere. For development environments, where stability is not as critical, using only Fargate Spot is often sufficient. However, to ensure stability when needed, we configured the system to fall back to standard Fargate if Fargate Spot instances are unavailable.
In the above configuration, Spot instances are prioritized, but if they are unavailable, ECS will automatically switch to standard Fargate. Setting the base value of FARGATE
to 0
ensures that it is only used when Spot instances cannot be provisioned.
If you are comfortable with the risk, you can configure ECS to use only Fargate Spot for even greater cost savings:
- Capacity Provider: FARGATE_SPOT
- Base: 1
- Weight: 1
This configuration ensures that only Fargate Spot is used, fully accepting the risk of potential interruptions.
3. Stopping Development Environments During Off-Hours
Fargate is billed based on usage time, meaning that stopping tasks during non-working hours in development environments can significantly reduce costs.
We used EventBridge and Lambda to schedule task stop and start times. This automation ensures that tasks are stopped during nights and weekends, resulting in cost savings.
For more details on this setup, check out my earlier article:
Mastering ECS Task Scheduling: Effective Strategies to Reduce Costs
4. Migrating to Arm Architecture
Fargate pricing also depends on the architecture you use. By switching from the x86_64
architecture to the AWS Graviton-based Arm
architecture, you can reduce costs by up to 20%. Graviton is an Arm-based processor developed by AWS, offering better performance at a lower cost.
Additionally, as of September 2024, Fargate Spot supports Arm, making it even more cost-effective to adopt Arm architecture in development environments.
Amazon ECS now supports AWS Graviton-based Spot compute with AWS Fargate
Cross-Compilation Configuration for CI/CD Pipelines:
In our CI/CD workflow, we used docker buildx
to cross-compile images for the Arm architecture. Here’s the configuration:
- name: Set up QEMU
run: docker run --privileged --rm tonistiigi/binfmt --install all
- name: Set up Docker Buildx
run: docker buildx create --use
- name: Build Docker Image
run: docker buildx build --platform linux/arm64 --build-arg ENV=${{ env.ENV }} -t ${{ env.REPOSITORY_URI }}:${{ github.sha }} -f ./backend/Dockerfile.ecs ./backend --load
By specifying the --platform linux/arm64
option, the image is built for the Arm architecture. docker buildx
enables efficient multi-architecture image builds, allowing us to build images for both x86_64
and Arm
, making our infrastructure more flexible.
Task Definition Configuration:
In the task definition, you can specify the architecture using the runtimePlatform
field:
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
}
This change allows us to fully leverage the cost benefits of Arm while maintaining high performance.
5. Utilizing Compute Savings Plans (Not Yet Implemented)
Although we haven’t implemented this yet, we plan to utilize Compute Savings Plans in the future. This flexible pricing model allows you to commit to a consistent amount of compute usage (measured in USD/hour) over a 1- or 3-year period, offering significant discounts across EC2, Lambda, and Fargate.
For more information, visit Compute and EC2 Instance Savings Plans.
Conclusion
By implementing these cost optimization strategies—minimizing resources in development environments, leveraging Fargate Spot, switching to Arm architecture, and planning to use Compute Savings Plans—we were able to significantly reduce our ECS on Fargate costs while maintaining system stability where needed.
Top comments (0)