Multi-environment configuration pattern

There is often a need for multiple IT environments in larger organizations, and maintenance can often be a big problem. Using this configuration pattern, we can simplify and scale our IT platforms.

Note

This example is a for larger and more complex setups and is useful when you need to maintain:

  • Multiple test environments

  • Global deployments (one environment per region)

  • Disaster recovery environments

If you have a simpler setup with two or three environments, it’s often easier to use a more straightforward approach using the parameter sections in attini-config or AttiniCfn.

Example scenario

In this example, we will configure a VPC in:

  • 2 development environments for our 2 development teams.

  • 1 staging environment where new production-ready code is tested (integration tests, load tests, etc.).

  • 1 production environment for live applications.

  • 0-many (0:N) sandbox environments that are used for bigger projects or intrusive POCs.

Requirements:

  1. The configuration VpcCidr should be unique for development, staging and production.

  2. In sandbox environments, we want to use a default VpcCidr. However, we want to be able to override the default when required.

  3. The configuration HighAvailability (boolean) should be shared across development and sandbox environments.

  4. The configuration HighAvailability (boolean) should be shared across staging and production environments.

  5. A TransitGatewayId should be shared across all environments.


MultiEnvironmentConfig-Hierarchy

attini-config

Start by configuring the variables section in the attini-config file.

distributionName: network
initDeployConfig:
  template: /deployment-plan.yaml
  stackName: ${environment}-${distributionName}-deployment-plan
  variables:
    default:
      ConfigEnv: sandbox
    dev1:
      ConfigEnv: ${environment}
    dev2:
      ConfigEnv: ${environment}
    staging:
      ConfigEnv: ${environment}
    production:
      ConfigEnv: ${environment}

Configuration environment (ConfigEnv)

ConfigEnv is needed because of the sandbox environments. We don’t know the name of the sandbox environments at package/build time (they could be named “project-A” or “sandbox-2” etc.), so we can’t use the environment name to find the correct config. But if we use a variable (like ConfigEnv) to choose our configuration, we can use Attinis default feature to specify the sandbox configuration if environment-specific configuration is missing.

The other environments can use the ${environment} pseudo variable so that we guarantee that they don’t use the wrong configuration by mistake.


Configuration files

├── /config/default-parent.yaml
├── /config/dev-parent.yaml
├── /config/production-parent.yaml
├──────────────────────────────────────
├── /config/sandbox.yaml
├── /config/dev1.yaml
├── /config/dev2.yaml
├── /config/staging.yaml
└── /config/production.yaml

Parent configuration files

“/config/default-parent.yaml”
parameters:
  TransitGatewayId: tgw-0262a0e521EXAMPLE

Note that dev-parent and production-parent extends the default-parent file.

“/config/dev-parent.yaml”
extends: /config/default-parent.yaml
parameters:
  HighAvailability: False
“/config/production-parent.yaml”
extends: /config/default-parent.yaml
parameters:
  HighAvailability: True

Environment-specific configuration files

For the sandbox configuration we use 10.0.0.0/16 as a default, and if someone populates AWS SSM Parameter Store with the parameter /${Environment}/vpc/cidr, that value will be used. This way, the package/build phase can remain unaware of the sandbox environments.

Find more information about the Attinis AWS SSM Parameter Store integration here.

“/config/sandbox.yaml”
extends: /config/dev-parent.yaml
parameters:
  VpcCidr:
    type: ssm-parameter
    value: /${Environment}/vpc/cidr
    default: 10.0.0.0/16

Now let’s create the other environment files:

Note that dev1, dev2 and sandbox extends the dev-parent.yaml.

“/config/dev1.yaml”
extends: /config/dev-parent.yaml
parameters:
  VpcCidr: 10.1.0.0/16
“/config/dev2.yaml”
extends: /config/dev-parent.yaml
parameters:
  VpcCidr: 10.2.0.0/16

Note that staging and production extends the production-parent.yaml.

“/config/staging.yaml”
extends: /config/production-parent.yaml
parameters:
  VpcCidr: 10.3.0.0/16
“/config/production.yaml”
extends: /config/production-parent.yaml
parameters:
  VpcCidr: 10.4.0.0/16

Deployment plan

Then we configure the Attini deployment plan like this (see ConfigFile) to always use the correct configuration file.

Resources:
  DeploymentPlan:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        StartAt: Vpc
        States:
          Vpc:
            Type: AttiniCfn
            Properties:
              StackName: vpc
              Template: /vpc.yaml
              ConfigFile: /config/${ConfigEnv}.yaml
            End: True

More configuration options

Overrides

If we need to override the HighAvailability parameter for one environment, that’s easily done due to the priority hierarchy. For example, in the file below, we now made dev2 highly available.

“/config/dev2.yaml”
extends: /config/dev-parent.yaml
parameters:
  VpcCidr: 10.2.0.0/16
  HighAvailability: True

Fallback

Another common scenario is the ability to do manual configuration straight in the AWS console or using the AWS CLI. The problem is that a deployment framework will always rollback manual changes during the next deployment. This automatic rollback behavior is often desirable, but not always. Therefore Attini supports fallback configuration options, meaning that Attini will always respect the current configuration.

Let’s say that we sometimes need dev2 to be highly available. We can then update the configuration file to look like this:

“/config/dev2.yaml”
extends: /config/dev-parent.yaml
parameters:
  VpcCidr: 10.2.0.0/16
  HighAvailability:
    fallback: True
    value: False

Then the development team can easily update HighAvailability parameters straight in the AWS console, and that change will be respected by the next deployment.

Note

Using the fallback configuration option comes with risks of configuration drifts between your IT environments and your source control.

Any parameter using the fallback options will require manual maintenance, so keep its usage to a minimum.

We recommend using the AWS::CloudFormation::Interface feature to clearly define which parameters have to be manually maintained.

Additional CloudFormation configuration

The configuration files can configure anything in the AttiniCfn type, so you can also use the files to configure stack names, IAM roles, templates path etc.

If you for example have an inconsistent naming convection, let’s say that you have 2 VPC stacks, one called vpc for the production VPC, and another called staging-vpc for the staging VPC.

You can easily configure Attini to manage these edge cases:

“/config/staging.yaml”
extends: /config/production-parent.yaml
stackName: staging-vpc
parameters:
  VpcCidr: 10.3.0.0/16
“/config/production.yaml”
extends: /config/production-parent.yaml
stackName: vpc
parameters:
  VpcCidr: 10.4.0.0/16

More information

Find more information about Attini configuration options here: