Keep application configuration decoupled from Helm chart values
If you use Helm chart to manage your applications on Kubernetes, don’t embed parameters from your application configuration in the values of your Helm chart. Doing so means you’ll have to update the values.yaml schema every time you add or modify a field in your application configuration.
Instead, follow this approach:
- Define sensible defaults in your application configuration, and allow overrides via the configuration file
This is the most important step. A set of sensible defaults that suit most users in the beginning is necessary. Users won’t have to tweak too many settings just to have an initial installation of the Helm chart, thus having a better user experience.
For example, if you have the following configuration in a Golang application:
type Config struct {
Port int `yaml:"port"`
LogLevel string `yaml:"logLevel"`
} Create a variable with the default configuration:
var DefaultAppConfig = AppConfig{
Port: 8080,
LogLevel: "INFO",
} When initializing the application, set the application configuration to the default variable. Afterwards, read the configuration file, check which fields of the configuration struct are set in the configuration file and overwrite the default values set previously in the application configuration. This can be easily done by using a library such as Viper.
- Create a single field in the values file. Let’s call it
appConfig. It must be empty by default:
# Default values for ..
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nginx
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ''
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
# Application configuration. It should be empty by default.
appConfig: {} - Use this field to fill a ConfigMap in the Helm chart templates:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "app.configMapName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "app.labels" . | nindent 4 }}
{{- with .Values.configAnnotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
data:
config.yaml: |
{{- toYaml .Values.appConfig | nindent 4 }} - Mount the ConfigMap in your application.
apiVersion: apps/v1
kind: Deployment
metadata:
name: { { include "app.fullname" . } }
labels: { { - include "app.labels" . | nindent 4 } }
spec:
# [...]
template:
metadata:
annotations:
# Add the checksum so the deployment can pick up updates to the ConfigMap.
checksum/config: { { include (print $.Template.BasePath "/configmap.yaml") . | sha256sum } }
# [...]
spec:
# Create a volume containing the file.
volumes:
- name: app-config
configMap:
name: { { include "app.configMapName" . } }
optional: false
containers:
- name: { { .Chart.Name } }
securityContext: { { - toYaml .Values.securityContext | nindent 12 } }
image: '{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}'
imagePullPolicy: { { .Values.image.pullPolicy } }
command: ['/app', '--config', '/etc/app/config.yaml']
ports:
- name: http
containerPort: { { .Values.service.port } }
protocol: TCP
# Mount the volume containing the file in the application container.
volumeMounts:
- name: app-config
mountPath: /etc/app/config
readOnly: true
# [...] Make sure to include the checksum annotation. On every ConfigMap change, the checksum is computed and the annotation will change. The change will trigger a restart of the application which will pick up the new version of the ConfigMap. If using the helm create command to initialize the Helm chart, a ConfigMap will be created that will include this annotation.
By following this recipe, a user can easily start with the default configuration of the application and set fields in the appConfig field of the values of the Helm chart whenever they feel the need. Whenever you add a new configuration parameter to your app which is optional or has a default value, it will be supported as is for the Helm chart too, you just have to increase the application version in the Helm chart. And you don’t bloat the values yaml file.