One Chart To Rule Them All
So you've written a Spring Boot application. You want to deploy it to a Kubernetes cluster so you Google how to go about it. You'll probably find solutions using Helm charts, which works for me. But all the suggestions/variations I found have one problem: they cloud the separation between Spring Boot and Kubernetes domains. In this post I will explain what I mean by this and show a solution that works for both domains without having to have much knowledge about the other.
For whom?
- Spring Boot developers that don't (want to) know much about Kubernetes/helm charts
- Helm (OPS) developers that don't know much about Spring Boot
- DevOps people that are comfortable working in both domains, I think I can still simplify your work
The app
I've created an example app that has two REST endpoints that simply return a (configured) message. It also contains two extra endpoints that are switched on/off by feature toggles. You can find the git repo here:
See the README file for instructions how to run it.
Spring Boot configuration
When writing a Spring Boot application I like to start of with an application.yaml
with my main configuration without any local config. If I want to talk to a local Postgres database, have debug logging etc. I don't want it in application.yaml
. I add an application-local.yaml
for this with my database config, feature toggles, log settings, etc.
demoApp:
features:
alpha: false
beta: false
messages:
messageOfTheDay: "Today's a good day for coffee!"
application.yaml
Both feature toggles are set to 'off' (sensible defaults) and I've not yet thought of a demoApp.messages.quote
which is missing from this file. Running the app with only this will result in an error (like missing a database URL).
For local development I do want to check the alpha feature and remind myself of important stuff. And of course fix the missing value for quote
:
demoApp:
features:
alpha: true
messages:
messageOfTheDay: "Go outside and enjoy the sun!"
quote: "May al your threads be lightweight"
application-local.yaml
If you run this app with the Spring profile local
enabled, you will see the new messageOfTheDay
when calling http://localhost:8080/messages/motd. Also the AlphaController
is enabled, giving you http://localhost:8080/alpha/awesomeResult
Application tested, we can now move on to Kubernetes and we need a Helm chart for this.
How not to create your Helm chart
As Helm charts as a whole are another domain and not the focus of this post (I want to give you one chart, remember?) I won't go into much detail and just point out the flaws I see with the Google suggested approaches and present you with my solution.
The internet suggests these two solutions when creating a Helm chart for Spring Boot: use environment variables or define a ConfigMap. Both are shown in this blogpost:
My problems with these solutions are:
- Your Spring Boot configurable properties are now a part of the Helm domain (either in the
Deployment
orConfigMap
) - The actual values will be in a
values.yaml
that might be in the same layout as yourapplication.yaml
, but no guarantee - The Spring Boot properties and the values are now separated and must be glued back together with Helm 'coding'
- Adding a property to your project involves changing Helm files and have you type more glue Helm code
All in all a complex process with a real possibility of errors.
Let Spring Boot be Spring Boot
Why create something new when we have something that we know and already works? Your carefully crafted Spring Boot application.yaml
has a nice structure and sensible defaults. You've created an application-local.yaml
with overrides/additions to those defaults. Let's just use this override approach in the Helm chart!
First, lets bring your application-local.yaml
over to Helm, because the sensible defaults are available in application.yaml
which is packaged with the Spring Boot app.
You can just copy your application-local.yaml
contents into Helm's values.yaml
, but not in the root of the yaml, we'll create an extra key (let's say springConfig
) to set it apart from the actual Kubernetes stuff like replicas, resource limits etc.
replicas: 1
springConfig:
demoApp:
features:
alpha: true
messages:
quote: "Kubernetes all the way!"
values.yaml
And of course you can add/remove properties here or change values, just like you would in application*.yaml
.
The one element to bring these worlds together
Spring Boot has a lot of ways to bind configuration to your application as shown in the Spring Boot documentation. The best choice for this is the environment variable SPRING_APPLICATION_JSON
which is explained here.
Now we need to get the springConfig
from values.yaml
into this environment variable, this can be done in one go:
spec:
containers:
- name: helm-demo
env:
- name: SPRING_APPLICATION_JSON
value: {{ .Values.springConfig | toJson | quote }}
- This creates an
env
variable with the nameSPRING_APPLICATION_JSON
for the container running our Spring Boot app - The
value
of the variable is taken from thespringConfig
entry of the.Values
- which is converted to JSON
- and then surrounded with quotes (to prevent error messages from mixing JSON and YAML)
Deploy this chart using helm install helm-demo . -f values.yaml
(for detailed instructions see the README in the git repo)
More than one environment?
In Kubernetes you'll probably have a test, acceptance and production environment (maybe more). For Spring we can use Spring Profiles to define different values for different profiles.
In Helm you would take a similar approach: just create a values-test.yaml
and because the solution I've shown above is so generic, you can just override one property for test:
springConfig:
features:
alpha: true
application-test.yaml
To deploy this chart for your test environment, simply add the new values file to your commandline: helm install helm-demo . -f values.yaml -f values-test.yaml
Why this works
Of course there are things not covered in this post that require a bit more in-depth Helm/Kubernetes knowledge (e.g. password handling), but overall this solution has brought simplicity for several reasons:
- Your whole Spring Boot configuration is kept together in
values.yaml
- It uses the same structure as your
application*.yaml
during development - Adding properties to your app configuration can be copy/pasted to
values.yaml
in your Helm chart - You only need very basic knowledge of Helm to change your configuration: the location of the
values.yaml
file - No accidental complexity or indirection/glue code (see the 'How not to...' above)
- The Helm templates don't contain part(s) of your Spring Boot configuration, so the domains are nicely separated
- Changes to your Spring Boot config in
values.yaml
will replace your pods automatically when applied to your cluster, no need to restart anything to activate your new config
And to summarize: I've promised you "one chart". All the magic is in these lines:
env:
- name: SPRING_APPLICATION_JSON
value: {{ .Values.springConfig | toJson | quote }}
Just save this snippet, copy/paste it to make any Helm chart work with any Spring Boot app you write!