Configuring Microservices

Setting up secrets and configuration for your microservices

Objective

In this section, we’ll show you how to configure your microservices with both configuration, and secrets. We’ll also show you how to use these in your code, and the process in which they are made accessible to your application in Kubernetes.

Prerequisites

Step-by-step

If you’re used to developing applications in a traditional way, you’ll be used to having configuration files, usually either an application.properties or an application.yaml.

This is a very popular format for Java based services. Both Micronaut and Spring Boot for instance will look for these files in the classpath (typically loaded from src/main/resources or src/test/resources (if running tests), and load them into the application.

Naturally, we want to be able to piggy-back on this, and use the same format for our microservices. However, we also find there is benefit in separating out distinctly on the application layer which configuration is sensitive, and which is not.

That is to say, we want to be able to separate out configuration that is not sensitive, such as the port number, from configuration that is sensitive, such as a database password.

1. The multi yamls

Take a look at the src/main/config directory in your microservice. You’ll notice that the application comes pre-loaded with an application.yaml file.

This is the part of your application config that is not treated as sensitive.

application.yaml

Now, take a look at the src/main/secrets directory. You’ll notice that there is also a application.yaml file. This is the part of your application config that is treated as sensitive.

application.yaml

These are intentionally distinct such that it is obvious to a developer which of these files are loaded into the relevant Kubernetes resource.

On that, let’s cycle back a bit. Kubernetes has the concept of configmaps and secrets, which are a way of storing information in a Kubernetes cluster that is related to your running application.

Without getting too deep on Kube concepts, you can naturally expect that the config yaml will be loaded into a K8s configmap, and the secrets yaml will be loaded into a K8s secret. This is exactly what happens.

💡
All Kubernetes resources are generated at build time. Take a look under `target/skaffold/kubernetes` to understand what is being deployed to Kube during the local deploy step.

4. Using config + secrets

Open src/main/java/io/practiv/starter/service/UserService.java. You’ll see the following:

application.yaml

As you can see, this is a fairly simple singleton class. It has a single method, getUser, which returns a User object. The User object is a simple POJO, with two fields, a name, and a greeting phrase.

@Value is a Micronaut + Spring annotation that allows you to inject configuration into your class. In this case, we’re injecting the app.greeting property from the application.yaml file.

The app.greeting property can be supplied at a few different levels. Let’s take a look at the options.

3. Variable substitution

You’ll notice that within these yaml files, there are references to unresolved variables. For example, in the application.yaml file in the src/main/config directory, you’ll see the following:

app:
  greeting: ${userGreetingPhrase}

micronaut:
  application:
    name: ${project.name}
  server:
    port: ${tile-deploy.port}

These vars will be resolved at any point up until deployment to a real environment. There are generally a few places that this can happen:

Build time

Any properties within the projects pom.xml file can be referenced in the yaml files. For example, the project.name property is defined in the pom.xml file. tile-deploy.port is defined in a tile, so you won’t see it in the pom. Though, if you want your app on a different port, you can override it in the pom.xml file like so:

<properties>
  <tile-deploy.port>8081</tile-deploy.port>
</properties>

Product aggregation time

When you’re building a product, you’ll be aggregating multiple microservices into a single product. Variables that are relevant to multiple services and don’t need to be specific per-environment can be defined on this level.

We’ll cover this in more detail in the product aggregation section.

Per environment config

Every environment has a repository associated with it. Config and secrets specific to a given environment can be defined in this repository.

We can define environment specific config within the environment.properties file at this level:

userGreetingPhrase="Hello from a delivery environment!"

Now, whenever we reference ${userGreetingPhrase} in either our config or secret yaml, it will be resolved to "Hello from a delivery environment!".

We’ll cover this in more detail in the environment section.

For local development only

For local development, we don’t have as much concern about security. Variables required to run your app locally in Kubernetes can be defined in src/test/resources/skaffold.properties.

application.yaml

This is quite powerful, as it gives you the ability to define config at multiple levels while still defining config for your app in a consistent way.

You’ll notice that when we run this app locally and hit the /user endpoint with a GET /user/Practiv, we get the following response:

{
  "name": "Practiv",
  "greeting": "Hello from a Kubernetes! Practiv"
}

This is due to the fact that we’ve configured the greeting response to be different when running our app locally within Kubernetes.

Extra knowledge: How does this work?

So we have a config.yaml and a secrets.yaml file.

If you haven’t recently, run either a

branchout maven cv

or a

skaffold dev

Now, let’s open target/skaffold/kubernetes/config.yaml: application.yaml

You’ll notice that our config is built up on a per-file basis. application.yaml has its own section. This is the contents of src/main/config/application.yaml, with the variables resolved through skaffold.properties.

Also take note of the other files loaded. We have both a logback.xml for our log format, some system properties, and also a version, section as a rule. If we were to query our apps /version endpoint - then this value will be outputted.

If you take a look at target/skaffold/kubernetes/secrets.yaml, you’ll also see an application.yaml section. This is the contents of src/main/secrets/application.yaml, with the variables resolved through skaffold.properties.

These files are both mounted to the container as configmaps and secrets respectively. Both application.yaml files are merged into one, and when the app runs, it points to this application yaml.

From the point of view of the running service, it just loads up a single application.yaml file.

💡
To see how the config map, and secret map are mounted - check out the `deployment.yaml` file, under the same directory.

Conclusion

In this tutorial, we’ve covered how to set up your application with config and secrets, in a Kubernetes native development pattern.

In a follow up guide, we will cover how you can use this pattern to deploy your app to a real environment using the environment method highlighted above, and also how to encrypt secrets at this layer.