AWS offers a pay-per-use pricing model, allowing businesses to scale their infrastructure as needed and align costs with requirements. This model minimises costs by avoiding charges for unused capacity. Automatic scaling and load balancing in serverless architectures further optimise resource allocation based on demand.
Software development organisations commonly maintain multiple discrete deployment environments. These environments include development stages where code is iterated and tested before promotion to the primary production environment. In AWS, each environment typically consists of one or more AWS accounts, acting as natural boundaries for contained resources.
From a billing perspective, AWS treats all environments equally. Whether a resource runs in a development or production account, the costs remain the same. By avoiding long-term commitments to “always on” infrastructure, organisations can control AWS service costs by turning off services in development environments when not in use. AWS App Runner is one such service that benefits from the pay-per-use pricing model.
What is AWS App Runner?
AWS App Runner is a fully managed service that simplifies the process of building, deploying, and scaling containerised applications. It offers developers a streamlined approach to quickly and easily deploy their code without the need for complex infrastructure configuration.
By leveraging AWS App Runner, developers can simply provide their source code or container image, and the service takes care of the rest. It automatically builds and deploys the application, handles scaling based on demand, and monitors and manages the application’s health. With App Runner, developers can deploy applications from various sources, including GitHub repositories, container registries, or local file systems. This allows for seamless integration with existing development workflows and enables rapid iteration and deployment of applications.
Optimising for cost in non-production environments
A neat feature of AWS App Runner is that a service can be paused. When paused, a service is no longer available to handle requests and is effectively offline. When a service is paused you no longer accrue any charges. This is great for development environments where you can tolerate not having a service running 24/7. A good use-case is to pause services in development environments outside of business hours, such as overnight and on weekends, as services often receive no traffic during such times.
In my experience it typically takes about 90 seconds for a paused service to become available after being resumed.
Scheduled pausing with EventBridge and Lambda
We can use EventBridge scheduled rules to build a simple solution which handles the pausing and resuming of our services on a defined schedule. With EventBridge we can create scheduled rules that invoke targets on a defined schedule. In this example we will invoke Lambda functions as our rule targets. One rule/function pair will handle the pausing the other, resuming.
Our Lambda functions are quite simple. To determine which services should be paused, we can simply iterate over the services defined in AppRunner by calling the ListServices API. For each service, we then query the tags attached to the service via the ListTagsForResource API. Only services matching a user-defined tag, provided to the function as an environment variable, will be paused/resumed. For example, you may wish to target only services tagged with 'environment': 'development'
or 'scheduled-pausing': 'true'
. How you tag your services and what tag you choose to match on for scheduling is ultimately up to you!
Optionally, our Lambda functions can notify an SNS topic when a service is paused/resumed. This can be a convenient sanity check to verify your service was in fact paused or resumed when they should be.
Simplifying with the AWS CDK
We can simplify the deployment of this architecture by using the AWS Cloud Development Kit (CDK). Here we have a custom construct that abstracts the infrastructure behind a simple-to-understand interface. Simply provide the service tag you wish to match on and your schedule configuration and deploy. That’s it!
new AwsAppRunnerScheduler(this, ‘ExampleScheduler’, { |
'environment': 'development'
will be paused at 02:00 UTC and resumed at 21:00 UTC.