Simplify application configuration with AWS AppConfig

Know How Guide and Hands on Guide for AWS

Simplify application configuration with AWS AppConfig

AWS AppConfig makes it easy for customers to quickly roll out application configurations across applications hosted on EC2 instances, containers, Lambdas, mobile apps, IoT devices, and on-premise servers in a validated, controlled and monitored way.

AWS AppConfig enable customer to manage configuration changes, similar to the way they manage code. Additional AWS AppConfig provide valuable features:

  1. Validate application’s configuration data against a schema or validate via a Lambda function. This ensure your configuration data is syntactically and semantically correct before making it available to your application
  2. Rolling out configuration changes over a defined time period while monitoring the application to catch any errors and rollback the changes if in case any errors occur
  3. Store and manage your configuration content similar to the way they manage code.

    The type can be either as Systems Manager Parameters or Systems Manager Documents

    • Systems Manager Parameters: configuration parameters such as URLs, certificates, accounts, and names
    • Systems Manager Documents: typed configuration data in structured formats such as JSON or YAML.
    • AWS AppConfig hosted configuration store: YAML, JSON, or text documents
    • Amazon S3 bucket: Objects

You can also integrate the AWS AppConfig in your CICD pipeline

![AppConfig-pipeline.png)

Now let’s build the QuickStart demo!

Create the AppConfig quickstart application

  1. Prepare the AppConfig ReadOnly permission
  1. Create configuration application

Navigate to AWS AppConfig console, Clicking Create configuration data to create a new application configuration

create-application

  1. Create environment
aws cloudwatch put-metric-data --metric-name production-error-count --namespace "Custom" \
--value 2 --region cn-northwest-1

create-alarm

create-environment

  1. Create the configuration profile

And trusted entity as appconfig.amazonaws.com

    {
        "Version": "2012-10-17",
        "Statement": [
            {
            "Effect": "Allow",
            "Principal": {
                "Service": "appconfig.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
            }
        ]
    }

Create the ProductionEnvProfile

create-configuration-profile

If you choice the AWS AppConfig hosted configuration, then input below JSON content

{
    "session":6000,
    "process":5000,
    "timeout":120,
    "poolsize":300
}

create-configuration-source

If you choice the AWS Systems Manager document, then New document

Deploy a configuration

An AWS AppConfig deployment strategy defines

Predefined deployment strategies:

  1. Creating a deployment strategy

Here we define: deployment of 20% to the hosts every 24 seconds (total 2mins). Additionally, we will configure a Bake time of 1min.

create-deployment-stategy

  1. Start deployment

start-deployment

start-deployment-processing

start-deployment-processing-bake

start-deployment-processing-complete

  1. Simulate the alarm for rollback

Start a new deployment

aws cloudwatch put-metric-data --metric-name production-error-count --namespace "Custom" \
--value 15 --region cn-northwest-1

start-deployment-rolledBack

Testing the validator

AWS AppConfig calls your validation Lambda when calling the StartDeployment and ValidateConfigurationActivity API actions.

  1. Prepare validators

test-validator-profile

Make sure your S3 bucket enable versioning

AppConfigLabS3ConfigurationProfile

test-validator-role-validator

test-validator-startdeploy

    {
      "$schema": "http://json-schema.org/draft-04/schema#",
      "title": "$id$",
      "description": "BasicFeatureToggle-1",
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
          "[^\\s]+$": {
              "type": "boolean"
          }
      },
      "minProperties": 1
    }

Receiving the configuration

  1. Using the CLI ```bash aws appconfig get-configuration
    –application AppConfigQuickStart –environment ProdEnv
    –configuration ProductionEnvProfile –client-id AppConfigQuickStartCLIClient
    configuration.json –region cn-northwest-1

cat devops/appconfig/configuration.json  ✔ { “session”:6000, “process”:5000, “timeout”:120, “poolsize”:300 }


2. Using Application Code - Here we use the lambda as example

- Permission

```json
{
"Effect": "Allow",
"Action": "appconfig:GetConfiguration",
"Resource": "arn:aws-cn:appconfig:cn-northwest-1:YOUR_ACCOUNT_NUMBER:*"
}
const AWS = require('aws-sdk');
const appconfig = new AWS.AppConfig({apiVersion: '2019-10-09'});

