Anthony Chu Contact Me

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:

localhost

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!

secrets!

We can also peek inside a running container in the Kubernetes cluster to see what's going on:

terminal

Source code

https://github.com/anthonychu/aspnet-core-secrets-kubernetes/