Managing ASP.NET Core App Settings on Kubernetes
Tuesday, March 14, 2017
Kubernetes is quickly becoming my favorite container orchestrator. Everything, so far, has been intuitive and it looks like they've put a lot of thought into how all the pieces fit together. One example is how it handles configuration and secrets.
Today we'll look at how to use secrets in Kubernetes to override some properties in an ASP.NET Core app's configuration at runtime.
The sample application
Before we look at Kubernetes, let's quickly look at our example application. It's a super simple ASP.NET Core app that prints a few lines to the screen. Here's the Configure
method in startup.cs
:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("secrets/appsettings.secrets.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
loggerFactory.AddConsole();
app.Run(async (context) =>
{
var message = $"Host: {Environment.MachineName}\n" +
$"EnvironmentName: {env.EnvironmentName}\n" +
$"Secret value: {Configuration["Database:ConnectionString"]}";
await context.Response.WriteAsync(message);
});
}
This will print out 3 things:
- Machine name - this will be the pod name in Kubernetes
- ASP.NET environment name - this is set via the
ASPNETCORE_ENVIRONMENT
environment variable
- A secret configuration value - we'll use
Database:ConnectionString
as an example
In the ConfigurationBuilder
, we're telling ASP.NET to get its app settings from appsettings.json
, then from a file named secrets/appsettings.secrets.json
, then finally from the environment. If a value exists in multiple places, the last one that gets applied wins.
This means that if there is a file named secrets/appsettings.secrets.json
, we can use it to override values in appsettings.json
.
Of course, we'll need to build this image in Docker and push it to a registry like Docker Hub before Kubernetes can use it. We won't cover that here.
If we run the container locally, we'll see this in the browser:
It's pulling the connection string value from the default appsettings.json
. If we wanted to, we could override it by setting an environment variable named Database__ConnectionString
or by somehow putting a file in the container at /app/secrets/appsettings.secrets.json
.
Creating a secret
Kubernetes has support for runtime configuration via Secrets and ConfigMaps. Right now, there isn't a huge difference in how secrets and configmaps work, but I would imagine they'll make storing secrets more secure in the future.
Kubernetes secrets can hold one or more files. Secrets can be mounted as a directory on a pod and the files that it contains are available in the file system. This makes it great for things like certificates; and we can use it to drop an app settings file in a pod to override an ASP.NET Core app's existing app settings.
Secrets can also be passed to containers as environment variables. But today, we'll stick to mounting them as files in the file system.
Say we have an ASP.NET Core app settings file that contains secret information like this one named appsettings.secrets.json
:
{
"Database": {
"ConnectionString": "Secret from kubernetes!!!"
}
}
We can use the Kubernetes CLI to add it as a secret:
$ kubectl create secret generic secret-appsettings --from-file=./appsettings.secrets.json
We've created a generic secret with an name of secret-appsettings
that can now be used in our pods.
Using the secret
It's pretty easy to use the secrets. We can do that by adding a few lines to the template we use to create our deployment:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: aspnet-core-secrets-demo
spec:
replicas: 3
template:
metadata:
labels:
app: aspnet-core-secrets-demo
spec:
containers:
- name: aspnet-core-secrets-demo
image: anthonychu/aspnet-core-secrets:latest
ports:
- containerPort: 80
env:
- name: "ASPNETCORE_ENVIRONMENT"
value: "Kubernetes"
volumeMounts:
- name: secrets
mountPath: /app/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: secret-appsettings
The key lines are the at the end of the file. We create a volume named secrets and mount it at /app/secrets
.
We are also setting the ASPNETCORE_ENVIRONMENT
environment variable to Kubernetes
, in case you wonder where that's coming from when we run it.
All we have to do now is create our deployment:
$ kubectl create -f deployment.yaml
(We also have to create a service of type LoadBalancer
to expose it to the outside world. Check out the source code to see that.)
Now if we hit the external endpoint for our service, we see that the secrets have been applied!
We can also peek inside a running container in the Kubernetes cluster to see what's going on:
Source code
https://github.com/anthonychu/aspnet-core-secrets-kubernetes/
Kubernetes is quickly becoming my favorite container orchestrator. Everything, so far, has been intuitive and it looks like they've put a lot of thought into how all the pieces fit together. One example is how it handles configuration and secrets.
Today we'll look at how to use secrets in Kubernetes to override some properties in an ASP.NET Core app's configuration at runtime.
The sample application
Before we look at Kubernetes, let's quickly look at our example application. It's a super simple ASP.NET Core app that prints a few lines to the screen. Here's the Configure
method in startup.cs
:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("secrets/appsettings.secrets.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
loggerFactory.AddConsole();
app.Run(async (context) =>
{
var message = $"Host: {Environment.MachineName}\n" +
$"EnvironmentName: {env.EnvironmentName}\n" +
$"Secret value: {Configuration["Database:ConnectionString"]}";
await context.Response.WriteAsync(message);
});
}
This will print out 3 things:
- Machine name - this will be the pod name in Kubernetes
- ASP.NET environment name - this is set via the
ASPNETCORE_ENVIRONMENT
environment variable - A secret configuration value - we'll use
Database:ConnectionString
as an example
In the ConfigurationBuilder
, we're telling ASP.NET to get its app settings from appsettings.json
, then from a file named secrets/appsettings.secrets.json
, then finally from the environment. If a value exists in multiple places, the last one that gets applied wins.
This means that if there is a file named secrets/appsettings.secrets.json
, we can use it to override values in appsettings.json
.
Of course, we'll need to build this image in Docker and push it to a registry like Docker Hub before Kubernetes can use it. We won't cover that here.
If we run the container locally, we'll see this in the browser:
It's pulling the connection string value from the default appsettings.json
. If we wanted to, we could override it by setting an environment variable named Database__ConnectionString
or by somehow putting a file in the container at /app/secrets/appsettings.secrets.json
.
Creating a secret
Kubernetes has support for runtime configuration via Secrets and ConfigMaps. Right now, there isn't a huge difference in how secrets and configmaps work, but I would imagine they'll make storing secrets more secure in the future.
Kubernetes secrets can hold one or more files. Secrets can be mounted as a directory on a pod and the files that it contains are available in the file system. This makes it great for things like certificates; and we can use it to drop an app settings file in a pod to override an ASP.NET Core app's existing app settings.
Secrets can also be passed to containers as environment variables. But today, we'll stick to mounting them as files in the file system.
Say we have an ASP.NET Core app settings file that contains secret information like this one named appsettings.secrets.json
:
{
"Database": {
"ConnectionString": "Secret from kubernetes!!!"
}
}
We can use the Kubernetes CLI to add it as a secret:
$ kubectl create secret generic secret-appsettings --from-file=./appsettings.secrets.json
We've created a generic secret with an name of secret-appsettings
that can now be used in our pods.
Using the secret
It's pretty easy to use the secrets. We can do that by adding a few lines to the template we use to create our deployment:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: aspnet-core-secrets-demo
spec:
replicas: 3
template:
metadata:
labels:
app: aspnet-core-secrets-demo
spec:
containers:
- name: aspnet-core-secrets-demo
image: anthonychu/aspnet-core-secrets:latest
ports:
- containerPort: 80
env:
- name: "ASPNETCORE_ENVIRONMENT"
value: "Kubernetes"
volumeMounts:
- name: secrets
mountPath: /app/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: secret-appsettings
The key lines are the at the end of the file. We create a volume named secrets and mount it at /app/secrets
.
We are also setting the ASPNETCORE_ENVIRONMENT
environment variable to Kubernetes
, in case you wonder where that's coming from when we run it.
All we have to do now is create our deployment:
$ kubectl create -f deployment.yaml
(We also have to create a service of type LoadBalancer
to expose it to the outside world. Check out the source code to see that.)
Now if we hit the external endpoint for our service, we see that the secrets have been applied!
We can also peek inside a running container in the Kubernetes cluster to see what's going on:
Source code
https://github.com/anthonychu/aspnet-core-secrets-kubernetes/