// add params and cache constants code here
const constParams = {
    Application: 'AppConfigQuickStart', /* required */
    Configuration: 'ProductionEnvProfile', /* required */
    Environment: 'ProdEnv', /* required */
};

let cachedParams = {};
let cachedConfigData = {};

exports.handler = async (event) => {
  /* 
  checks if ClientId is present in cachedParams and, if not, 
  adds the ClientId property to the cachedParams object
  using the value returned for the create_UUID function call
  */
  if (!cachedParams.ClientId) {
    cachedParams.ClientId = create_UUID();
    console.log('ClientId: ', cachedParams.ClientId);
  }

  /* 
  merges constParams and cachedParams into 
  a new params constant using spread operator
  and outputs params to the console 
  */
  const params = {...constParams, ...cachedParams};
  console.log('params: ', params);

  /* 
  calls GetConfiguration API and output response to the console  
  */
  const appConfigResponse = await appconfig.getConfiguration(params).promise();
  console.log('appConfigResponse: ', appConfigResponse);

  /* 
  decode the configuration content from the appconfigResponse and output decoded value to the console
  */
  const configData = Buffer.from(appConfigResponse.Content, 'base64').toString('utf-8');
  console.log('configData: ', configData);

  /* 
  checks if configData (appConfigResponse.Content) is present, if true, 
  adds the ClientConfigurationVersion property to cachedParams
  and updates the cached version of the configuration data for use on subsequent calls
  */
  if (configData) {    
    cachedParams.ClientConfigurationVersion = appConfigResponse.ConfigurationVersion;
    cachedConfigData = JSON.parse(configData);
  }
  console.log('cachedConfigData: ', cachedConfigData);

  let result = getServices();

  const response = {
    statusCode: 200,
    body: cachedConfigData,
  };
  return response;
};

function getServices() {
  return [
    {
      name: 'AWS AppConfig'
    },
    {
      name: 'Amazon SageMaker Studio'
    },
    {
      name: 'Amazon EKS'
    },
    {
      name: 'AWS Transit Gateway'
    }
  ];
}

function create_UUID(){
    let dt = new Date().getTime();
    const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = (dt + Math.random()*16)%16 | 0;
        dt = Math.floor(dt/16);
        return (c=='x' ? r :(r&0x3|0x8)).toString(16);
    });
    return uuid;
}

Testing Output:

Response:
{
  "statusCode": 200,
  "body": {
    "session": 6000,
    "process": 5000,
    "timeout": 120,
    "poolsize": 300
  }
}

After the second execution, observe that the is empty. This is because AppConfig will only send updates in subsequent calls for a given ClientId and ConfigurationVersion.

2020-09-29T01:52:05.903Z	c9500d5f-aad8-4a5f-8c4d-a8654d81986e	INFO	params:  {
  Application: 'AppConfigQuickStart',
  Configuration: 'ProductionEnvProfile',
  Environment: 'ProdEnv',
  ClientId: '546bd173-51fa-4c87-878a-08dae085f875',
  ClientConfigurationVersion: '1'
}
2020-09-29T01:52:06.287Z	c9500d5f-aad8-4a5f-8c4d-a8654d81986e	INFO	appConfigResponse:  {
  ConfigurationVersion: '1',
  ContentType: 'application/json',
  Content: <Buffer >
}
2020-09-29T01:52:06.287Z	c9500d5f-aad8-4a5f-8c4d-a8654d81986e	INFO	configData:  
2020-09-29T01:52:06.287Z	c9500d5f-aad8-4a5f-8c4d-a8654d81986e	INFO	cachedConfigData:  { session: 6000, process: 5000, timeout: 120, poolsize: 300 }
import boto3
import os
client = boto3.client('appconfig')

constParams = {
    'Application': 'AppConfigQuickStart', 
    'Configuration': 'ProductionEnvProfile',
    'Environment': 'ProdEnv'
}

def lambda_handler(event, context):
  print(constParams)
  response = client.list_applications(
    MaxResults=10
  )
  print(response)
  config = client.get_configuration(
    Application=constParams['Application'],
    Environment=constParams['Environment'],
    Configuration=constParams['Configuration'],
    ClientId=context.function_name
  )

  return {
    "statusCode": 200,
    "data": config['Content'].read().decode("utf-8") 
  }

Reference

AWS AppConfig Announce leter

aws-management.tools workshop - appconfig