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
- Creating your first microservice - You should have a microservice created and running locally.
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.
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.
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.
4. Using config + secrets
Open src/main/java/io/practiv/starter/service/UserService.java
. You’ll see the following:
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
.
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
:
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.
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.