From 69b8487010b45594cf9bf95aedbdb42208032040 Mon Sep 17 00:00:00 2001 From: "s.stoyanov" Date: Mon, 5 Jan 2026 09:59:24 +0200 Subject: [PATCH 1/5] feat: add advanced infinispan session replication, thread working and database credential management Signed-off-by: s.stoyanov --- .../ha-multi-az/ha-simple-values.yaml | 84 +++++++++ charts/keycloakx/templates/_helpers.tpl | 53 ++++++ charts/keycloakx/templates/statefulset.yaml | 88 ++++++++- charts/keycloakx/values.yaml | 176 +++++++++--------- 4 files changed, 309 insertions(+), 92 deletions(-) create mode 100644 charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml diff --git a/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml b/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml new file mode 100644 index 000000000..99d73d3a5 --- /dev/null +++ b/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml @@ -0,0 +1,84 @@ +replicas: 3 +database: + vendor: postgres + existingSecret: "keycloak-db-credentials" + existingSecretKeys: + username: "username" + password: "password" + hostname: "host" + port: "port" + database: "database" + vendor: "vendor" +cache: + stack: jdbc-ping +infinispan: + owners: + sessions: 2 + authenticationSessions: 2 + userSessions: 2 + offlineSessions: 2 +loadShedding: + enabled: true + httpMaxQueuedRequests: 1000 + httpPoolMaxThreads: 50 +threads: + autoCalculate: true + autoCalculateBase: 200 +resources: + requests: + cpu: "500m" + memory: "1Gi" + limits: + cpu: "1000m" + memory: "2Gi" +service: + type: ClusterIP + httpPort: 80 + httpsPort: 8443 +ingress: + enabled: true + ingressClassName: "nginx" + annotations: + nginx.ingress.kubernetes.io/affinity: "cookie" + nginx.ingress.kubernetes.io/session-cookie-name: "keycloak-affinity" + nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + rules: + - host: keycloak.example.com + paths: + - path: /auth + pathType: Prefix + tls: + - hosts: + - keycloak.example.com + secretName: keycloak-tls +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 6 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 +podDisruptionBudget: + minAvailable: 2 +serviceMonitor: + enabled: true +dbchecker: + enabled: trues +affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + {{- include "keycloak.selectorLabels" . | nindent 12 }} + topologyKey: kubernetes.io/hostname + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + {{- include "keycloak.selectorLabels" . | nindent 14 }} + topologyKey: topology.kubernetes.io/zone diff --git a/charts/keycloakx/templates/_helpers.tpl b/charts/keycloakx/templates/_helpers.tpl index 18d7ca6dc..25fe23fb8 100644 --- a/charts/keycloakx/templates/_helpers.tpl +++ b/charts/keycloakx/templates/_helpers.tpl @@ -65,6 +65,9 @@ Create the service DNS name. {{ include "keycloak.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{- end }} +{{/* +Database password environment variable handling +*/}} {{- define "keycloak.databasePasswordEnv" -}} {{- if or .Values.database.password .Values.database.existingSecret -}} - name: KC_DB_PASSWORD @@ -74,3 +77,53 @@ Create the service DNS name. key: {{ .Values.database.existingSecretKey | default "password" }} {{- end }} {{- end -}} + +{{/* +Database credential environment variable handling from existing secret +*/}} +{{- define "keycloak.databaseCredentialEnv" -}} +{{- if .Values.database.existingSecret -}} + {{- if .Values.database.existingSecretKeys.username }} +- name: KC_DB_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.username }} + {{- end }} + {{- if .Values.database.existingSecretKeys.password }} +- name: KC_DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.password }} + {{- end }} + {{- if .Values.database.existingSecretKeys.hostname }} +- name: KC_DB_URL_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.hostname }} + {{- end }} + {{- if .Values.database.existingSecretKeys.port }} +- name: KC_DB_URL_PORT + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.port }} + {{- end }} + {{- if .Values.database.existingSecretKeys.database }} +- name: KC_DB_URL_DATABASE + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.database }} + {{- end }} + {{- if .Values.database.existingSecretKeys.vendor }} +- name: KC_DB + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.vendor }} + {{- end }} +{{- end -}} +{{- end -}} diff --git a/charts/keycloakx/templates/statefulset.yaml b/charts/keycloakx/templates/statefulset.yaml index 07b96238f..10b82cff3 100644 --- a/charts/keycloakx/templates/statefulset.yaml +++ b/charts/keycloakx/templates/statefulset.yaml @@ -94,6 +94,11 @@ spec: value: {{ tpl .Values.http.relativePath $ | trimSuffix "/" }} {{- end }} {{- if eq .Values.cache.stack "default" }} + - name: KC_CACHE + value: "ispn" + - name: KC_CACHE_STACK + value: "jdbc-ping" + {{- else if eq .Values.cache.stack "jdbc-ping" }} - name: KC_CACHE value: "ispn" - name: KC_CACHE_STACK @@ -111,25 +116,38 @@ spec: - name: KC_DB value: {{ .Values.database.vendor }} {{- end }} - {{- if .Values.database.hostname }} + {{- if not .Values.database.existingSecret }} + {{- if .Values.database.hostname }} - name: KC_DB_URL_HOST value: {{ .Values.database.hostname }} - {{- end }} - {{- if .Values.database.port }} + {{- end }} + {{- if .Values.database.port }} - name: KC_DB_URL_PORT value: {{ .Values.database.port | quote }} - {{- end }} - {{- if .Values.database.database }} + {{- end }} + {{- if .Values.database.database }} - name: KC_DB_URL_DATABASE value: {{ .Values.database.database }} + {{- end }} + {{- if .Values.database.username }} + - name: KC_DB_USERNAME + value: {{ .Values.database.username }} + {{- end }} {{- end }} - {{- if .Values.database.username }} + {{- if .Values.database.existingSecret }} + {{- if not .Values.database.existingSecretKeys.username }} + {{- if .Values.database.username }} - name: KC_DB_USERNAME value: {{ .Values.database.username }} + {{- end }} + {{- end }} + {{- include "keycloak.databaseCredentialEnv" . | nindent 12 }} {{- end }} {{- if or .Values.database.password .Values.database.existingSecret -}} + {{- if not .Values.database.existingSecretKeys.password }} {{- include "keycloak.databasePasswordEnv" . | nindent 12 }} {{- end }} + {{- end }} {{- if .Values.metrics.enabled }} - name: KC_METRICS_ENABLED value: "true" @@ -138,6 +156,64 @@ spec: - name: KC_HEALTH_ENABLED value: "true" {{- end }} + {{- if .Values.infinispan.owners.sessions }} + - name: KC_CACHE__OWNERS_SESSIONS + value: {{ .Values.infinispan.owners.sessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.authenticationSessions }} + - name: KC_CACHE__OWNERS_AUTHENTICATION_SESSIONS + value: {{ .Values.infinispan.owners.authenticationSessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.userSessions }} + - name: KC_CACHE__OWNERS_USER_SESSIONS + value: {{ .Values.infinispan.owners.userSessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.offlineSessions }} + - name: KC_CACHE__OWNERS_OFFLINE_SESSIONS + value: {{ .Values.infinispan.owners.offlineSessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.clientSessions }} + - name: KC_CACHE__OWNERS_CLIENT_SESSIONS + value: {{ .Values.infinispan.owners.clientSessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.offlineClientSessions }} + - name: KC_CACHE__OWNERS_OFFLINE_CLIENT_SESSIONS + value: {{ .Values.infinispan.owners.offlineClientSessions | quote }} + {{- end }} + {{- if .Values.infinispan.owners.loginFailures }} + - name: KC_CACHE__OWNERS_LOGIN_FAILURES + value: {{ .Values.infinispan.owners.loginFailures | quote }} + {{- end }} + {{- if .Values.loadShedding.enabled }} + {{- if .Values.loadShedding.httpMaxQueuedRequests }} + - name: KC_HTTP_MAX_QUEUED_REQUESTS + value: {{ .Values.loadShedding.httpMaxQueuedRequests | quote }} + {{- end }} + {{- if .Values.loadShedding.httpPoolMaxThreads }} + - name: KC_HTTP_POOL_MAX_THREADS + value: {{ .Values.loadShedding.httpPoolMaxThreads | quote }} + {{- end }} + {{- end }} + {{- if .Values.threads.autoCalculate }} + {{- $replicas := .Values.replicas }} + {{- if .Values.autoscaling.enabled }} + {{- $replicas = .Values.autoscaling.minReplicas }} + {{- end }} + {{- $calculatedThreads := div .Values.threads.autoCalculateBase $replicas }} + - name: KC_HTTP_POOL_MAX_THREADS + value: {{ $calculatedThreads | quote }} + {{- else if .Values.threads.http.poolMaxThreads }} + - name: KC_HTTP_POOL_MAX_THREADS + value: {{ .Values.threads.http.poolMaxThreads | quote }} + {{- end }} + {{- if .Values.threads.http.queuedRequests }} + - name: KC_HTTP_MAX_QUEUED_REQUESTS + value: {{ .Values.threads.http.queuedRequests | quote }} + {{- end }} + {{- if .Values.threads.jgroups.maxThreads }} + - name: KC_THREADS_MAX + value: {{ .Values.threads.jgroups.maxThreads | quote }} + {{- end }} {{- with .Values.extraEnv }} {{- tpl . $ | nindent 12 }} {{- end }} diff --git a/charts/keycloakx/values.yaml b/charts/keycloakx/values.yaml index 709a7960c..a82bae017 100644 --- a/charts/keycloakx/values.yaml +++ b/charts/keycloakx/values.yaml @@ -1,12 +1,9 @@ # Optionally override the fully qualified name fullnameOverride: "" - # Optionally override the name nameOverride: "" - # The number of replicas to create (has no effect if autoscaling enabled) replicas: 1 - image: # The Keycloak image repository repository: quay.io/keycloak/keycloak @@ -16,7 +13,6 @@ image: digest: "" # The Keycloak image pull policy pullPolicy: IfNotPresent - # Image pull secrets for the Pod imagePullSecrets: [] # - name: myRegistrKeySecretName @@ -29,16 +25,12 @@ hostAliases: [] # Indicates whether information about services should be injected into Pod's environment variables, matching the syntax of Docker links enableServiceLinks: true - # Pod management policy. One of `Parallel` or `OrderedReady` podManagementPolicy: OrderedReady - # StatefulSet's update strategy updateStrategy: RollingUpdate - # Pod restart policy. One of `Always`, `OnFailure`, or `Never` restartPolicy: Always - serviceAccount: # Specifies whether a ServiceAccount should be created create: true @@ -55,7 +47,6 @@ serviceAccount: imagePullSecrets: [] # Automount API credentials for the Service Account automountServiceAccountToken: true - rbac: create: false rules: [] @@ -67,30 +58,24 @@ rbac: # verbs: # - get # - list - # SecurityContext for the entire Pod. Every container running in the Pod will inherit this SecurityContext. This might be relevant when other components of the environment inject additional containers into running Pods (service meshes are the most prominent example for this) podSecurityContext: fsGroup: 1000 - # SecurityContext for the Keycloak container securityContext: runAsUser: 1000 runAsNonRoot: true - # Additional init containers, e. g. for providing custom themes extraInitContainers: "" - # When using service meshes which rely on a sidecar, it may be necessary to skip init containers altogether, # since the sidecar doesn't start until the init containers are done, and the sidecar may be required # for network access. # For example, Istio in strict mTLS mode prevents the dbchecker init container from ever completing skipInitContainers: false - # Additional sidecar containers, e. g. for a database proxy, such as Google's cloudsql-proxy extraContainers: "" - # Lifecycle hooks for the Keycloak container -lifecycleHooks: | +lifecycleHooks: "" # postStart: # exec: # command: @@ -100,27 +85,45 @@ lifecycleHooks: | # Termination grace period in seconds for Keycloak shutdown. Clusters with a large cache might need to extend this to give Infinispan more time to rebalance terminationGracePeriodSeconds: 60 - # The internal Kubernetes cluster domain clusterDomain: cluster.local - ## Overrides the default entrypoint of the Keycloak container command: [] - ## Overrides the default args for the Keycloak container args: [] - +# Load shedding configuration for handling high load +loadShedding: + # Enable load shedding (default: false) + enabled: false + # Maximum number of queued HTTP requests (default: 1000) + httpMaxQueuedRequests: 1000 + # Maximum number of threads in HTTP pool (default: 50) + httpPoolMaxThreads: 50 +# Thread management configuration +threads: + # Enable automatic thread calculation based on replica count (default: false) + autoCalculate: false + # Base number for auto-calculation (default: 200) + autoCalculateBase: 200 + # HTTP thread pool configuration + http: + # Maximum number of threads in HTTP pool + poolMaxThreads: 50 + # Maximum number of queued requests for load shedding + queuedRequests: 1000 + # JGroups thread pool configuration + jgroups: + # Maximum number of threads for JGroups operations + maxThreads: 200 # Additional environment variables for Keycloak extraEnv: "" - # - name: KC_LOG_LEVEL - # value: DEBUG +# - name: KC_LOG_LEVEL +# value: DEBUG # Additional environment variables for Keycloak mapped from Secret or ConfigMap extraEnvFrom: "" - # Pod priority class name priorityClassName: "" - # Pod affinity affinity: | podAntiAffinity: @@ -146,22 +149,16 @@ affinity: | values: - test topologyKey: topology.kubernetes.io/zone - # Topology spread constraints template topologySpreadConstraints: - # Node labels for Pod assignment nodeSelector: {} - # Node taints to tolerate tolerations: [] - # Additional Pod labels podLabels: {} - # Additional Pod annotations podAnnotations: {} - # Liveness probe configuration livenessProbe: | httpGet: @@ -170,7 +167,6 @@ livenessProbe: | scheme: '{{ .Values.http.internalScheme }}' initialDelaySeconds: 0 timeoutSeconds: 5 - # Readiness probe configuration readinessProbe: | httpGet: @@ -179,7 +175,6 @@ readinessProbe: | scheme: '{{ .Values.http.internalScheme }}' initialDelaySeconds: 10 timeoutSeconds: 1 - # Startup probe configuration startupProbe: | httpGet: @@ -190,36 +185,32 @@ startupProbe: | timeoutSeconds: 1 failureThreshold: 60 periodSeconds: 5 - # Pod resource requests and limits resources: {} - # requests: - # cpu: "500m" - # memory: "1024Mi" - # limits: - # cpu: "500m" - # memory: "1024Mi" +# requests: +# cpu: "500m" +# memory: "1024Mi" +# limits: +# cpu: "500m" +# memory: "1024Mi" # Add additional volumes, e. g. for custom themes extraVolumes: "" - # Add volume claim templates to the StatefulSet, e. g. for dynamic provisioning volumeClaimTemplates: "" - # - metadata: - # name: themes - # spec: - # accessModes: [ "ReadWriteOncePod" ] - # storageClassName: "my-storage-class" - # resources: - # requests: - # storage: 1Gi +# - metadata: +# name: themes +# spec: +# accessModes: [ "ReadWriteOncePod" ] +# storageClassName: "my-storage-class" +# resources: +# requests: +# storage: 1Gi # Add additional volumes mounts, e. g. for custom themes extraVolumeMounts: "" - # Add additional ports, e. g. for admin console or exposing JGroups ports extraPorts: [] - # Pod disruption budget podDisruptionBudget: {} # maxUnavailable: 1 @@ -227,18 +218,16 @@ podDisruptionBudget: {} # Annotations for the StatefulSet statefulsetAnnotations: {} - # Additional labels for the StatefulSet statefulsetLabels: {} - # Configuration for secrets that should be created secrets: {} - # mysecret: - # type: {} - # annotations: {} - # labels: {} - # stringData: {} - # data: {} +# mysecret: +# type: {} +# annotations: {} +# labels: {} +# stringData: {} +# data: {} service: # Annotations for HTTP service @@ -273,13 +262,11 @@ service: sessionAffinity: "" # Session affinity config sessionAffinityConfig: {} - serviceHeadless: # Annotations for headless service annotations: {} # Add additional ports to the headless service, e. g. for admin console or exposing JGroups ports extraPorts: [] - ingress: # If `true`, an Ingress is created enabled: false @@ -289,16 +276,15 @@ ingress: servicePort: http # Ingress annotations annotations: {} - ## Resolve HTTP 502 error using ingress-nginx: - ## See https://www.ibm.com/support/pages/502-error-ingress-keycloak-response - # nginx.ingress.kubernetes.io/proxy-buffer-size: 128k + ## Resolve HTTP 502 error using ingress-nginx: + ## See https://www.ibm.com/support/pages/502-error-ingress-keycloak-response + # nginx.ingress.kubernetes.io/proxy-buffer-size: 128k # Additional Ingress labels labels: {} - # List of rules for the Ingress + # List of rules for the Ingress rules: - - - # Ingress host + - # Ingress host host: '{{ .Release.Name }}.keycloak.example.com' # Paths for the host paths: @@ -322,33 +308,27 @@ ingress: # Additional Ingress labels for console path only labels: {} rules: - - - # Ingress host + - # Ingress host host: '{{ .Release.Name }}.keycloak.example.com' # Paths for the host paths: - path: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/admin' pathType: Prefix - # Console TLS configuration tls: [] # - hosts: # - console.keycloak.example.com # secretName: "" - ## Network policy configuration # https://kubernetes.io/docs/concepts/services-networking/network-policies/ networkPolicy: # If true, the Network policies are deployed enabled: false - # Additional Network policy labels labels: {} - # Define all other external allowed source # See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#networkpolicypeer-v1-networking-k8s-io extraFrom: [] - # Define egress networkpolicies for the Keycloak pods (external database for example) # See https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#networkpolicyegressrule-v1-networking-k8s-io # egress: @@ -359,7 +339,6 @@ networkPolicy: # - protocol: TCP # port: 3306 egress: [] - route: # If `true`, an OpenShift Route is created enabled: false @@ -379,7 +358,6 @@ route: insecureEdgeTerminationPolicy: Redirect # TLS termination of the route. Can be `edge`, `passthrough`, or `reencrypt` termination: edge - dbchecker: enabled: false image: @@ -403,13 +381,21 @@ dbchecker: limits: cpu: "20m" memory: "32Mi" - database: # don't create secret for db password. Instead use existing k8s secret # existingSecret: "my-existent-dbpass-secret" # existingSecretKey: "password" existingSecret: "" existingSecretKey: "" + # Comprehensive database credentials from existing secret + # Maps secret keys to database configuration fields + existingSecretKeys: + username: "" + password: "" + hostname: "" + port: "" + database: "" + vendor: "" # E.g. dev-file, dev-mem, mariadb, mssql, mysql, oracle or postgres vendor: hostname: @@ -417,29 +403,51 @@ database: database: username: password: - cache: # Use "custom" to disable automatic cache configuration stack: default - + # Advanced cache stack configuration for jdbc-ping + # When stack is "jdbc-ping", JGroups configuration will be generated + jgroups: + # JGroups protocol stack configuration (optional) + # If not provided, default jdbc-ping configuration will be used + config: "" + # Additional JGroups properties + properties: {} +# Infinispan session replication configuration +infinispan: + # Number of owners for cache entries (replication factor) + owners: + # Number of owners for sessions cache (default: 2) + sessions: 2 + # Number of owners for authenticationSessions cache (default: 2) + authenticationSessions: 2 + # Number of owners for userSessions cache (default: 2) + userSessions: 2 + # Number of owners for offlineSessions cache (default: 2) + offlineSessions: 2 + # Number of owners for clientSessions cache (default: 2) + clientSessions: 2 + # Number of owners for offlineClientSessions cache (default: 2) + offlineClientSessions: 2 + # Number of owners for loginFailures cache (default: 2) + loginFailures: 2 + # Additional Infinispan configuration + additionalConfig: "" proxy: enabled: true mode: forwarded http: enabled: true - metrics: enabled: true - health: enabled: true - http: # For backwards compatibility reasons we set this to the value used by previous Keycloak versions. relativePath: "/auth" internalPort: http-internal internalScheme: HTTP - serviceMonitor: # If `true`, a ServiceMonitor resource for the prometheus-operator is created enabled: false @@ -463,7 +471,6 @@ serviceMonitor: path: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/metrics' # The Service port at which metrics are served port: '{{ .Values.http.internalPort }}' - extraServiceMonitor: # If `true`, a ServiceMonitor resource for the prometheus-operator is created enabled: false @@ -487,7 +494,6 @@ extraServiceMonitor: path: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/metrics' # The Service port at which metrics are served port: '{{ .Values.http.internalPort }}' - prometheusRule: # If `true`, a PrometheusRule resource for the prometheus-operator is created enabled: false @@ -519,7 +525,6 @@ prometheusRule: # for: 5m # labels: # severity: warning - autoscaling: # If `true`, an autoscaling/v2 HorizontalPodAutoscaler resource is created (requires Kubernetes 1.23 or above) # Autoscaling seems to be most reliable when using KUBE_PING service discovery (see README for details) @@ -547,7 +552,6 @@ autoscaling: - type: Pods value: 1 periodSeconds: 300 - test: # If `true`, test resources are created enabled: false From 5752222a115d662b897ead108b499bb3eb3ee302 Mon Sep 17 00:00:00 2001 From: "s.stoyanov" Date: Mon, 5 Jan 2026 11:25:20 +0200 Subject: [PATCH 2/5] feat: update nfinispan replication and load shedding Signed-off-by: s.stoyanov --- .../ha-multi-az/ha-simple-values.yaml | 39 ++++++------ .../ha-multi-az/keycloak-cluster.yaml | 13 ++++ charts/keycloakx/templates/_helpers.tpl | 26 ++++++++ charts/keycloakx/templates/statefulset.yaml | 61 ++++++++++++------- charts/keycloakx/values.yaml | 33 +++++----- 5 files changed, 112 insertions(+), 60 deletions(-) create mode 100644 charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml diff --git a/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml b/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml index 99d73d3a5..cc48c74ea 100644 --- a/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml +++ b/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml @@ -1,6 +1,14 @@ replicas: 3 +command: + - "/opt/keycloak/bin/kc.sh" + - "--verbose" + - "start" + - "--http-port=8080" + - "--hostname-strict=false" + - "--db=postgres" + - "--spi-events-listener-jboss-logging-success-level=info" + - "--spi-events-listener-jboss-logging-error-level=warn" database: - vendor: postgres existingSecret: "keycloak-db-credentials" existingSecretKeys: username: "username" @@ -20,10 +28,11 @@ infinispan: loadShedding: enabled: true httpMaxQueuedRequests: 1000 - httpPoolMaxThreads: 50 threads: - autoCalculate: true - autoCalculateBase: 200 + http: + poolMaxThreads: "" + jgroups: + maxThreads: "" resources: requests: cpu: "500m" @@ -31,28 +40,16 @@ resources: limits: cpu: "1000m" memory: "2Gi" -service: - type: ClusterIP - httpPort: 80 - httpsPort: 8443 ingress: enabled: true - ingressClassName: "nginx" - annotations: - nginx.ingress.kubernetes.io/affinity: "cookie" - nginx.ingress.kubernetes.io/session-cookie-name: "keycloak-affinity" - nginx.ingress.kubernetes.io/proxy-buffer-size: "128k" + ingressClassName: "" rules: - - host: keycloak.example.com + - host: auth.example.com paths: - path: /auth pathType: Prefix - tls: - - hosts: - - keycloak.example.com - secretName: keycloak-tls autoscaling: - enabled: true + enabled: false minReplicas: 3 maxReplicas: 6 metrics: @@ -65,9 +62,9 @@ autoscaling: podDisruptionBudget: minAvailable: 2 serviceMonitor: - enabled: true + enabled: false dbchecker: - enabled: trues + enabled: true affinity: | podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml b/charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml new file mode 100644 index 000000000..75f039574 --- /dev/null +++ b/charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml @@ -0,0 +1,13 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: keycloak-db + namespace: keycloak +spec: + instances: 3 + storage: + size: 5Gi + bootstrap: + initdb: + database: keycloak + owner: keycloak diff --git a/charts/keycloakx/templates/_helpers.tpl b/charts/keycloakx/templates/_helpers.tpl index 25fe23fb8..1a3632845 100644 --- a/charts/keycloakx/templates/_helpers.tpl +++ b/charts/keycloakx/templates/_helpers.tpl @@ -65,6 +65,32 @@ Create the service DNS name. {{ include "keycloak.fullname" . }}-headless.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }} {{- end }} +{{/* +Get database hostname from existingSecretKeys or direct value +*/}} +{{- define "keycloak.databaseHostname" -}} +{{- if .Values.database.existingSecretKeys.hostname }} +{{- .Values.database.existingSecretKeys.hostname }} +{{- else if .Values.database.hostname }} +{{- .Values.database.hostname }} +{{- else }} +{{- fail "database hostname is required" }} +{{- end }} +{{- end }} + +{{/* +Get database port from existingSecretKeys or direct value +*/}} +{{- define "keycloak.databasePort" -}} +{{- if .Values.database.existingSecretKeys.port }} +{{- .Values.database.existingSecretKeys.port }} +{{- else if .Values.database.port }} +{{- .Values.database.port }} +{{- else }} +{{- fail "database port is required" }} +{{- end }} +{{- end }} + {{/* Database password environment variable handling */}} diff --git a/charts/keycloakx/templates/statefulset.yaml b/charts/keycloakx/templates/statefulset.yaml index 10b82cff3..13618751e 100644 --- a/charts/keycloakx/templates/statefulset.yaml +++ b/charts/keycloakx/templates/statefulset.yaml @@ -48,13 +48,43 @@ spec: imagePullPolicy: {{ .Values.dbchecker.image.pullPolicy }} securityContext: {{- toYaml .Values.dbchecker.securityContext | nindent 12 }} + env: + {{- if .Values.database.existingSecret }} + # Read hostname from secret + {{- if .Values.database.existingSecretKeys.hostname }} + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.hostname }} + {{- else if .Values.database.hostname }} + - name: DB_HOST + value: {{ .Values.database.hostname | quote }} + {{- end }} + # Read port from secret + {{- if .Values.database.existingSecretKeys.port }} + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKeys.port }} + {{- else if .Values.database.port }} + - name: DB_PORT + value: {{ .Values.database.port | quote }} + {{- end }} + {{- else }} + - name: DB_HOST + value: {{ .Values.database.hostname | quote }} + - name: DB_PORT + value: {{ .Values.database.port | quote }} + {{- end }} command: - sh - -c - | echo 'Waiting for Database to become ready...' - until printf "." && nc -z -w 2 {{ required ".Values.database.hostname is required if dbchecker is enabled!" .Values.database.hostname }} {{ required ".Values.database.port is required if dbchecker is enabled!" .Values.database.port }}; do + until printf "." && nc -z -w 2 "$DB_HOST" "$DB_PORT"; do sleep 2; done; @@ -104,6 +134,11 @@ spec: - name: KC_CACHE_STACK value: "jdbc-ping" {{- end }} + {{- if and (eq .Values.cache.stack "jdbc-ping") .Values.cache.jgroups.config }} + - name: KC_CACHE_STACK_CONFIG + value: | +{{ tpl .Values.cache.jgroups.config . | indent 16 }} + {{- end }} {{- if .Values.proxy.enabled }} - name: KC_PROXY_HEADERS value: {{ .Values.proxy.mode }} @@ -112,7 +147,7 @@ spec: - name: KC_HTTP_ENABLED value: "true" {{- end }} - {{- if .Values.database.vendor }} + {{- if and .Values.database.vendor (not .Values.database.existingSecretKeys.vendor) }} - name: KC_DB value: {{ .Values.database.vendor }} {{- end }} @@ -189,28 +224,12 @@ spec: - name: KC_HTTP_MAX_QUEUED_REQUESTS value: {{ .Values.loadShedding.httpMaxQueuedRequests | quote }} {{- end }} - {{- if .Values.loadShedding.httpPoolMaxThreads }} - - name: KC_HTTP_POOL_MAX_THREADS - value: {{ .Values.loadShedding.httpPoolMaxThreads | quote }} - {{- end }} {{- end }} - {{- if .Values.threads.autoCalculate }} - {{- $replicas := .Values.replicas }} - {{- if .Values.autoscaling.enabled }} - {{- $replicas = .Values.autoscaling.minReplicas }} - {{- end }} - {{- $calculatedThreads := div .Values.threads.autoCalculateBase $replicas }} - - name: KC_HTTP_POOL_MAX_THREADS - value: {{ $calculatedThreads | quote }} - {{- else if .Values.threads.http.poolMaxThreads }} + {{- if and .Values.threads.http.poolMaxThreads (ne .Values.threads.http.poolMaxThreads "") }} - name: KC_HTTP_POOL_MAX_THREADS value: {{ .Values.threads.http.poolMaxThreads | quote }} {{- end }} - {{- if .Values.threads.http.queuedRequests }} - - name: KC_HTTP_MAX_QUEUED_REQUESTS - value: {{ .Values.threads.http.queuedRequests | quote }} - {{- end }} - {{- if .Values.threads.jgroups.maxThreads }} + {{- if and .Values.threads.jgroups.maxThreads (ne .Values.threads.jgroups.maxThreads "") }} - name: KC_THREADS_MAX value: {{ .Values.threads.jgroups.maxThreads | quote }} {{- end }} @@ -312,4 +331,4 @@ spec: {{- with .Values.volumeClaimTemplates }} volumeClaimTemplates: {{- tpl . $ | nindent 4 }} - {{- end }} + {{- end }} \ No newline at end of file diff --git a/charts/keycloakx/values.yaml b/charts/keycloakx/values.yaml index a82bae017..d36f7e6e7 100644 --- a/charts/keycloakx/values.yaml +++ b/charts/keycloakx/values.yaml @@ -91,30 +91,21 @@ clusterDomain: cluster.local command: [] ## Overrides the default args for the Keycloak container args: [] -# Load shedding configuration for handling high load +# Load shedding configuration for handling high load (recommended by Keycloak) loadShedding: - # Enable load shedding (default: false) + # Enable load shedding to protect against overload enabled: false - # Maximum number of queued HTTP requests (default: 1000) + # Maximum number of queued HTTP requests httpMaxQueuedRequests: 1000 - # Maximum number of threads in HTTP pool (default: 50) - httpPoolMaxThreads: 50 -# Thread management configuration +# Thread management - LET QUARKUS MANAGE AUTOMATICALLY (recommended) +# Quarkus automatically optimizes thread pools based on CPU cores and workload +# Override only if you have specific requirements threads: - # Enable automatic thread calculation based on replica count (default: false) - autoCalculate: false - # Base number for auto-calculation (default: 200) - autoCalculateBase: 200 - # HTTP thread pool configuration + # Override Quarkus defaults ONLY if necessary http: - # Maximum number of threads in HTTP pool - poolMaxThreads: 50 - # Maximum number of queued requests for load shedding - queuedRequests: 1000 - # JGroups thread pool configuration + poolMaxThreads: "" # Let Quarkus decide (usually CPU cores * 2) jgroups: - # Maximum number of threads for JGroups operations - maxThreads: 200 + maxThreads: "" # Let Quarkus optimize for clustering # Additional environment variables for Keycloak extraEnv: "" # - name: KC_LOG_LEVEL @@ -411,6 +402,12 @@ cache: jgroups: # JGroups protocol stack configuration (optional) # If not provided, default jdbc-ping configuration will be used + # Use template functions for dynamic database configuration: + # config: | + # + # + # + # config: "" # Additional JGroups properties properties: {} From 35d5980bf05124ea4e2111e9db749d15ea3772ec Mon Sep 17 00:00:00 2001 From: "s.stoyanov" Date: Mon, 5 Jan 2026 11:42:38 +0200 Subject: [PATCH 3/5] feat: update example Signed-off-by: s.stoyanov --- .../keycloak-cluster.yaml | 0 .../keycloak-server-values.yaml} | 0 .../examples/infinispan-jdbcping/readme.md | 37 +++++++++++++++++++ 3 files changed, 37 insertions(+) rename charts/keycloakx/examples/{ha-multi-az => infinispan-jdbcping}/keycloak-cluster.yaml (100%) rename charts/keycloakx/examples/{ha-multi-az/ha-simple-values.yaml => infinispan-jdbcping/keycloak-server-values.yaml} (100%) create mode 100644 charts/keycloakx/examples/infinispan-jdbcping/readme.md diff --git a/charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml b/charts/keycloakx/examples/infinispan-jdbcping/keycloak-cluster.yaml similarity index 100% rename from charts/keycloakx/examples/ha-multi-az/keycloak-cluster.yaml rename to charts/keycloakx/examples/infinispan-jdbcping/keycloak-cluster.yaml diff --git a/charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml b/charts/keycloakx/examples/infinispan-jdbcping/keycloak-server-values.yaml similarity index 100% rename from charts/keycloakx/examples/ha-multi-az/ha-simple-values.yaml rename to charts/keycloakx/examples/infinispan-jdbcping/keycloak-server-values.yaml diff --git a/charts/keycloakx/examples/infinispan-jdbcping/readme.md b/charts/keycloakx/examples/infinispan-jdbcping/readme.md new file mode 100644 index 000000000..bbabc1074 --- /dev/null +++ b/charts/keycloakx/examples/infinispan-jdbcping/readme.md @@ -0,0 +1,37 @@ +# Keycloak.X with JDBC Ping, Infinispan Replication and Load Shedding + +This example shows how to configure Keycloak.X to use a PostgreSQL database. + +# Setup + +## Deploy Kubernetes Cluster + +## Deploy a PostgreSQL database +``` +kubectl apply -f keycloak-cluster.yaml +kubectl wait -for=condition=Ready cluster/keycloak-database --timeout=300s +``` + +# Deploy Keycloak +``` +helm install keycloak . \ + --namespace keycloak --create-namespace \ + --values examples/infinispan-jdbc-ping/keycloak-server-values.yaml +``` + +# Access Keycloak +Once Keycloak is running, forward the HTTP service port to 8080. + +``` +kubectl port-forward service/keycloak-keycloakx-http 8080:80 +``` + +You can then access the Keycloak Admin-Console via `http://localhost:8080/auth` with +username: `admin` and password: `secret`. + +# Remove Keycloak + +``` +helm uninstall keycloak +kubectl delete -f keycloak-cluster.yaml +``` From a647c172a5563cb352b1ef692fa135f3ad72f83e Mon Sep 17 00:00:00 2001 From: "s.stoyanov" Date: Mon, 5 Jan 2026 11:46:33 +0200 Subject: [PATCH 4/5] feat: update values schema Signed-off-by: s.stoyanov --- charts/keycloakx/values.schema.json | 103 ++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/charts/keycloakx/values.schema.json b/charts/keycloakx/values.schema.json index ad571a7fa..10eccf612 100644 --- a/charts/keycloakx/values.schema.json +++ b/charts/keycloakx/values.schema.json @@ -37,6 +37,69 @@ } } } + }, + "infinispan": { + "type": "object", + "properties": { + "owners": { + "type": "object", + "properties": { + "sessions": { + "type": "integer" + }, + "authenticationSessions": { + "type": "integer" + }, + "userSessions": { + "type": "integer" + }, + "offlineSessions": { + "type": "integer" + }, + "clientSessions": { + "type": "integer" + }, + "offlineClientSessions": { + "type": "integer" + }, + "loginFailures": { + "type": "integer" + } + } + } + } + }, + "loadShedding": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "httpMaxQueuedRequests": { + "type": "integer" + } + } + }, + "threads": { + "type": "object", + "properties": { + "http": { + "type": "object", + "properties": { + "poolMaxThreads": { + "type": "string" + } + } + }, + "jgroups": { + "type": "object", + "properties": { + "maxThreads": { + "type": "string" + } + } + } + } } }, "properties": { @@ -239,9 +302,26 @@ "properties": { "stack": { "type": "string" + }, + "jgroups": { + "type": "object", + "properties": { + "config": { + "type": "string" + } + } } } }, + "infinispan": { + "$ref": "#/definitions/infinispan" + }, + "loadShedding": { + "$ref": "#/definitions/loadShedding" + }, + "threads": { + "$ref": "#/definitions/threads" + }, "proxy": { "type": "object", "properties": { @@ -292,6 +372,29 @@ }, "existingSecret": { "type": "string" + }, + "existingSecretKeys": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "port": { + "type": "string" + }, + "database": { + "type": "string" + }, + "vendor": { + "type": "string" + } + } } } }, From eeafd1e16f31a73c461d82a91cf2cd3ffd21b2ce Mon Sep 17 00:00:00 2001 From: "s.stoyanov" Date: Mon, 5 Jan 2026 11:49:14 +0200 Subject: [PATCH 5/5] feat: fix readme Signed-off-by: s.stoyanov --- charts/keycloakx/examples/infinispan-jdbcping/readme.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/charts/keycloakx/examples/infinispan-jdbcping/readme.md b/charts/keycloakx/examples/infinispan-jdbcping/readme.md index bbabc1074..a5a068715 100644 --- a/charts/keycloakx/examples/infinispan-jdbcping/readme.md +++ b/charts/keycloakx/examples/infinispan-jdbcping/readme.md @@ -1,9 +1,5 @@ # Keycloak.X with JDBC Ping, Infinispan Replication and Load Shedding -This example shows how to configure Keycloak.X to use a PostgreSQL database. - -# Setup - ## Deploy Kubernetes Cluster ## Deploy a PostgreSQL database