Files
HelmChartSammlung/charts/keycloak/templates/statefulset.yaml
Marko Oldenburg c084706fc8 Add initial Keycloak Helm chart with comprehensive configuration
This commit introduces a complete Helm chart for deploying Keycloak on
Kubernetes. The chart includes a variety of configurations such as
service and ingress definitions, metrics exposure, resource limits, and
autoscaling options.

Key features include:
- Full support for PostgreSQL as a database, configurable through chart
  values.
- Ingress resources for external access, including support for TLS and
  admin interfaces.
- Options to use custom configurations and initialization scripts via
  ConfigMaps.
- Metrics service for Prometheus integration, alongside ServiceMonitor
  configurations for Kubernetes monitoring.
- Enhanced environment variables management, including secret handling
  for sensitive data like passwords.

These changes provide a robust foundation for deploying Keycloak in
both development and production environments. Users should be aware
that this initial setup gives flexibility for customization, but care
should be taken when altering default configurations to ensure
compatibility with existing deployments.
2025-08-10 11:04:12 +02:00

450 lines
23 KiB
YAML

{{- /*
Copyright Broadcom, Inc. All Rights Reserved.
SPDX-License-Identifier: APACHE-2.0
*/}}
apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }}
kind: StatefulSet
metadata:
name: {{ template "common.names.fullname" . }}
namespace: {{ include "common.names.namespace" . | quote }}
labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }}
app.kubernetes.io/component: keycloak
{{- if or .Values.statefulsetAnnotations .Values.commonAnnotations }}
annotations: {{- include "common.tplvalues.merge" ( dict "values" ( list .Values.statefulsetAnnotations .Values.commonAnnotations ) "context" $ ) | nindent 4 }}
{{- end }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimitCount }}
podManagementPolicy: {{ .Values.podManagementPolicy }}
serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }}
updateStrategy:
{{- include "common.tplvalues.render" (dict "value" .Values.updateStrategy "context" $ ) | nindent 4 }}
{{- if .Values.minReadySeconds }}
minReadySeconds: {{ .Values.minReadySeconds }}
{{- end }}
{{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }}
selector:
matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }}
app.kubernetes.io/component: keycloak
template:
metadata:
annotations:
checksum/configmap-env-vars: {{ include (print $.Template.BasePath "/configmap-env-vars.yaml") . | sha256sum }}
{{- if not .Values.auth.existingSecret }}
checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
{{- end }}
{{- if (include "keycloak.createConfigmap" .) }}
checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.podAnnotations }}
{{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }}
{{- end }}
labels: {{- include "common.labels.standard" ( dict "customLabels" $podLabels "context" $ ) | nindent 8 }}
app.kubernetes.io/component: keycloak
app.kubernetes.io/app-version: {{ .Chart.AppVersion }}
spec:
serviceAccountName: {{ template "keycloak.serviceAccountName" . }}
{{- include "keycloak.imagePullSecrets" . | nindent 6 }}
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
{{- if .Values.hostAliases }}
hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }}
{{- end }}
{{- if .Values.affinity }}
affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }}
{{- else }}
affinity:
podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "customLabels" $podLabels "context" $) | nindent 10 }}
podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "customLabels" $podLabels "context" $) | nindent 10 }}
nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }}
{{- end }}
{{- if .Values.nodeSelector }}
nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }}
{{- end }}
{{- if .Values.tolerations }}
tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" .) | nindent 8 }}
{{- end }}
{{- if .Values.topologySpreadConstraints }}
topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" .) | nindent 8 }}
{{- end }}
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName | quote }}
{{- end }}
{{- if .Values.schedulerName }}
schedulerName: {{ .Values.schedulerName }}
{{- end }}
{{- if .Values.podSecurityContext.enabled }}
securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.podSecurityContext "context" $) | nindent 8 }}
{{- end }}
{{- if .Values.dnsPolicy }}
dnsPolicy: {{ .Values.dnsPolicy }}
{{- end }}
{{- if .Values.dnsConfig }}
dnsConfig: {{- include "common.tplvalues.render" (dict "value" .Values.dnsConfig "context" .) | nindent 8 }}
{{- end }}
enableServiceLinks: {{ .Values.enableServiceLinks }}
{{- if .Values.terminationGracePeriodSeconds }}
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
{{- end }}
{{- if or .Values.enableDefaultInitContainers .Values.initContainers }}
initContainers:
{{- if .Values.enableDefaultInitContainers }}
- name: prepare-write-dirs
image: {{ template "keycloak.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/bash
args:
- -ec
- |
. /opt/bitnami/scripts/liblog.sh
info "Copying writable dirs to empty dir"
# In order to not break the application functionality we need to make some
# directories writable, so we need to copy it to an empty dir volume
cp -r --preserve=mode,timestamps /opt/bitnami/keycloak/lib/quarkus /emptydir/app-quarkus-dir
cp -r --preserve=mode,timestamps /opt/bitnami/keycloak/data /emptydir/app-data-dir
cp -r --preserve=mode,timestamps /opt/bitnami/keycloak/providers /emptydir/app-providers-dir
cp -r --preserve=mode,timestamps /opt/bitnami/keycloak/themes /emptydir/app-themes-dir
info "Copy operation completed"
{{- if .Values.containerSecurityContext.enabled }}
securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.containerSecurityContext "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.resources }}
resources: {{- toYaml .Values.resources | nindent 12 }}
{{- else if ne .Values.resourcesPreset "none" }}
resources: {{- include "common.resources.preset" (dict "type" .Values.resourcesPreset) | nindent 12 }}
{{- end }}
volumeMounts:
- name: empty-dir
mountPath: /emptydir
{{- end }}
{{- if .Values.initContainers }}
{{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }}
{{- end }}
{{- end }}
containers:
- name: keycloak
image: {{ template "keycloak.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.lifecycleHooks }}
lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.containerSecurityContext.enabled }}
securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.containerSecurityContext "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.diagnosticMode.enabled }}
command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }}
{{- else if .Values.command }}
command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.diagnosticMode.enabled }}
args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }}
{{- else if .Values.args }}
args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }}
{{- end }}
env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: BITNAMI_DEBUG
value: {{ ternary "true" "false" .Values.image.debug | quote }}
{{- if .Values.usePasswordFiles }}
- name: KC_BOOTSTRAP_ADMIN_PASSWORD_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/%s" (include "keycloak.secretKey" .) }}
- name: KEYCLOAK_DATABASE_PASSWORD_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/db-%s" (include "keycloak.databaseSecretPasswordKey" .) }}
{{- if .Values.externalDatabase.existingSecretHostKey }}
- name: KEYCLOAK_DATABASE_HOST_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/db-%s" (include "keycloak.databaseSecretHostKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretPortKey }}
- name: KEYCLOAK_DATABASE_PORT_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/db-%s" (include "keycloak.databaseSecretPortKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretUserKey }}
- name: KEYCLOAK_DATABASE_USER_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/db-%s" (include "keycloak.databaseSecretUserKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretDatabaseKey }}
- name: KEYCLOAK_DATABASE_NAME_FILE
value: {{ printf "/opt/bitnami/keycloak/secrets/db-%s" (include "keycloak.databaseSecretDatabaseKey" .) }}
{{- end }}
{{- if and .Values.tls.enabled (or .Values.tls.keystorePassword .Values.tls.passwordsSecret) }}
- name: KEYCLOAK_HTTPS_KEY_STORE_PASSWORD_FILE
value: "/opt/bitnami/keycloak/secrets/tls-keystore-password"
{{- end }}
{{- if and .Values.tls.enabled (or .Values.tls.truststorePassword .Values.tls.passwordsSecret) }}
- name: KEYCLOAK_HTTPS_TRUST_STORE_PASSWORD_FILE
value: "/opt/bitnami/keycloak/secrets/tls-truststore-password"
{{- end }}
{{- if and .Values.spi.existingSecret (or .Values.spi.truststorePassword .Values.spi.passwordsSecret) }}
- name: KEYCLOAK_SPI_TRUSTSTORE_PASSWORD_FILE
value: "/opt/bitnami/keycloak/secrets/spi-truststore-password"
{{- end }}
{{- else }}
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.secretName" . }}
key: {{ include "keycloak.secretKey" . }}
- name: KEYCLOAK_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.databaseSecretName" . }}
key: {{ include "keycloak.databaseSecretPasswordKey" . }}
{{- if .Values.externalDatabase.existingSecretHostKey }}
- name: KEYCLOAK_DATABASE_HOST
valueFrom:
secretKeyRef:
name: {{ include "keycloak.databaseSecretName" . }}
key: {{ include "keycloak.databaseSecretHostKey" . }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretPortKey }}
- name: KEYCLOAK_DATABASE_PORT
valueFrom:
secretKeyRef:
name: {{ include "keycloak.databaseSecretName" . }}
key: {{ include "keycloak.databaseSecretPortKey" . }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretUserKey }}
- name: KEYCLOAK_DATABASE_USER
valueFrom:
secretKeyRef:
name: {{ include "keycloak.databaseSecretName" . }}
key: {{ include "keycloak.databaseSecretUserKey" . }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretDatabaseKey }}
- name: KEYCLOAK_DATABASE_NAME
valueFrom:
secretKeyRef:
name: {{ include "keycloak.databaseSecretName" . }}
key: {{ include "keycloak.databaseSecretDatabaseKey" . }}
{{- end }}
{{- if and .Values.tls.enabled (or .Values.tls.keystorePassword .Values.tls.passwordsSecret) }}
- name: KEYCLOAK_HTTPS_KEY_STORE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.tlsPasswordsSecretName" . }}
key: "tls-keystore-password"
{{- end }}
{{- if and .Values.tls.enabled (or .Values.tls.truststorePassword .Values.tls.passwordsSecret) }}
- name: KEYCLOAK_HTTPS_TRUST_STORE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.tlsPasswordsSecretName" . }}
key: "tls-truststore-password"
{{- end }}
{{- if and .Values.spi.existingSecret (or .Values.spi.truststorePassword .Values.spi.passwordsSecret) }}
- name: KEYCLOAK_SPI_TRUSTSTORE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.spiPasswordsSecretName" . }}
key: "spi-truststore-password"
{{- end }}
{{- end }}
- name: KEYCLOAK_HTTP_RELATIVE_PATH
value: {{ .Values.httpRelativePath | quote }}
{{- if .Values.extraStartupArgs }}
- name: KEYCLOAK_EXTRA_ARGS
value: {{ .Values.extraStartupArgs | quote }}
{{- end }}
{{- if .Values.adminRealm }}
- name: KC_SPI_ADMIN_REALM
value: "{{ .Values.adminRealm }}"
{{- end }}
{{- if .Values.extraEnvVars }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }}
{{- end }}
envFrom:
- configMapRef:
name: {{ printf "%s-env-vars" (include "common.names.fullname" .) }}
{{- if .Values.extraEnvVarsCM }}
- configMapRef:
name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }}
{{- end }}
{{- if .Values.extraEnvVarsSecret }}
- secretRef:
name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }}
{{- end }}
{{- if .Values.resources }}
resources: {{- toYaml .Values.resources | nindent 12 }}
{{- else if ne .Values.resourcesPreset "none" }}
resources: {{- include "common.resources.preset" (dict "type" .Values.resourcesPreset) | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.containerPorts.http }}
protocol: TCP
{{- if .Values.tls.enabled }}
- name: https
containerPort: {{ .Values.containerPorts.https }}
protocol: TCP
{{- end }}
{{- if and (.Values.metrics.enabled) (not (eq (.Values.containerPorts.http | int) (.Values.containerPorts.metrics | int) )) }}
- name: metrics
containerPort: {{ .Values.containerPorts.metrics }}
protocol: TCP
{{- end}}
{{- /* Constant in code: https://github.com/keycloak/keycloak/blob/ce8e925c1ad9bf7a3180d1496e181aeea0ab5f8a/operator/src/main/java/org/keycloak/operator/Constants.java#L60 */}}
- name: discovery
containerPort: 7800
{{- if .Values.extraContainerPorts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraContainerPorts "context" $) | nindent 12 }}
{{- end }}
{{- if not .Values.diagnosticMode.enabled }}
{{- if .Values.customStartupProbe }}
startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }}
{{- else if .Values.startupProbe.enabled }}
startupProbe: {{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }}
httpGet:
path: {{ .Values.httpRelativePath }}
port: http
{{- end }}
{{- if .Values.customLivenessProbe }}
livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }}
{{- else if .Values.livenessProbe.enabled }}
livenessProbe: {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }}
tcpSocket:
port: http
{{- end }}
{{- if .Values.customReadinessProbe }}
readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }}
{{- else if .Values.readinessProbe.enabled }}
readinessProbe: {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }}
httpGet:
path: {{ .Values.httpRelativePath }}realms/{{ .Values.adminRealm | default "master" }}
port: http
{{- end }}
{{- end }}
volumeMounts:
- name: empty-dir
mountPath: /tmp
subPath: tmp-dir
- name: empty-dir
mountPath: /bitnami/keycloak
subPath: app-volume-dir
- name: empty-dir
mountPath: /opt/bitnami/keycloak/conf
subPath: app-conf-dir
- name: empty-dir
mountPath: /opt/bitnami/keycloak/lib/quarkus
subPath: app-quarkus-dir
- name: empty-dir
mountPath: /opt/bitnami/keycloak/data
subPath: app-data-dir
- name: empty-dir
mountPath: /opt/bitnami/keycloak/providers
subPath: app-providers-dir
- name: empty-dir
mountPath: /opt/bitnami/keycloak/themes
subPath: app-themes-dir
{{- if .Values.usePasswordFiles }}
- name: keycloak-secrets
mountPath: /opt/bitnami/keycloak/secrets
{{- end }}
{{- if or .Values.configuration .Values.existingConfigmap }}
- name: keycloak-config
mountPath: /bitnami/keycloak/conf/keycloak.conf
subPath: keycloak.conf
{{- end }}
{{- if .Values.tls.enabled }}
- name: certificates
mountPath: /opt/bitnami/keycloak/certs
readOnly: true
{{- end }}
{{- if .Values.customCaExistingSecret }}
- name: custom-ca
mountPath: /opt/bitnami/keycloak/custom-ca
readOnly: true
{{- end }}
{{- if .Values.spi.existingSecret }}
- name: spi-certificates
mountPath: /opt/bitnami/keycloak/spi-certs
readOnly: true
{{- end }}
{{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }}
- name: custom-init-scripts
mountPath: /docker-entrypoint-initdb.d
{{- end }}
{{- if .Values.extraVolumeMounts }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.sidecars }}
{{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }}
{{- end }}
volumes:
- name: empty-dir
emptyDir: {}
{{- if .Values.usePasswordFiles }}
- name: keycloak-secrets
projected:
sources:
- secret:
name: {{ include "keycloak.secretName" . }}
- secret:
name: {{ include "keycloak.databaseSecretName" . }}
items:
- key: {{ include "keycloak.databaseSecretPasswordKey" . }}
path: {{ printf "db-%s" (include "keycloak.databaseSecretPasswordKey" .) }}
{{- if .Values.externalDatabase.existingSecretHostKey }}
- key: {{ include "keycloak.databaseSecretHostKey" . }}
path: {{ printf "db-%s" (include "keycloak.databaseSecretHostKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretPortKey }}
- key: {{ include "keycloak.databaseSecretPortKey" . }}
path: {{ printf "db-%s" (include "keycloak.databaseSecretPortKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretUserKey }}
- key: {{ include "keycloak.databaseSecretUserKey" . }}
path: {{ printf "db-%s" (include "keycloak.databaseSecretUserKey" .) }}
{{- end }}
{{- if .Values.externalDatabase.existingSecretDatabaseKey }}
- key: {{ include "keycloak.databaseSecretDatabaseKey" . }}
path: {{ printf "db-%s" (include "keycloak.databaseSecretDatabaseKey" .) }}
{{- end }}
{{- if and .Values.tls.enabled (or .Values.tls.keystorePassword .Values.tls.truststorePassword .Values.tls.passwordsSecret) }}
- secret:
name: {{ include "keycloak.tlsPasswordsSecretName" . }}
{{- end }}
{{- if and .Values.spi.existingSecret (or .Values.spi.truststorePassword .Values.spi.passwordsSecret) }}
- secret:
name: {{ include "keycloak.spiPasswordsSecretName" . }}
{{- end }}
{{- end }}
{{- if or .Values.configuration .Values.existingConfigmap }}
- name: keycloak-config
configMap:
name: {{ include "keycloak.configmapName" . }}
{{- end }}
{{- if .Values.tls.enabled }}
- name: certificates
secret:
secretName: {{ include "keycloak.tlsSecretName" . }}
defaultMode: 420
{{- end }}
{{- if .Values.customCaExistingSecret }}
- name: custom-ca
secret:
secretName: {{ .Values.customCaExistingSecret }}
defaultMode: 420
{{- end }}
{{- if .Values.spi.existingSecret }}
- name: spi-certificates
secret:
secretName: {{ .Values.spi.existingSecret }}
defaultMode: 420
{{- end }}
{{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }}
- name: custom-init-scripts
configMap:
name: {{ include "keycloak.initdbScriptsCM" . }}
{{- end }}
{{- if .Values.extraVolumes }}
{{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }}
{{- end }}