This blog post is all about AWS AppConfig and how we can use it to inject configuration at runtime.
As a part of the series Serverless Rust you can check out the other parts:
Part 1 and 2 describe how to set up Rust and VsCode.
Part 3 how to process in parallel AWS SQS messages.
Part 4 how to execute AWS Step Function for each AWS SQS message received.
AWS AppConfig
AWS AppConfig is part of AWS Systems Manager, and you can use it to create, manage, and deploy application configurations. In addition, it is compatible with AWS Lambda and many other services.
Based on the developer user guide AWS AppConfig offers the following benefits:
- Reduce errors in configuration changes - it provides JSON schema and AWS Lambda function validation.
- Deploy changes across a set of targets quickly - and has simple integration with AWS Lambda.
- Update applications without interruptions - without taking your targets out of service.
- Control deployment of changes across your application - you can enable deployment strategies
To configure AWS AppConfig, I should setup three types of resources:
- Application
- Environment
- Configuration profile
This is an example of a CloudFormation Template to configure AppConfig:
// defines the “application”, the component where we group all our configurations
MyApplication:
Type: AWS::AppConfig::Application
Properties:
Name: "MyTestApplication"
// defines a logical deployment group of AppConfig targets like test, stage, prod
Environment:
Type: AWS::AppConfig::Environment
Properties:
Name: "MyTestEnvironment"
ApplicationId: { Ref: MyApplication }
// defines the configuration, what in the end we will refer from the lambda to get the JSON
MyConfigurationProfile:
Type: AWS::AppConfig::ConfigurationProfile
Properties:
ApplicationId: !Ref MyApplication
Name: "MyTestProfile"
LocationUri: "hosted"
// A deployment strategy defines critical criteria for rolling out your configuration to the designated targets
MyDeploymentStrategy:
Type: AWS::AppConfig::DeploymentStrategy
Properties:
Name: "MyTestDeploymentStrategy"
Description: "A deployment strategy to deploy the config immediately"
DeploymentDurationInMinutes: 0
FinalBakeTimeInMinutes: 0
GrowthFactor: 100
GrowthType: LINEAR
ReplicateTo: NONE
// It is the “glue” that connects a configuration to an application JSON that defines the
// it is possible to avoid hardcoding values by using transformations and S3 for the uploading phase only.
BasicHostedConfigurationVersion:
Type: AWS::AppConfig::HostedConfigurationVersion
Properties:
ApplicationId: !Ref MyApplication
ConfigurationProfileId: !Ref MyConfigurationProfile
Description: 'A sample hosted configuration version'
ContentType: 'application/json'
Content: |
{
"name": "ExampleApplication",
"id": 1,
"rank": 7
}
// starts the deployment of the configuration
AppConfigDeployment:
Type: AWS::AppConfig::Deployment
Properties:
ApplicationId: !Ref MyApplication
ConfigurationProfileId: !Ref MyConfigurationProfile
ConfigurationVersion: !Ref BasicHostedConfigurationVersion
DeploymentStrategyId: !Ref MyDeploymentStrategy
EnvironmentId: !Ref Environment
In this example, I pass a configuration hardcoding its value in the template. It is not possible to load a JSON file from your local pipeline or filesystem. So if you decide to hardcode the configuration, remember there is a limitation of 4KB.
Suppose you want to use more than 4KB or do not like hardcode configuration into the template. In that case, you can upload it beforehand to AWS S3 and change the HostedConfigurationVersion section into:
BasicHostedConfigurationVersion:
Type: AWS::AppConfig::HostedConfigurationVersion
Properties:
ApplicationId: !Ref MyTestApplication
ConfigurationProfileId: !Ref ConfigurationProfileTest
Description: 'A sample hosted configuration version'
Fn::Transform:
Name: AWS::Include
Parameters:
Location: !Sub s3://${configBucketName}/${configurationName}.json
ContentType: application/json
AWS Lambda integration
Thanks to AWS AppConfig Lambda extension as Lambda layer, the integration is simple, and with a few Environment variables to set the configuration is completed.
Environment:
Variables:
APP_CONFIG_NAME: MyTestApplication
AWS_APPCONFIG_ENVIRONMENT: MyTestEnvironment
AWS_APPCONFIG_PROFILE: MyTestProfile
AWS_APPCONFIG_EXTENSION_HTTP_PORT: 2772
Layers:
- arn:aws:lambda:eu-central-1:066940009817:layer:AWS-AppConfig-Extension:54
In concrete, to retrieve a specific configuration, you need to send a GET request from your Lambda to localhost, using the following URL convention:
async fn fetch_config(appconfig_profile: &str) -> Result<MyConfig, ApplicationError> {
let appconfig_name = std::env::var("APP_CONFIG_NAME").expect("APP_CONFIG_NAME must be set");
let appconfig_env = std::env::var("AWS_APPCONFIG_ENVIRONMENT").expect("AWS_APPCONFIG_ENVIRONMENT must be set");
let appconfig_port = std::env::var("AWS_APPCONFIG_EXTENSION_HTTP_PORT").expect("AWS_APPCONFIG_EXTENSION_HTTP_PORT must be set");
let url = format!("http://localhost:{}/applications/{}/environments/{}/configurations/{}", appconfig_port, appconfig_name, appconfig_env, appconfig_profile);
log::info!("URL {:?}", url);
let response = reqwest::get(url)
.await?
.json::<MyConfig>()
.await?;
Ok(response)
}
The complete example is available on GitHub.
AWS AppConfig - extras
- By default, you have 1000 TPS, and in a serverless world, it is easy to reach it. You can request an increase of quota opening a ticket.
- After the first request, the profile is cached, and so from now on, you hit the internal cache of AppConfig. What I have noticed is that even if you have this in place when you reach the 1000 TPS, you will get an error like invalid JSON response body at localhost:2772/applications/MyTestApplicati..: Unexpected token R in JSON at position 0
- Plan for retries for when AppConfig returns A 504 Gateway Timeout Error.
- Use the Lambda function context to cache the configuration you already have retrieved to avoid calling AWS AppConfig (point 2).
- You can access AWS AppConfig configuration from another account and use it as a central point for an application composed of multiple teams.
- You can prefetch configuration data.
A note for AWS AppConfig pricing
AWS AppConfig is expensive if you have a lot of cold starts, but in a cluster setup, this problem is not so evident.
This is the AppConfig bill for December 2021
Without going into detail, if I keep strictly on a B2B serverless application where cold starts are common, for example, my last seven days:
The AWS AppConfig price is much higher ($60 from the billing report) than the other serverless services like Lambda functions, for example, which cost $6.
Conclusions
Many will tell you to use AWS S3 to inject configuration, but AWS AppConfig is a good service. Of course, it can be cheaper, and it would be nice to improve the AWS Console experience. Still, I can validate and deploy configuration changes from a central location without interruptions, which could come in handy.