diff --git a/charts/keycloakx/Chart.yaml b/charts/keycloakx/Chart.yaml index 626e9d0a..d4c08184 100644 --- a/charts/keycloakx/Chart.yaml +++ b/charts/keycloakx/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: keycloakx -version: 7.1.11 -appVersion: 26.5.6 +version: 7.1.12 +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 43e31c33..09d31dba 100644 --- a/charts/keycloakx/README.md +++ b/charts/keycloakx/README.md @@ -238,7 +238,14 @@ 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 | `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.port` | Kubernetes API port for the update hook (Required if updateHook is 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: @@ -372,6 +379,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/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 new file mode 100644 index 00000000..38812cfe --- /dev/null +++ b/charts/keycloakx/templates/hooks/networkpolicy.yaml @@ -0,0 +1,24 @@ +{{- 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: {{ printf "%s/32" .Values.updateHook.kubernetesApi.ip | 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..1ba4c276 --- /dev/null +++ b/charts/keycloakx/templates/hooks/rbac.yaml @@ -0,0 +1,40 @@ +{{- 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..b3c1f5de --- /dev/null +++ b/charts/keycloakx/templates/hooks/scale-down.yaml @@ -0,0 +1,42 @@ +{{- 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="https://{{ .Values.updateHook.kubernetesApi.ip }}:{{ .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..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 @@ -646,3 +646,30 @@ extraManifests: [] # name: "{{ include \"keycloak.fullname\" . }}-tpl" # data: # foo: bar + +updateHook: + enabled: false + 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: + # ip: 10.20.30.40 + # port: 8443