While the standard .NET approach to configuration management is easy to get started with, it really becomes a pain point as your application grows. Out of the box, gives us two static JSON config files – one that defines the default set of configurations for your app across all environments, and a second that provides overrides per environment by reading the app’s hosting environment (usually set via ASPNETCORE_ENVIRONMENT environmental variable). The configuration can also be overridden via Environmental variables or CLI args used to start your app. Let’s define some common goals we would normally want out of our configuration management approach and see how the default falls short of those goals:
- Reduce overhead of managing configurations across environments by having a set of common default values with overrides
- While we can define default config per app, those defaults are not shared across multiple apps. For example lets say we standardized on using NLog and wanted the same default configuration for all our apps, we would have to duplicate the config into each deployable project’s
appsettings.json
.
- While we can define default config per app, those defaults are not shared across multiple apps. For example lets say we standardized on using NLog and wanted the same default configuration for all our apps, we would have to duplicate the config into each deployable project’s
- Associate configuration with an environment
- Creating a new environment configuration will generally trigger whole continuous integration pipeline. If we have promotion policies associate with our continuous delivery workflow, config changes to upper environments will not make it without being first promoted from lower environments, even though no source code changes were made.
- Update configuration without redeploying or restarting the app
- CI/CD workflows will generally cause the app to be redeployed and restared. If we don’t have HA mode for our apps, we’ll also experience downtime
- Track current state of configuration in each environment
- If we’re relying on purely file based approach this is doable, but if any values were overriden via environmental variables or cli args (ex. sensitive credentials), we don’t have full picture
- Provide easy way to manage configuration changes
- Json files are icky to work with. They are easy to make mistakes in by mismatching curly braces, become very painful to add values that have quotes or line breaks in them. Ever try inserting a PEM encoded RSA key into your config? .NET serializer uses relaxed rules for trailing commas and comments – technically both are not allowed in json and will give you bunch of errors if you’re using IDEs that validate json.
- Ability to apply governance policies to changes in configuration in different environments
- While you there’s generally governance associated with your code CI/CD pipeline, sensitive values will often not be allowed to be checked into source control, and be configured as environmental variables on some node, as such there are inconsistencies in change controls for configuration.
- Audit changes to configuration over time
- While git history will show who made the change if everything is managed through files, changes to config values introduced via environmental variables and cli args do not usually leave an audit trail.
- Protect sensitive configuration, such as connection strings and credentials
- .NET Core and above doesn’t really havee anything to offer out of the box. Microsoft pushes the use of Azure KeyVault, which is both a paid service and ties you to Azure making it suboptimal for many usecases
We can solve the above goals quite elegantly by introducing Spring Cloud Config Server to manage all our config. Here’s how it works:
Spring Cloud Config Server runs as a simple HTTP service. It is configured to talk to one or more configuration source providers. In that way, it is nearly identical to how you can plug in different configuration sources into your app’s standard provider layers “cake” that makeup IConfiguration
. There are significant advantages to doing this over having each app connect to each source:
- each app does not need special libraries and credentials needed to connect to each backend
- configuration providers can be managed independent of app’s release lifecycle
- config server backends can be configured differently in deployment environments
- configuration is managed independent of your application source code
- supports “hot” config reloads
One of the most popular ways to use a config server is with a Git backend. By storing the config in Git we automatically get change history, we can apply change approval workflows with pull requests,
Let’s look at how that would work. First, you create a separate repo to store configuration – this can serve more than one app. The recommended format for config is YAML, since it’s much more human-friendly to work with than JSON. Inside the Git repo, we use the following file naming convention to produce a final set of configuration values:
application.yaml | Contributes configuration values applicable to all apps |
<APPNAME>.yaml | Per-app configuration value overrides |
<APPNAME>.<ENVIRONMENT>.yaml | Per-app-environment configuration value overrides |
The config server is accessible