From 3381af8b1711dda61acd586ff87e7e4a51586dff Mon Sep 17 00:00:00 2001 From: Mohamed Amine AROUS Date: Sat, 25 Apr 2026 23:03:08 +0200 Subject: [PATCH 1/2] feat(keycloakx): automate minor/major upgrade process in cluster mode Signed-off-by: Mohamed Amine AROUS --- charts/keycloakx/Chart.yaml | 2 +- charts/keycloakx/README.md | 29 ++++++++++++- .../templates/hooks/networkpolicy.yaml | 23 +++++++++++ charts/keycloakx/templates/hooks/rbac.yaml | 39 ++++++++++++++++++ .../keycloakx/templates/hooks/scale-down.yaml | 41 +++++++++++++++++++ charts/keycloakx/values.yaml | 28 +++++++++++++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 charts/keycloakx/templates/hooks/networkpolicy.yaml create mode 100644 charts/keycloakx/templates/hooks/rbac.yaml create mode 100644 charts/keycloakx/templates/hooks/scale-down.yaml diff --git a/charts/keycloakx/Chart.yaml b/charts/keycloakx/Chart.yaml index 626e9d0a..f5a8c349 100644 --- a/charts/keycloakx/Chart.yaml +++ b/charts/keycloakx/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: keycloakx -version: 7.1.11 +version: 7.1.12 appVersion: 26.5.6 description: Keycloak.X - Open Source Identity and Access Management for Modern Applications and Services keywords: diff --git a/charts/keycloakx/README.md b/charts/keycloakx/README.md index 43e31c33..1bc034a3 100644 --- a/charts/keycloakx/README.md +++ b/charts/keycloakx/README.md @@ -238,7 +238,15 @@ The following table lists the configurable parameters of the Keycloak-X chart an | `test.image.pullPolicy` | The image pull policy for the test Pod image | `IfNotPresent` | | `test.podSecurityContext` | SecurityContext for the entire test Pod | `{"fsGroup":1000}` | | `test.securityContext` | SecurityContext for the test container | `{"runAsNonRoot":true,"runAsUser":1000}` | -| `test.deletionPolicy` | `helm.sh/hook-delete-policy` for the test Pod | `before-hook-creation` | | `before-hook-creation` | +| `test.deletionPolicy` | `helm.sh/hook-delete-policy` for the test Pod | `before-hook-creation` | +| `updateHook.enabled` | If `true`, enables the update hook that runs before statefulset updates | `true` | +| `updateHook.image` | The image used for the update hook | `docker.io/curlimages/curl` | +| `updateHook.podSecurityContext` | SecurityContext for the update hook Pod | `{"fsGroup":1000,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}} | +| `updateHook.securityContext` | SecurityContext for the update hook container | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsUser":1000} | +| `updateHook.resources` | Resource requests and limits for the update hook container | `{"limits":{"cpu":"20m","memory":"32Mi"},"requests":{"cpu":"20m","memory":"32Mi"}}` | +| `updateHook.kubernetesApi.url` | Kubernetes API URL for the update hook (Required if updateHook is enabled) | `""` | +| `updateHook.kubernetesApi.port` | Kubernetes API port for the update hook (Required if updateHook is enabled) | `""` | +| `updateHook.kubernetesApi.cidr` | Kubernetes API CIDR for the update hook (Required if updateHook and networkpolicy are enabled) | `""` | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example: @@ -372,6 +380,25 @@ extraEnv: | For high availability, Keycloak must be run with multiple replicas (`replicas > 1`). The chart has a helper template (`keycloak.serviceDnsName`) that creates the DNS name based on the headless service. +#### Updating Minor and Major Versions in Cluster Mode + +Keycloak does **not** support minor or major version upgrades while running in cluster mode. Only patch updates are supported. +(Refer to the official Keycloak documentation for more details.) + +If you attempt such an upgrade in cluster mode, you may encounter errors due to JGroups version mismatches, for example: + +> WARN [org.jgroups.protocols.TCP] (TcpServer.Acceptor[7800]-1,keycloakx-1-2180(v=16.0.8)) JGRP000006: 10.151.254.47:7800: failed accepting connection from peer SSLSocket[hostname=127.0.0.6, port=51749, Session(1776862509121|TLS_AES_256_GCM_SHA384)]: java.io.IOException: 10.151.254.47:7800: readPeerAddress(): packet from /127.0.0.6:51749 has different version (5.3.16) from ours (5.5.1); discarding it + +Because of this limitation, **downtime is required** to perform minor or major version upgrades. + +**Recommended upgrade procedure:** + +1. Scale down the StatefulSet to a single replica to disable cluster mode. +2. Perform the version upgrade. +3. Once the upgrade is complete, scale the StatefulSet back to its original number of replicas. + +To simplify this process, the chart includes an `updateHook` parameter that automates these steps. + ### Default Cache Stack The default cache stack is now using `jdbc-ping` which leverages a table called `jgroups_ping` in the keycloak database to store the cache and significantly reduces network complexity. Keycloak has set this [transport stack](https://www.keycloak.org/server/caching#_transport_stacks) as the default starting in 26.1.0 and it is backwards compatible with all 26.X releases. diff --git a/charts/keycloakx/templates/hooks/networkpolicy.yaml b/charts/keycloakx/templates/hooks/networkpolicy.yaml new file mode 100644 index 00000000..d549da64 --- /dev/null +++ b/charts/keycloakx/templates/hooks/networkpolicy.yaml @@ -0,0 +1,23 @@ +{{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: "{{ include "keycloak.fullname" . }}-scale-down" + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "-15" +spec: + podSelector: + matchLabels: + job-name: "{{ include "keycloak.fullname" . }}-scale-down" + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: {{ .Values.updateHook.kubernetesApi.cidr | quote }} + ports: + - protocol: TCP + port: {{ .Values.updateHook.kubernetesApi.port }} +{{- end }} diff --git a/charts/keycloakx/templates/hooks/rbac.yaml b/charts/keycloakx/templates/hooks/rbac.yaml new file mode 100644 index 00000000..e20ecf20 --- /dev/null +++ b/charts/keycloakx/templates/hooks/rbac.yaml @@ -0,0 +1,39 @@ +{{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: "{{ include "keycloak.fullname" . }}-scale-down" + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "-15" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: "{{ include "keycloak.fullname" . }}-scale-down" + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "-15" +rules: + - apiGroups: ["apps"] + resources: ["statefulsets", "statefulsets/scale"] + verbs: ["get", "patch", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: "{{ include "keycloak.fullname" . }}-scale-down" + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "-15" +subjects: + - kind: ServiceAccount + name: "{{ include "keycloak.fullname" . }}-scale-down" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: "{{ include "keycloak.fullname" . }}-scale-down" +{{- end }} diff --git a/charts/keycloakx/templates/hooks/scale-down.yaml b/charts/keycloakx/templates/hooks/scale-down.yaml new file mode 100644 index 00000000..dfe88f15 --- /dev/null +++ b/charts/keycloakx/templates/hooks/scale-down.yaml @@ -0,0 +1,41 @@ +{{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) }} +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{ include "keycloak.fullname" . }}-scale-down" + annotations: + "helm.sh/hook": pre-upgrade + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded + "helm.sh/hook-weight": "-10" +spec: + template: + spec: + serviceAccountName: "{{ include "keycloak.fullname" . }}-scale-down" + restartPolicy: Never + securityContext: + {{- toYaml .Values.updateHook.podSecurityContext | nindent 8 }} + containers: + - name: kubectl + image: "{{ .Values.updateHook.image }}" + securityContext: + {{- toYaml .Values.updateHook.securityContext | nindent 12 }} + command: + - /bin/sh + - -ec + - | + TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" + NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)" + CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" + KUBERNETES_API="{{ .Values.updateHook.kubernetesApi.url }}:{{ .Values.updateHook.kubernetesApi.port }}" + + curl --fail --silent --show-error \ + --cacert "${CACERT}" \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/merge-patch+json" \ + -X PATCH \ + "${KUBERNETES_API}/apis/apps/v1/namespaces/${NAMESPACE}/statefulsets/{{ include "keycloak.fullname" . }}/scale" \ + --data '{"spec":{"replicas":1}}' + resources: + {{- toYaml .Values.updateHook.resources | nindent 12 }} + backoffLimit: 0 +{{- end }} diff --git a/charts/keycloakx/values.yaml b/charts/keycloakx/values.yaml index 85b99b3f..07d986ef 100644 --- a/charts/keycloakx/values.yaml +++ b/charts/keycloakx/values.yaml @@ -646,3 +646,31 @@ extraManifests: [] # name: "{{ include \"keycloak.fullname\" . }}-tpl" # data: # foo: bar + +updateHook: + enabled: true + image: docker.io/curlimages/curl + podSecurityContext: + fsGroup: 1000 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + runAsGroup: 1000 + runAsUser: 1000 + resources: + requests: + cpu: "20m" + memory: "32Mi" + limits: + cpu: "20m" + memory: "32Mi" + # kubernetesApi: + # url: https://kubernetes-api.example.com + # port: 8443 + # cidr: 10.20.30.40/32 From 1cb10582259fa6d01fb2618f1067d16411239e67 Mon Sep 17 00:00:00 2001 From: Mohamed Amine AROUS Date: Sat, 25 Apr 2026 23:04:39 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Bump=20keycloak=20version:=2026.5.6=20?= =?UTF-8?q?=E2=86=92=2026.6.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mohamed Amine AROUS --- charts/keycloakx/Chart.yaml | 2 +- charts/keycloakx/README.md | 5 ++--- charts/keycloakx/examples/postgresql-kubeping/Dockerfile | 2 +- charts/keycloakx/templates/hooks/networkpolicy.yaml | 3 ++- charts/keycloakx/templates/hooks/rbac.yaml | 1 + charts/keycloakx/templates/hooks/scale-down.yaml | 3 ++- charts/keycloakx/values.yaml | 7 +++---- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/charts/keycloakx/Chart.yaml b/charts/keycloakx/Chart.yaml index f5a8c349..d4c08184 100644 --- a/charts/keycloakx/Chart.yaml +++ b/charts/keycloakx/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: keycloakx version: 7.1.12 -appVersion: 26.5.6 +appVersion: 26.6.1 description: Keycloak.X - Open Source Identity and Access Management for Modern Applications and Services keywords: - sso diff --git a/charts/keycloakx/README.md b/charts/keycloakx/README.md index 1bc034a3..09d31dba 100644 --- a/charts/keycloakx/README.md +++ b/charts/keycloakx/README.md @@ -239,14 +239,13 @@ The following table lists the configurable parameters of the Keycloak-X chart an | `test.podSecurityContext` | SecurityContext for the entire test Pod | `{"fsGroup":1000}` | | `test.securityContext` | SecurityContext for the test container | `{"runAsNonRoot":true,"runAsUser":1000}` | | `test.deletionPolicy` | `helm.sh/hook-delete-policy` for the test Pod | `before-hook-creation` | -| `updateHook.enabled` | If `true`, enables the update hook that runs before statefulset updates | `true` | +| `updateHook.enabled` | If `true`, enables the update hook that runs before statefulset updates | `false` | | `updateHook.image` | The image used for the update hook | `docker.io/curlimages/curl` | | `updateHook.podSecurityContext` | SecurityContext for the update hook Pod | `{"fsGroup":1000,"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}} | | `updateHook.securityContext` | SecurityContext for the update hook container | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsGroup":1000,"runAsUser":1000} | | `updateHook.resources` | Resource requests and limits for the update hook container | `{"limits":{"cpu":"20m","memory":"32Mi"},"requests":{"cpu":"20m","memory":"32Mi"}}` | -| `updateHook.kubernetesApi.url` | Kubernetes API URL for the update hook (Required if updateHook is enabled) | `""` | | `updateHook.kubernetesApi.port` | Kubernetes API port for the update hook (Required if updateHook is enabled) | `""` | -| `updateHook.kubernetesApi.cidr` | Kubernetes API CIDR for the update hook (Required if updateHook and networkpolicy are enabled) | `""` | +| `updateHook.kubernetesApi.ip` | Kubernetes API IP for the update hook (Required if updateHook and networkpolicy are enabled) | `""` | Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example: diff --git a/charts/keycloakx/examples/postgresql-kubeping/Dockerfile b/charts/keycloakx/examples/postgresql-kubeping/Dockerfile index f587e7a7..2e180929 100644 --- a/charts/keycloakx/examples/postgresql-kubeping/Dockerfile +++ b/charts/keycloakx/examples/postgresql-kubeping/Dockerfile @@ -1,4 +1,4 @@ -FROM quay.io/keycloak/keycloak:26.5.6 +FROM quay.io/keycloak/keycloak:26.6.1 ENV JGROUPS_KUBERNETES_VERSION 1.0.16.Final diff --git a/charts/keycloakx/templates/hooks/networkpolicy.yaml b/charts/keycloakx/templates/hooks/networkpolicy.yaml index d549da64..38812cfe 100644 --- a/charts/keycloakx/templates/hooks/networkpolicy.yaml +++ b/charts/keycloakx/templates/hooks/networkpolicy.yaml @@ -1,4 +1,5 @@ {{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) .Values.networkPolicy.enabled }} +--- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -16,7 +17,7 @@ spec: egress: - to: - ipBlock: - cidr: {{ .Values.updateHook.kubernetesApi.cidr | quote }} + cidr: {{ printf "%s/32" .Values.updateHook.kubernetesApi.ip | quote }} ports: - protocol: TCP port: {{ .Values.updateHook.kubernetesApi.port }} diff --git a/charts/keycloakx/templates/hooks/rbac.yaml b/charts/keycloakx/templates/hooks/rbac.yaml index e20ecf20..1ba4c276 100644 --- a/charts/keycloakx/templates/hooks/rbac.yaml +++ b/charts/keycloakx/templates/hooks/rbac.yaml @@ -1,4 +1,5 @@ {{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) }} +--- apiVersion: v1 kind: ServiceAccount metadata: diff --git a/charts/keycloakx/templates/hooks/scale-down.yaml b/charts/keycloakx/templates/hooks/scale-down.yaml index dfe88f15..b3c1f5de 100644 --- a/charts/keycloakx/templates/hooks/scale-down.yaml +++ b/charts/keycloakx/templates/hooks/scale-down.yaml @@ -1,4 +1,5 @@ {{- if and .Values.updateHook.enabled (gt (int .Values.replicas) 1) }} +--- apiVersion: batch/v1 kind: Job metadata: @@ -26,7 +27,7 @@ spec: TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)" CACERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" - KUBERNETES_API="{{ .Values.updateHook.kubernetesApi.url }}:{{ .Values.updateHook.kubernetesApi.port }}" + KUBERNETES_API="https://{{ .Values.updateHook.kubernetesApi.ip }}:{{ .Values.updateHook.kubernetesApi.port }}" curl --fail --silent --show-error \ --cacert "${CACERT}" \ diff --git a/charts/keycloakx/values.yaml b/charts/keycloakx/values.yaml index 07d986ef..eb5cd7a8 100644 --- a/charts/keycloakx/values.yaml +++ b/charts/keycloakx/values.yaml @@ -11,7 +11,7 @@ image: # The Keycloak image repository repository: quay.io/keycloak/keycloak # Overrides the Keycloak image tag whose default is the chart appVersion - tag: "26.5.6" + tag: "" # Overrides the Keycloak image tag with a specific digest digest: "" # The Keycloak image pull policy @@ -648,7 +648,7 @@ extraManifests: [] # foo: bar updateHook: - enabled: true + enabled: false image: docker.io/curlimages/curl podSecurityContext: fsGroup: 1000 @@ -671,6 +671,5 @@ updateHook: cpu: "20m" memory: "32Mi" # kubernetesApi: - # url: https://kubernetes-api.example.com + # ip: 10.20.30.40 # port: 8443 - # cidr: 10.20.30.40/32