keycloakx 차트를 7.2.2에서 7.3.0으로 업그레이드한다

breaking_change_check 결과 breaking=false — custom-values.yaml이 실제로 쓰는 키
(image, http.relativePath, command, ingress, proxy, resources, database, extraEnv)
중 어느 것도 diff에 걸리지 않았다. 유일한 템플릿 변경(probe 경로가
managementRelativePath를 coalesce로 우선하도록 바뀜)도 relativePath: "/" 를 그대로
쓰므로 동작 영향이 없다. CRD·의존성 변경 없음.

appVersion은 26.6.4→26.7.2로 오르지만 custom-values.yaml이 자체 빌드 이미지
태그(26.7.1-bci15.7-hardened)를 명시적으로 고정하므로 이 업그레이드로 실제 배포
버전이 바뀌지는 않는다 — 26.7.2로 올리려면 hardened-containers에서 별도로
자체 빌드해야 한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wbsong111
2026-08-26 11:18:14 +09:00
parent 8b89aa78af
commit fae1ec2668
32 changed files with 3671 additions and 0 deletions
@@ -0,0 +1,7 @@
[bumpversion]
current_version = 7.0.0
commit = true
tag = false
message = Bump keycloakx chart version: {current_version} → {new_version}
[bumpversion:file:Chart.yaml]
@@ -0,0 +1,25 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
ci/
examples/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
@@ -0,0 +1,88 @@
# keycloakx 버전 갱신 가이드
## 1. git 작업 환경 구성
- DIP 카탈로그 git 다운로드
```
$ git clone https://github.com/paasup/dip-catalog.git
```
- 작업 브랜치로 체크아웃
```
$ git checkout -b update-keycloakx/7.3.0
```
## 2. helm chart 업데이트
#### * 배경 — 왜 keycloakx인가 (이슈 #1)
기존 `manifests/helm/keycloak/` 는 codecentric의 구버전 WildFly 기반 `keycloak` 차트(17.0.1-legacy)였고, bitnami postgresql 서브차트를 조건부 의존성으로 포함하고 있었다(bitnami 무료 배포 정책 변경으로 문제가 된 지점). codecentric은 17.0.1 이후 이 WildFly 차트를 더 이상 업데이트하지 않고, Quarkus 기반 Keycloak(17+)용으로 새 차트 `keycloakx`를 별도로 제공한다. `keycloakx`는 서브차트 의존성이 전혀 없어(`Chart.yaml`에 dependencies 없음) bitnami 의존 문제가 근본적으로 해소된다 — 그래서 `keycloak` 대신 `keycloakx`로 신규 등록한다. 기존 `manifests/helm/keycloak/18.4.0/` 는 같은 PR에서 제거한다(실제 소비자 없음, 문서 예시 표 한 줄 외 참조 없음).
### 1) 차트 버전 변경
- BUILD-README.md, CUSTOM-README.md, custom-values.yaml을 제외한 파일 삭제.
``` sh
# chart 디렉토리로 이동
cd manifests/helm/keycloakx/7.3.0
# 파일 삭제 전 삭제할 파일 목록 확인
find . -mindepth 1 \( -name "CUSTOM-README.md" -o -name "BUILD-README.md" -o -name "custom-values.yaml" \) -prune -o -print
# 파일 삭제
find . -mindepth 1 \( -name "CUSTOM-README.md" -o -name "BUILD-README.md" -o -name "custom-values.yaml" \) -prune -o -exec rm -rf {} +
```
- codecentric/keycloakx 차트 다운로드
``` sh
# manifests/helm 디렉토리로 이동
cd manifests/helm
# helm repo 추가
helm repo add codecentric https://codecentric.github.io/helm-charts
helm repo update
# helm 차트 pull
helm pull codecentric/keycloakx --version="7.3.0" --untar --untardir /tmp/keycloakx-pull
# 새 버전 디렉토리로 복사
mkdir -p keycloakx/7.3.0
cp -R /tmp/keycloakx-pull/keycloakx/. keycloakx/7.3.0/
rm -rf /tmp/keycloakx-pull
```
## 3. git push 및 tag 추가
- 갱신작업 진행 후 commit
```
$ git add .
$ git commit -m "add keycloakx/7.3.0 (이슈 #1 대응, keycloak/18.4.0 대체)"
```
- main 브랜치에 체크아웃 후 merge
```
$ git checkout main
$ git merge update-keycloakx/7.3.0
```
- git에 push 후 작업 브랜치 삭제
```
$ git push -u origin main
$ git branch -d update-keycloakx/7.3.0
```
- git tag 추가 후 push
```
$ git tag keycloakx/7.3.0
$ git push origin keycloakx/7.3.0
```
## 4. 차트 버전 정보
- keycloakx/7.3.0 (appVersion 26.7.2)
- `manifests/helm/keycloak/18.4.0`(codecentric 구버전 WildFly 기반, bitnami postgresql 서브차트 포함) 대체. 신규 등록.
- 서브차트 의존성 없음(`Chart.yaml`에 dependencies 없음).
- 서비스 배포를 위하여 custom-values.yaml에 정의하였다.
- **주의 1**: 이 차트의 `http.relativePath` 기본값은 `"/auth"`(구 WildFly Keycloak 호환용)다. `custom-values.yaml`에서 `"/"`로 반드시 오버라이드해야 한다 — 그대로 두면 OIDC issuer/admin API 경로가 소비자 앱들의 가정(경로 접미사 없음)과 어긋난다.
- **주의 2**: `command`/`args` 기본값이 둘 다 빈 배열이라, 지정하지 않으면 컨테이너가 인자 없는 `kc.sh`(도움말 출력, exit 0)로 끝나 CrashLoopBackOff가 된다. `custom-values.yaml`의 `command: ["/opt/keycloak/bin/kc.sh", "start"]`를 유지해야 한다.
- **주의 3**: `extraEnv`에 `KC_HOSTNAME`을 반드시 지정해야 한다 — 미지정 시 hostname-strict 검증(기본 true)으로 `hostname is not configured` 에러가 나며 기동이 실패한다.
- 세 항목 모두 dev 클러스터 격리 네임스페이스 실배포 테스트로 확인했다(admin 부트스트랩 로그, DB 연결, realm/client 생성 REST API 호출까지 성공).
@@ -0,0 +1,183 @@
# Upgrade History
## 7.2.2 → 7.3.0
### 변경 요약
- from_version: 7.2.2
- to_version: 7.3.0
- Chart `keycloakx` 7.2.2 → 7.3.0 업데이트
- Values: +4 / -0 / ~6 / type~0
- Templates: +0 / -0
- Dependencies: +0 / -0 / ~0
### custom-values.yaml 수정 필요 항목
없음
### 참고
- severity: warning
- breaking: false
# keycloakx 배포
## 1. 배포 방법
### 1) 배포 시 주의 사항
- keycloakx를 배포하려면 외부 postgresql이 필요하다(이 차트는 내장 DB를 지원하지 않는다 — 서브차트 의존성 없음).
- `custom-values.yaml``database.*` 를 배포된 DB 정보로 변경한다.
- **`http.relativePath: "/"` 를 지우거나 값을 바꾸지 말 것.** 이 차트의 기본값은 구버전 WildFly Keycloak 호환을 위한 `"/auth"`다. `"/"`로 명시하지 않으면 Quarkus 네이티브 경로 규칙과 달라져, OIDC issuer URL(`/realms/{realm}`)이나 admin REST API(`/admin/realms/...`)를 경로 접미사 없이 호출하는 소비 앱들의 연동이 조용히 깨진다.
- **`command`를 반드시 지정할 것.** 차트 기본값(`command: []`, `args: []`)만으로는 컨테이너가 인자 없는 `kc.sh`(도움말 출력, exit 0)로 끝나 CrashLoopBackOff가 된다(실측 확인). `custom-values.yaml``command: ["/opt/keycloak/bin/kc.sh", "start"]`를 유지한다.
- **`extraEnv``KC_HOSTNAME`을 반드시 지정할 것.** 미지정 시 `hostname is not configured; either configure hostname, or set hostname-strict to false`로 기동이 실패한다(실측 확인, hostname-strict 기본값 true).
- **이미지는 업스트림이 아니라 자체 빌드 하드닝 이미지다** — 아래 "3. 자체 빌드 이미지" 참조. `kcadm.sh`/`kcreg.sh`(`bin/client`)가 들어 있지 않다.
### 2) 배포 방법
``` sh
git clone https://github.com/paasup/dip-catalog.git
cd manifests/helm/keycloakx/7.3.0
helm upgrade keycloak ./ -f custom-values.yaml --install -n platform --create-namespace
```
## 2. custom-values.yaml 설명
### 1) pod 설정
| Name | 설명 | 기본값 |
| --- | --- | --- |
| `image.repository`/`image.tag` | **자체 빌드 하드닝 이미지**(아래 3절). 오프라인 설치 시에는 사설 미러 레지스트리로 변경. | `custom-values.yaml 참조` |
| `resources` | keycloak pod의 자원 설정. | `custom-values.yaml 참조` |
### 2) Postgresql 연동 설정
`database.*` 구조화 필드를 사용한다(구버전 `keycloak` 차트의 `DB_VENDOR`/`DB_ADDR` 같은 extraEnv 방식이 아니다).
``` yaml
database:
vendor: postgres
hostname: keycloak-postgresql # 배포된 DB 서비스명으로 변경
port: 5432
database: keycloak
username: keycloak
existingSecret: keycloak-db # kubernetes.io/basic-auth 시크릿 이름
existingSecretKey: password # 시크릿 안의 비밀번호 키 (기본값 "password")
extraEnv: |
- name: KC_HOSTNAME # 필수 — 미지정 시 hostname-strict 검증으로 기동 실패
value: keycloak.example.org
- name: KC_DB_SCHEMA # public 이 아닌 전용 스키마를 쓸 때 지정
value: keycloak
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
value: ChangeMe1234! # 예시 값 — 배포 전 교체한다
- name: TZ
value: Asia/Seoul
```
- `existingSecret`으로 지정한 시크릿은 미리 생성해야 한다(이 차트는 시크릿을 만들어주지 않고 참조만 한다):
``` sh
kubectl create secret generic keycloak-db \
--type=kubernetes.io/basic-auth \
--from-literal=username=keycloak \
--from-literal=password=<비밀번호> \
-n platform
```
- `KC_BOOTSTRAP_ADMIN_USERNAME`/`KC_BOOTSTRAP_ADMIN_PASSWORD`(Keycloak 25+ 표준 부트스트랩 메커니즘)는 **master realm이 완전히 비어있는 최초 부팅에만** admin 계정을 생성한다. 재설치·재기동 시 비밀번호를 바꿔주지 않는다 — 정상 동작이다.
### 3) Ingress 설정
#### 3.1) tls 시크릿 직접 생성
``` yaml
ingress:
enabled: true
ingressClassName: apisix # 사용하는 ingress controller 클래스로 변경
rules:
- host: keycloak.example.org # keycloak에서 사용할 도메인으로 변경
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- keycloak.example.org # keycloak에서 사용할 도메인으로 변경
secretName: keycloak-tls
```
인증서를 secret으로 직접 제공하는 경우:
``` sh
kubectl create secret tls keycloak-tls --cert=<path-to-cert-file> --key=<path-to-key-file> -n <namespace>
```
#### 3.2) cert-manager를 이용한 자동 생성
`custom-values.yaml`의 `ingress.annotations.cert-manager.io/cluster-issuer`를 미리 배포된 ClusterIssuer 이름으로 변경한다.
``` yaml
ingress:
enabled: true
ingressClassName: apisix
annotations:
cert-manager.io/cluster-issuer: "root-ca-issuer"
rules:
- host: keycloak.example.org
paths:
- path: /
pathType: Prefix
tls:
- hosts:
- keycloak.example.org
secretName: keycloak-tls
```
### 4) Proxy 설정
ingress/리버스 프록시 뒤에 배포하는 표준 구성:
``` yaml
proxy:
enabled: true
mode: forwarded
```
## 3. 자체 빌드 이미지
`image.repository`/`image.tag` 는 업스트림 `quay.io/keycloak/keycloak` 이 아니라
`docker.io/paasup/keycloak` 자체 빌드 하드닝 이미지를 가리킨다. 빌드 정의와, 왜 자체
빌드인지·업스트림과 무엇이 다른지는 별도 레포 `hardened-containers` 의 `images/keycloak/`
(`README.md` 포함)가 단일 출처다 — 이 레포에는 없다. 배포 관점에서 알아야 할 것만
아래에 적는다.
### 앱 버전이 차트 `appVersion` 과 다르다
차트(7.3.0) `appVersion` 은 `26.7.2` 지만 이미지는 **Keycloak 26.7.1** 이다.
- `appVersion` 은 `image.tag` 미지정 시의 기본값일 뿐이고, `custom-values.yaml` 이
태그를 명시하므로 실제 배포 버전은 26.7.1 이다.
- 7.2.2 시절에는 차트 `appVersion`(26.6.4)이 우리가 쓰는 26.7.1보다 뒤처져 있었다.
7.3.0(`appVersion` 26.7.2)로 올라오며 이제 반대로 차트 기본값이 우리 배포판보다
한 패치 앞선다 — 26.7.2로 올리려면 `hardened-containers`에서 그 버전을 새로 자체
빌드해야 한다("이미지 갱신" 절 참고). 차트 업그레이드 자체는 이미지 버전과 무관하게
적용 가능했다(breaking=false, 아래 "Upgrade History" 참고).
- 26.6.4 를 쓰지 않는 이유: 26.7.1(및 26.6.5)에서만 패치된 `keycloak-services`
HIGH 5건(CVE-2026-16102 / 16442 / 16443 / 15572 / 15573)에 취약하다.
### 업스트림 이미지와의 차이 — `kcadm.sh`/`kcreg.sh` 없음
`/opt/keycloak/bin/client/` 를 제거했다. 이 디렉토리의 `keycloak-admin-cli-*.jar` 가
취약한 jackson 을 shade 로 품은 uber-jar 라 교체가 불가능해서다. **서버 런타임은 이
디렉토리를 쓰지 않으므로 배포 동작에는 영향이 없다.**
파드에 exec 해서 `kcadm.sh` 를 쓰던 절차가 있다면 대안이 필요하다.
- 권장: admin REST API 직접 호출 (`/admin/realms/...`, 토큰은
`/realms/master/protocol/openid-connect/token` 에서 발급)
- 또는 업스트림 이미지(`quay.io/keycloak/keycloak:26.7.1`)를 일회성 잡/디버그
컨테이너로 띄워 `kcadm.sh` 만 쓴다 (서버로 쓰지 않는다)
### 이미지 갱신
`hardened-containers` 레포의 `images/keycloak/suse.build.env` 의 `KEYCLOAK_VERSION` 과
jar 오버레이 버전을 사람이 고쳐 커밋하는 것이 갱신 트리거다(그 레포에서). 그 레포의
`build-image.yml` 이 빌드·게이트 통과 후 push 하면 `published.json` 이 갱신되고,
이 카탈로그의 `catalog-tag-update.yml` 이 그것을 읽어가 이 파일의 `image.tag` 를
자동 갱신한 브랜치를 이 레포에 만든다.
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
appVersion: 26.7.2
description: Keycloak.X - Open Source Identity and Access Management for Modern Applications
and Services
home: https://www.keycloak.org/
icon: https://www.keycloak.org/resources/images/keycloak_logo_200px.svg
keywords:
- sso
- idm
- openid connect
- saml
- kerberos
- oauth
- ldap
- keycloakx
- quarkus
maintainers:
- email: thomas.darimont+github@gmail.com
name: thomasdarimont
name: keycloakx
sources:
- https://github.com/codecentric/helm-charts
- https://github.com/keycloak/keycloak/tree/main/quarkus/container
version: 7.3.0
+4
View File
@@ -0,0 +1,4 @@
approvers:
- thomasdarimont
reviewers:
- thomasdarimont
+694
View File
@@ -0,0 +1,694 @@
# Keycloak-X
[Keycloak-X](http://www.keycloak.org/) is an open source identity and access management for modern applications and services.
Note that this chart is the logical successor of the Wildfly based [codecentric/keycloak](https://github.com/codecentric/helm-charts/tree/master/charts/keycloak) chart.
## TL;DR;
```console
$ cat << EOF > values.yaml
command:
- "/opt/keycloak/bin/kc.sh"
- "start"
- "--http-port=8080"
- "--hostname-strict=false"
extraEnv: |
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
value: admin
- name: JAVA_OPTS_APPEND
value: >-
-Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless
EOF
$ helm install keycloak codecentric/keycloakx --values ./values.yaml
```
Note that the default configuration is not suitable for production since it uses a h2 file database by default.
It is strongly recommended to use a dedicated database with Keycloak.
For more examples see the [examples](./examples) folder.
## Introduction
This chart bootstraps a [Keycloak](http://www.keycloak.org/) StatefulSet on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
It provisions a fully featured Keycloak installation.
For more information on Keycloak and its capabilities, see its [documentation](http://www.keycloak.org/documentation.html).
## Installing the Chart
To install the chart with the release name `keycloakx`:
```console
$ helm install keycloak codecentric/keycloakx
```
or via GitHub Container Registry:
```console
$ helm install keycloak oci://ghcr.io/codecentric/helm-charts/keycloakx --version <version>
```
## Uninstalling the Chart
To uninstall the `keycloakx` deployment:
```console
$ helm uninstall keycloakx
```
## Configuration
The following table lists the configurable parameters of the Keycloak-X chart and their default values.
| Parameter | Description | Default |
|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `fullnameOverride` | Optionally override the fully qualified name | `""` |
| `nameOverride` | Optionally override the name | `""` |
| `namespaceOverride` | Optionally override the namespace for all resources. Useful for umbrella charts that deploy multiple aliased keycloak instances each into their own namespace. | `""` |
| `replicas` | The number of replicas to create | `1` |
| `commonLabels` | Additional labels applied to every resource in this chart, and on the StatefulSet's pods | `{}` |
| `image.repository` | The Keycloak image repository | `quay.io/keycloak/keycloak` |
| `image.tag` | Overrides the Keycloak image tag whose default is the chart version | `""` |
| `image.digest` | Overrides the Keycloak image tag with a digest | `""` |
| `image.pullPolicy` | The Keycloak image pull policy | `IfNotPresent` |
| `imagePullSecrets` | Image pull secrets for the Pod | `[]` |
| `hostAliases` | Mapping between IPs and hostnames that will be injected as entries in the Pod's hosts files | `[]` |
| `enableServiceLinks` | Indicates whether information about services should be injected into Pod's environment variables, matching the syntax of Docker links | `true` |
| `updateStrategy` | StatefulSet update strategy. One of `RollingUpdate` or `OnDelete` | `RollingUpdate` |
| `podManagementPolicy` | Pod management policy. One of `Parallel` or `OrderedReady` | `OrderedReady` |
| `revisionHistoryLimit` | Number of old ReplicaSets to retain for rollback. Defaults to Kubernetes default (10) if not set | `""` |
| `restartPolicy` | Pod restart policy. One of `Always`, `OnFailure`, or `Never` | `Always` |
| `serviceAccount.create` | Specifies whether a ServiceAccount should be created | `true` |
| `serviceAccount.allowReadPods` | Specifies whether the ServiceAccount can get or list pods | `false` |
| `serviceAccount.name` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | `""` |
| `serviceAccount.annotations` | Additional annotations for the ServiceAccount | `{}` |
| `serviceAccount.labels` | Additional labels for the ServiceAccount | `{}` |
| `serviceAccount.imagePullSecrets` | Image pull secrets that are attached to the ServiceAccount | `[]` |
| `serviceAccount.automountServiceAccountToken` | Automount API credentials for the Service Account | `true` |
| `rbac.create` | Specifies whether RBAC resources are to be created | `false` |
| `rbac.rules` | Custom RBAC rules, e. g. for KUBE_PING | `[]` |
| `podSecurityContext` | 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) | `{"fsGroup":1000}` |
| `securityContext` | SecurityContext for the Keycloak container | `{"runAsNonRoot":true,"runAsUser":1000}` |
| `extraInitContainers` | Additional init containers, e. g. for providing custom themes | `[]` |
| `skipInitContainers` | Skip all init containers (to avoid issues with service meshes which require sidecar proxies for connectivity) | `false` |
| `extraContainers` | Additional sidecar containers, e. g. for a database proxy, such as Google's cloudsql-proxy | `[]` |
| `lifecycleHooks` | Lifecycle hooks for the Keycloak container | `{}` |
| `terminationGracePeriodSeconds` | 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 | `60` |
| `clusterDomain` | The internal Kubernetes cluster domain | `cluster.local` |
| `command` | Overrides the default entrypoint of the Keycloak container | `[]` |
| `args` | Overrides the default args for the Keycloak container | `[]` |
| `extraEnv` | Additional environment variables for Keycloak | `""` |
| `extraEnvFrom` | Additional environment variables for Keycloak mapped from a Secret or ConfigMap | `""` |
| `priorityClassName` | Pod priority class name | `""` |
| `affinity` | Pod affinity | Hard node and soft zone anti-affinity |
| `topologySpreadConstraints` | Topology spread constraints | Constraints used to spread pods |
| `nodeSelector` | Node labels for Pod assignment | `{}` |
| `tolerations` | Node taints to tolerate | `[]` |
| `podLabels` | Additional Pod labels | `{}` |
| `podAnnotations` | Additional Pod annotations | `{}` |
| `livenessProbe` | Liveness probe configuration | `{"httpGet":{"path":"{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ \| trimSuffix "/" }}/health/live","port":"http-internal","scheme":"HTTP"},"initialDelaySeconds":0,"timeoutSeconds":5}` |
| `readinessProbe` | Readiness probe configuration | `{"httpGet":{"path":"{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ \| trimSuffix "/" }}/health/ready","port":"http-internal","scheme":"HTTP"},"initialDelaySeconds":10,"timeoutSeconds":1}` |
| `startupProbe` | Startup probe configuration | `{"httpGet":{"path":"{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ \| trimSuffix "/" }}/health","port":"http-internal","scheme":"HTTP"},"initialDelaySeconds":15,"timeoutSeconds":1,"failureThreshold":60,"periodSeconds":5}` |
| `resources` | Pod resource requests and limits | `{}` |
| `extraVolumes` | Add additional volumes, e. g. for custom themes | `""` |
| `volumeClaimTemplates` | Add volume claim templates to the StatefulSet, e. g. for dynamic provisioning | `""` |
| `extraVolumeMounts` | Add additional volumes mounts, e. g. for custom themes | `""` |
| `extraPorts` | Add additional ports, e. g. for admin console or exposing JGroups ports | `[]` |
| `podDisruptionBudget` | Pod disruption budget | `{}` |
| `statefulsetAnnotations` | Annotations for the StatefulSet | `{}` |
| `statefulsetLabels` | Additional labels for the StatefulSet | `{}` |
| `secrets` | Configuration for secrets that should be created | `{}` |
| `service.annotations` | Annotations for HTTP service | `{}` |
| `service.labels` | Additional labels for headless and HTTP Services | `{}` |
| `service.type` | The Service type | `ClusterIP` |
| `service.loadBalancerIP` | Optional IP for the load balancer. Used for services of type LoadBalancer only | `""` |
| `loadBalancerSourceRanges` | Optional List of allowed source ranges (CIDRs). Used for service of type LoadBalancer only | `[]` |
| `service.externalTrafficPolicy` | Optional external traffic policy. Used for services of type LoadBalancer & NodePort only | `"Cluster"` |
| `service.internalTrafficPolicy` | Optional internal traffic policy. Valid values: `Cluster`, `Local`. | `""` |
| `service.httpPort` | The http Service port | `80` |
| `service.httpNodePort` | The HTTP Service node port if type is NodePort | `""` |
| `service.httpsPort` | The HTTPS Service port | `8443` |
| `service.httpsNodePort` | The HTTPS Service node port if type is NodePort | `""` |
| `service.extraPorts` | Additional Service ports, e. g. for custom admin console | `[]` |
| `service.sessionAffinity` | sessionAffinity for Service, e. g. "ClientIP" | `""` |
| `service.sessionAffinityConfig` | sessionAffinityConfig for Service | `{}` |
| `serviceHeadless.annotations` | Annotations for headless service | `{}` |
| `serviceHeadless.extraPorts` | Add additional ports to the headless service, e. g. for admin console or exposing JGroups ports | `[]` |
| `ingress.enabled` | If `true`, an Ingress is created | `false` |
| `ingress.rules` | List of Ingress Ingress rule | see below |
| `ingress.rules[0].host` | Host for the Ingress rule | `{{ .Release.Name }}.keycloak.example.com` |
| `ingress.rules[0].paths` | Paths for the Ingress rule | see below |
| `ingress.rules[0].paths[0].path` | Path for the Ingress rule | `/` |
| `ingress.rules[0].paths[0].pathType` | Path Type for the Ingress rule | `Prefix` |
| `ingress.rules[0].paths[0].serviceName` | Optional override for backend service name (useful for AWS ALB action annotations) | `""` |
| `ingress.rules[0].paths[0].servicePort` | Optional override for backend service port name | `""` |
| `ingress.servicePort` | The Service port targeted by the Ingress | `http` |
| `ingress.annotations` | Ingress annotations | `{}` |
| `ingress.ingressClassName` | The name of the Ingress Class associated with the ingress | `""` |
| `ingress.labels` | Additional Ingress labels | `{}` |
| `ingress.tls` | TLS configuration | see below |
| `ingress.tls[0].hosts` | List of TLS hosts | `[keycloak.example.com]` |
| `ingress.tls[0].secretName` | Name of the TLS secret | `""` |
| `ingress.console.enabled` | If `true`, an Ingress for the console is created | `false` |
| `ingress.console.rules` | List of Ingress Ingress rule for the console | see below |
| `ingress.console.rules[0].host` | Host for the Ingress rule for the console | `{{ .Release.Name }}.keycloak.example.com` |
| `ingress.console.rules[0].paths` | Paths for the Ingress rule for the console | see below |
| `ingress.console.rules[0].paths[0].path` | Path for the Ingress rule for the console | `[{{ tpl .Values.http.relativePath $ \| trimSuffix "/" }}/admin]` |
| `ingress.console.rules[0].paths[0].pathType` | Path Type for the Ingress rule for the console | `Prefix` |
| `ingress.console.rules[0].paths[0].serviceName` | Optional override for backend service name (console ingress) | `""` |
| `ingress.console.rules[0].paths[0].servicePort` | Optional override for backend service port name (console ingress) | `""` |
| `ingress.console.labels` | Additional labels for the console ingress only | `{}` |
| `ingress.console.annotations` | Ingress annotations for the console | `{}` |
| `ingress.console.ingressClassName` | The name of the Ingress Class associated with the console ingress | `""` |
| `ingress.console.tls` | TLS configuration | see below |
| `ingress.console.tls[0].hosts` | List of TLS hosts | `[keycloak.example.com]` |
| `ingress.console.tls[0].secretName` | Name of the TLS secret | `""` |
| `networkPolicy.enabled` | If true, the ingress network policy is deployed | `false` |
| `networkPolicy.extraFrom` | Allows to define allowed external ingress traffic (see Kubernetes doc for network policy `from` format) | `[]` |
| `networkPolicy.egress` | Allows to define allowed egress from Keycloak pods (see Kubernetes doc for network policy `egress` format) | `[]` |
| `httpRoute.enabled` | If `true`, a Gateway API HTTPRoute is created | `false` |
| `httpRoute.labels` | Additional HTTPRoute labels | `{}` |
| `httpRoute.annotations` | HTTPRoute annotations | `{}` |
| `httpRoute.servicePort` | The Service port targeted by the HTTPRoute | `80` |
| `httpRoute.parentRefs` | Gateways this HTTPRoute is attached to. Unused when `listenerSet.enabled` is `true` | see values.yaml |
| `httpRoute.hostnames` | Hostnames matching HTTP header. Unused when `listenerSet.enabled` is `true` | `[chart-example.local]` |
| `httpRoute.rules` | List of rules and filters applied | see values.yaml |
| `httpRoute.console.enabled` | If `true`, a separate HTTPRoute is created for the admin console path only | `false` |
| `httpRoute.console.labels` | Additional labels for the console HTTPRoute | `{}` |
| `httpRoute.console.annotations` | Annotations for the console HTTPRoute | `{}` |
| `httpRoute.console.parentRefs` | Gateways the console HTTPRoute is attached to. Falls back to `httpRoute.parentRefs` if unset | see values.yaml |
| `httpRoute.console.hostnames` | Hostnames for the console HTTPRoute. Falls back to `httpRoute.hostnames` if unset | `[chart-example.local]` |
| `httpRoute.console.rules` | Rules for the console HTTPRoute | see values.yaml |
| `httpRoute.listenerSet.enabled` | If `true`, a Gateway API ListenerSet is created alongside the HTTPRoute, enabling namespace-level listener configuration without Gateway write access | `false` |
| `httpRoute.listenerSet.labels` | Additional ListenerSet labels | `{}` |
| `httpRoute.listenerSet.annotations` | ListenerSet annotations | `{}` |
| `httpRoute.listenerSet.parentRef.name` | Name of the Gateway this ListenerSet attaches to | `gateway` |
| `httpRoute.listenerSet.parentRef.namespace` | Namespace of the Gateway this ListenerSet attaches to | `""` |
| `httpRoute.listenerSet.listeners` | Listeners to attach to the Gateway. Full Gateway API listener spec accepted. Listener hostnames are used to populate the HTTPRoute `hostnames` field | `[]` |
| `route.enabled` | If `true`, an OpenShift Route is created | `false` |
| `route.path` | Path for the Route | `/` |
| `route.annotations` | Route annotations | `{}` |
| `route.labels` | Additional Route labels | `{}` |
| `route.host` | Host name for the Route | `""` |
| `route.tls.enabled` | If `true`, TLS is enabled for the Route | `true` |
| `route.tls.insecureEdgeTerminationPolicy` | Insecure edge termination policy of the Route. Can be `None`, `Redirect`, or `Allow` | `Redirect` |
| `route.tls.termination` | TLS termination of the route. Can be `edge`, `passthrough`, or `reencrypt` | `edge` |
| `dbchecker.enabled` | Enable database readiness check | `false` |
| `dbchecker.image.repository` | Docker image used to check database readiness at startup | `docker.io/busybox` |
| `dbchecker.image.tag` | Image tag for the dbchecker image | `1.32` |
| `dbchecker.image.pullPolicy` | Image pull policy for the dbchecker image | `IfNotPresent` |
| `dbchecker.securityContext` | SecurityContext for the dbchecker container | `{"allowPrivilegeEscalation":false,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` |
| `dbchecker.resources` | Resource requests and limits for the dbchecker container | `{"limits":{"cpu":"20m","memory":"32Mi"},"requests":{"cpu":"20m","memory":"32Mi"}}` |
| `database.vendor` | Database vendor | unset |
| `database.hostname` | Database Hostname | unset |
| `database.port` | Database Port | unset |
| `database.username` | Database User | unset |
| `database.password` | Database Password | unset |
| `database.database` | Database | unset |
| `database.existingSecret` | Existing Secret containing database password (expects key `password`) | `""` |
| `database.existingSecretKey` | Key in existing Secret containing database password | `""`
| `cache.stack` | Cache / Cluster Discovery, use `custom` to disable automatic configuration. | `default` |
| `proxy.enabled` | If `true`, the `KC_PROXY` env variable will be set to the configured mode | `true` |
| `proxy.mode` | The configured proxy mode | `forwarded` |
| `proxy.http.enabled` | If `true`, HTTP forwarding is enabled | `true` |
| `http.relativePath` | The relative http path (context-path) | `/auth` |
| `http.managementRelativePath` | The relative path for the management interface (KC_HTTP_MANAGEMENT_RELATIVE_PATH). When empty, Keycloak inherits the value from `http.relativePath`. Set to `/` to serve management endpoints at the root. | `""` |
| `http.internalPort` | The port of the internal management interface | `http-internal` |
| `http.internalScheme` | The scheme of the internal management interface | `HTTP` |
| `metrics.enabled` | If `true` then the metrics endpoint is exposed | `true` |
| `health.enabled` | If `true` then the health endpoint is exposed. If the `readinessProbe` is is needed `metrics.enable` must be `true`. | `true` |
| `serviceMonitor.enabled` | If `true`, a ServiceMonitor resource for the prometheus-operator is created | `false` |
| `serviceMonitor.namespace` | Optionally sets a target namespace in which to deploy the ServiceMonitor resource | `""` |
| `serviceMonitor.namespaceSelector` | Optionally sets a namespace selector for the ServiceMonitor | `{}` |
| `serviceMonitor.annotations` | Annotations for the ServiceMonitor | `{}` |
| `serviceMonitor.labels` | Additional labels for the ServiceMonitor | `{}` |
| `serviceMonitor.interval` | Interval at which Prometheus scrapes metrics | `10s` |
| `serviceMonitor.scrapeTimeout` | Timeout for scraping | `10s` |
| `serviceMonitor.relabelings` | Relabelings for the Servicemonitor | `[]` |
| `serviceMonitor.metricRelabelings` | metricRelabelings for the Servicemonitor | `[]` |
| `serviceMonitor.path` | The path at which metrics are served | `{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ \| trimSuffix "/" }}/metrics` |
| `serviceMonitor.port` | The Service port at which metrics are served | `http-internal` |
| `serviceMonitor.scheme` | The scheme to use for scraping metrics ("http" or "https"); if not set, the `http.internalScheme` value is used | `""` |
| `serviceMonitor.tlsConfig` | TLS configuration for the ServiceMonitor, set CA certificates or `insecureSkipVerify` if Keycloak uses https | `{}` |
| `extraServiceMonitor.enabled` | If `true`, an additional ServiceMonitor resource for the prometheus-operator is created. Could be used for additional metrics via [Keycloak Metrics SPI](https://github.com/aerogear/keycloak-metrics-spi) | `false` |
| `extraServiceMonitor.namespace` | Optionally sets a target namespace in which to deploy the additional ServiceMonitor resource | `""` |
| `extraServiceMonitor.namespaceSelector` | Optionally sets a namespace selector for the additional ServiceMonitor | `{}` |
| `extraServiceMonitor.annotations` | Annotations for the additional ServiceMonitor | `{}` |
| `extraServiceMonitor.labels` | Additional labels for the additional ServiceMonitor | `{}` |
| `extraServiceMonitor.interval` | Interval at which Prometheus scrapes metrics | `10s` |
| `extraServiceMonitor.scrapeTimeout` | Timeout for scraping | `10s` |
| `extraServiceMonitor.relabelings` | Relabelings for the additional ServiceMonitor | `[]` |
| `extraServiceMonitor.metricRelabelings` | metricRelabelings for the additional ServiceMonitor | `[]` |
| `extraServiceMonitor.path` | The path at which metrics are served | `{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ \| trimSuffix "/" }}/metrics` |
| `extraServiceMonitor.port` | The Service port at which metrics are served | `http-internal` |
| `extraServiceMonitor.scheme` | The scheme to use for scraping metrics ("http" or "https"); if not set, the `http.internalScheme` value is used | `""` |
| `prometheusRule.enabled` | If `true`, a PrometheusRule resource for the prometheus-operator is created | `false` |
| `prometheusRule.namespace` | Optionally sets a target namespace in which to deploy the PrometheusRule resource | `""` |
| `prometheusRule.annotations` | Annotations for the PrometheusRule | `{}` |
| `prometheusRule.labels` | Additional labels for the PrometheusRule | `{}` |
| `prometheusRule.rules` | List of rules for Prometheus | `[]` |
| `autoscaling.enabled` | Enable creation of a HorizontalPodAutoscaler resource | `false` |
| `autoscaling.labels` | Additional labels for the HorizontalPodAutoscaler resource | `{}` |
| `autoscaling.minReplicas` | The minimum number of Pods when autoscaling is enabled | `3` |
| `autoscaling.maxReplicas` | The maximum number of Pods when autoscaling is enabled | `10` |
| `autoscaling.metrics` | The metrics configuration for the HorizontalPodAutoscaler | `[{"resource":{"name":"cpu","target":{"averageUtilization":80,"type":"Utilization"}},"type":"Resource"}]` |
| `autoscaling.behavior` | The scaling policy configuration for the HorizontalPodAutoscaler | `{"scaleDown":{"policies":[{"periodSeconds":300,"type":"Pods","value":1}],"stabilizationWindowSeconds":300}` |
| `test.enabled` | If `true`, test resources are created | `false` |
| `test.image.repository` | The image for the test Pod | `docker.io/seleniarm/standalone-chromium` |
| `test.image.tag` | The tag for the test Pod image | `117.0` |
| `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` |
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example:
```console
$ helm install keycloak codecentric/keycloakx -n keycloak --set replicas=1
```
Alternatively, a YAML file that specifies the values for the parameters can be provided while
installing the chart. For example:
```console
$ helm install keycloak codecentric/keycloakx -n keycloak --values values.yaml
```
The chart offers great flexibility.
It can be configured to work with the official Keycloak-X Docker image but any custom image can be used as well.
For the official Docker image, please check it's configuration at https://github.com/keycloak/keycloak/tree/main/quarkus/container.
### Usage of the `tpl` Function
The `tpl` function allows us to pass string values from `values.yaml` through the templating engine.
It is used for the following values:
* `extraInitContainers`
* `extraContainers`
* `extraEnv`
* `extraEnvFrom`
* `affinity`
* `extraVolumeMounts`
* `extraVolumes`
* `livenessProbe`
* `readinessProbe`
* `startupProbe`
* `topologySpreadConstraints`
Additionally, custom labels and annotations can be set on various resources the values of which being passed through `tpl` as well.
It is important that these values be configured as strings.
Otherwise, installation will fail.
See example for Google Cloud Proxy or default affinity configuration in `values.yaml`.
### JVM Settings
Keycloak sets the following system properties by default:
`-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m`
You can override these by setting the `JAVA_OPTS` environment variable.
Make sure you configure container support.
This allows you to only configure memory using Kubernetes resources and the JVM will automatically adapt.
```yaml
extraEnv: |
- name: JAVA_OPTS
value: >-
-XX:MaxRAMPercentage=50.0
```
Alternatively one can append custom JVM options by setting the `JAVA_OPTS_APPEND` environment variable.
The parameter `-Djava.net.preferIPv4Stack=true` is [optional](https://github.com/keycloak/keycloak/commit/ee205c8fbc1846f679bd604fa8d25310c117c87e) for [Keycloak >= v22](https://www.keycloak.org/server/configuration-production#_configure_keycloak_server_with_ipv4_or_ipv6).
The parameter `-XX:+UseContainerSupport` is no longer required for [Keycloak >= v21 based on JDK v17](https://github.com/keycloak/keycloak/blob/release/21.0/quarkus/container/Dockerfile#L20).
The parameter `-Djava.awt.headless=true` is no longer required for Quarkus based Keycloak as it is set by [default](https://quarkus.io/guides/building-native-image).
#### Using an External Database
The Keycloak Docker image supports various database types.
Configuration happens in a generic manner.
##### Using a Secret Managed by the Chart
The following examples uses a PostgreSQL database with a secret that is managed by the Helm chart.
```yaml
dbchecker:
enabled: true
database:
vendor: postgres
hostname: mypostgres
port: 5432
username: '{{ .Values.dbUser }}'
password: '{{ .Values.dbPassword }}'
database: mydb
```
`dbUser` and `dbPassword` are custom values you'd then specify on the commandline using `--set-string`.
##### Using an Existing Secret
The following examples uses a PostgreSQL database with an existing secret.
```yaml
dbchecker:
enabled: true
database:
vendor: postgres
hostname: mypostgres
port: 5432
database: mydb
username: db-user
existingSecret: byo-db-creds # Password is retrieved via .password
```
### Creating a Keycloak Admin User
The Keycloak-X Docker image supports creating an initial admin user.
It must be configured via environment variables:
* `KEYCLOAK_ADMIN`
* `KEYCLOAK_ADMIN_PASSWORD`
This can be done like so in the `values.yaml`, where the `KEYCLOAK_ADMIN` is an insecure example with the value in plaintext.
The `KEYCLOAK_ADMIN_PASSWORD` is referenced from already existing secret but for testing it can be set with `value` too.
```yaml
extraEnv: |
- name: KEYCLOAK_ADMIN
value: admin
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-admin-password
key: password
```
### High Availability and Clustering
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.
### 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.
It is recommended to use the new default value as it works in kubernetes and across multiple cloud providers alike. Currently all other options have been marked as deprecated. However, if the original value of `kubernetes` is required in a given environment, it can still be set by using a custom stack:
```yaml
cache:
stack: custom
```
Addtionally, the following environment variables would need to be added for it to function properly:
```yaml
extraEnv: |
- name: KC_CACHE
value: "ispn"
- name: KC_CACHE_STACK
value: "kubernetes"
- name: JAVA_OPTS_APPEND
value: >-
-Djgroups.dns.query={{ include "keycloak.fullname" . }}-headless
```
#### Custom Service Discovery
If a custom JGroups discovery is needed, then you can configure:
```yaml
cache:
stack: custom
```
You can then reference your custom infinispan configuration file, e.g. `cache-custom.xml` via the `KC_CACHE_CONFIG_FILE` environment variable.
Note that the `cache-custom.xml` must be available via `/opt/keycloak/conf/cache-custom.xml`.
```yaml
extraEnv: |
- name: KC_CACHE
value: "ispn"
- name: KC_CACHE_CONFIG_FILE
value: cache-custom.xml
```
#### Autoscaling
Due to the caches in Keycloak only replicating to a few nodes (two in the example configuration above) and the limited controls around autoscaling built into Kubernetes, it has historically been problematic to autoscale Keycloak.
However, in Kubernetes 1.18 [additional controls were introduced](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-configurable-scaling-behavior) which make it possible to scale down in a more controlled manner.
The example autoscaling configuration in the values file scales from three up to a maximum of ten Pods using CPU utilization as the metric. Scaling up is done as quickly as required but scaling down is done at a maximum rate of one Pod per five minutes.
Autoscaling can be enabled as follows:
```yaml
autoscaling:
enabled: true
```
KUBE_PING service discovery seems to be the most reliable mechanism to use when enabling autoscaling, due to being faster than DNS_PING at detecting changes in the cluster.
### Running Keycloak Behind a Reverse Proxy
When running Keycloak behind a reverse proxy, which is the case when using an ingress controller,
proxy address forwarding must be enabled as follows:
```yaml
extraEnv: |
- name: KC_PROXY
value: "passthrough"
```
### Providing a Custom Theme
One option is certainly to provide a custom Keycloak-X image that includes the theme.
However, if you prefer to stick with the official Keycloak-X image, you can use an init container as theme provider.
Create your own theme and package it up into a Docker image.
```docker
FROM busybox
COPY mytheme /mytheme
```
In combination with an `emptyDir` that is shared with the Keycloak container, configure an init container that runs your theme image and copies the theme over to the right place where Keycloak will pick it up automatically.
```yaml
extraInitContainers: |
- name: theme-provider
image: myuser/mytheme:1
imagePullPolicy: IfNotPresent
command:
- sh
args:
- -c
- |
echo "Copying theme..."
cp -R /mytheme/* /theme
volumeMounts:
- name: theme
mountPath: /theme
extraVolumeMounts: |
- name: theme
mountPath: /opt/keycloak/themes/mytheme
extraVolumes: |
- name: theme
emptyDir: {}
```
### Using Google Cloud SQL Proxy
Depending on your environment you may need a local proxy to connect to the database.
This is, e. g., the case for Google Kubernetes Engine when using Google Cloud SQL.
Create the secret for the credentials as documented [here](https://cloud.google.com/sql/docs/postgres/connect-kubernetes-engine) and configure the proxy as a sidecar.
Because `extraContainers` is a string that is passed through the `tpl` function, it is possible to create custom values and use them in the string.
```yaml
database:
vendor: postgres
hostname: '127.0.0.1'
port: 5432
database: postgres
username: myuser
password: mypassword
# Custom values for Google Cloud SQL
cloudsql:
project: my-project
region: europe-west1
instance: my-instance
extraContainers: |
- name: cloudsql-proxy
image: gcr.io/cloudsql-docker/gce-proxy:1.17
command:
- /cloud_sql_proxy
args:
- -instances={{ .Values.cloudsql.project }}:{{ .Values.cloudsql.region }}:{{ .Values.cloudsql.instance }}=tcp:5432
- -credential_file=/secrets/cloudsql/credentials.json
volumeMounts:
- name: cloudsql-creds
mountPath: /secrets/cloudsql
readOnly: true
extraVolumes: |
- name: cloudsql-creds
secret:
secretName: cloudsql-instance-credentials
```
### Changing the Context Path
By default, Keycloak-X is served under context `/auth`.
Trailing slash is removed from path. This can be changed to another context path like `/` as follows:
```yaml
http:
relativePath: '/'
```
Alternatively, you may supply it via CLI flag:
```console
--set-string http.relativePath=/
```
### Management Interface
Keycloak serves health and metrics endpoints on a separate management interface (port 9000).
By default, the management interface inherits its relative path from `http.relativePath` (e.g. `/auth`),
so endpoints are available at `/auth/health` and `/auth/metrics`.
To serve management endpoints at a different path, set `http.managementRelativePath`:
```yaml
http:
relativePath: "/auth"
managementRelativePath: "/"
```
This sets `KC_HTTP_MANAGEMENT_RELATIVE_PATH=/` so health and metrics are available at
`/health` and `/metrics` on port 9000. When left empty, the env var is not set and
Keycloak inherits the value from `http.relativePath`.
### Prometheus Metrics Support
#### Keycloak Metrics
Keycloak-X can expose metrics via `/auth/metrics`.
Metrics are enabled by default via:
```yaml
metrics:
enabled: true
```
Add a ServiceMonitor if using prometheus-operator:
```yaml
serviceMonitor:
# If `true`, a ServiceMonitor resource for the prometheus-operator is created
enabled: true
```
Checkout `values.yaml` for customizing the ServiceMonitor and for adding custom Prometheus rules.
Add annotations if you don't use prometheus-operator:
```yaml
service:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
```
#### Keycloak Metrics SPI
Optionally, it is possible to add [Keycloak Metrics SPI](https://github.com/aerogear/keycloak-metrics-spi) via init container.
Note that the `keycloak-metrics-spi.jar` needs to be added to the `/opt/keycloak/providers` directory.
A separate `ServiceMonitor` can be enabled to scrape metrics from the SPI:
```yaml
extraServiceMonitor:
# If `true`, an additional ServiceMonitor resource for the prometheus-operator is created
enabled: true
```
Checkout `values.yaml` for customizing this ServiceMonitor.
Note that the metrics endpoint is exposed on the HTTP port.
You may want to restrict access to it in your ingress controller configuration.
For ingress-nginx, this could be done as follows:
```yaml
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
location ~* /auth/realms/[^/]+/metrics {
return 403;
}
```
### Extra Kubernetes Manifests
It is possible to deploy extra Kubernetes resources (such as ConfigMaps, Secrets, Jobs, or any other Kubernetes objects) alongside the Keycloak chart by using the `extraManifests` value.
This feature supports Helm templating, allowing you to use chart helpers like `{{ include "keycloak.fullname" . }}` within your manifests.
```yaml
extraManifests:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "keycloak.fullname" . }}-extra-config
labels:
{{- include "keycloak.labels" . | nindent 8 }}
data:
custom-key: custom-value
- |
apiVersion: v1
kind: Secret
metadata:
name: {{ include "keycloak.fullname" . }}-extra-secret
type: Opaque
stringData:
my-secret-key: my-secret-value
```
## Why StatefulSet?
The headless service that governs the StatefulSet is used for DNS discovery via DNS_PING.
## Bad Gateway and Proxy Buffer Size in Nginx
A common issue with Keycloak and nginx is that the proxy buffer may be too small for what Keycloak is trying to send. This will result in a Bad Gateway (502) error. There are [many](https://github.com/kubernetes/ingress-nginx/issues/4637) [issues](https://stackoverflow.com/questions/56126864/why-do-i-get-502-when-trying-to-authenticate) around the internet about this. The solution is to increase the buffer size of nginx. This can be done by creating an annotation in the ingress specification:
```yaml
ingress:
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "128k"
```
## Upgrading
Notes for upgrading from previous Keycloak chart versions.
### From chart < 18.0.0
* Keycloak is updated to 18.0.0
* Added new `health.enabled` option.
Keycloak 18.0.0 allows to enable the health endpoint independently of the metrics endpoint via the `health-enabled` setting.
We reflect that via the new config option `health.enabled`.
Please read the additional notes about [Migrating to 18.0.0](https://www.keycloak.org/docs/latest/upgrading/index.html#migrating-to-18-0-0) in the Keycloak documentation.
@@ -0,0 +1,80 @@
image:
repository: docker.io/paasup/keycloak
tag: "26.7.1-bci15.7-hardened-20260807"
# keycloakx 기본값은 "/auth"(구 WildFly 기반 codecentric/keycloak 호환용). Keycloak
# 26(Quarkus) 네이티브 기본값은 "/"이며, /auth 를 그대로 두면 OIDC issuer/admin API
# 경로가 다른 앱들의 가정(경로 접미사 없음)과 어긋난다.
http:
relativePath: "/"
# 차트 기본값은 command/args 모두 빈 배열이다 — 지정하지 않으면 컨테이너가 인자 없는
# kc.sh(도움말 출력, exit 0)로 끝나 CrashLoopBackOff가 된다(실측 확인, dip-catalog#1).
command:
- "/opt/keycloak/bin/kc.sh"
- "start"
# path 를 exact "/" 로 두면 apisix 에서 루트만 매치되어 Keycloak 의 실제 엔드포인트
# (/realms/*, /admin/*, /resources/*)가 전부 404 가 난다 — airflow·superset·mlflow·
# lakekeeper 에서 이미 실측된 문제로 카탈로그 전체가 regex 방식으로 통일돼 있다
# (.claude/pitfalls.md). ingressClassName 도 반드시 명시한다 — 미지정 시 이 클러스터의
# apisix 가 인식하지 않아 CLASS: <none> 으로 뜨고 접근 자체가 불가능하다.
ingress:
enabled: true
ingressClassName: apisix
annotations:
cert-manager.io/cluster-issuer: "root-ca-issuer"
k8s.apisix.apache.org/use-regex: "true"
rules:
- host: keycloak.example.org
paths:
- path: /.*
pathType: ImplementationSpecific
tls:
- hosts:
- keycloak.example.org
secretName: keycloak-tls
proxy:
enabled: true
mode: forwarded
resources:
requests:
cpu: 100m
memory: 500Mi
limits:
cpu: 2000m
memory: 1500Mi
# 외부 postgresql 사용. vendor/hostname/port/database/username 값을 배포된 DB로 변경.
# 비밀번호는 password 평문 대신 existingSecret(kubernetes.io/basic-auth 시크릿의
# password 키, 기본 existingSecretKey)을 쓰는 것을 권장.
database:
vendor: postgres
hostname: keycloak-postgresql
port: 5432
database: keycloak
username: keycloak
existingSecret: keycloak-db
existingSecretKey: password
# KC_DB_SCHEMA: Keycloak Quarkus 정식 config 옵션(db-schema) — public 이 아닌 전용
# 스키마를 쓸 때 지정.
# KC_BOOTSTRAP_ADMIN_USERNAME/PASSWORD: Keycloak 25+ 의 admin 계정 부트스트랩
# 메커니즘(구버전 KEYCLOAK_ADMIN/KEYCLOAK_ADMIN_PASSWORD 대체). master realm이
# 완전히 비어있는 최초 부팅에만 동작 — 재설치 시 비밀번호가 갱신되지 않는 것이 정상.
# KC_HOSTNAME: 반드시 지정할 것. hostname-strict 가 기본 true 라 미지정 시
# "hostname is not configured; either configure hostname, or set hostname-strict
# to false" 로 기동이 실패한다(실측 확인).
extraEnv: |
- name: KC_HOSTNAME
value: keycloak.example.org
- name: KC_DB_SCHEMA
value: keycloak
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
value: ChangeMe1234! # 예시 값 — 배포 전 교체한다
- name: TZ
value: Asia/Seoul
@@ -0,0 +1,75 @@
***********************************************************************
* *
* Keycloak.X Helm Chart by codecentric AG *
* *
***********************************************************************
{{- if and .Values.httpRoute.enabled .Values.httpRoute.listenerSet.enabled }}
Keycloak was installed with a Gateway API HTTPRoute and ListenerSet.
The ListenerSet {{ include "keycloak.fullname" . }} attaches listeners to the Gateway "{{ .Values.httpRoute.listenerSet.parentRef.name }}"{{ with .Values.httpRoute.listenerSet.parentRef.namespace }} in namespace "{{ . }}"{{ end }}.
Ensure the Gateway is configured to allow ListenerSet attachment from namespace {{ .Release.Namespace }}.
{{- else if .Values.httpRoute.enabled }}
Keycloak was installed with a Gateway API HTTPRoute attached to:
{{- range .Values.httpRoute.parentRefs }}
- Gateway: {{ .name }}{{ with .sectionName }}, section: {{ . }}{{ end }}
{{- end }}
{{- else if .Values.ingress.enabled }}
Keycloak was installed with an Ingress and an be reached at the following URL(s):
{{ range $unused, $rule := .Values.ingress.rules }}
{{- range $rule.paths }}
- http{{ if $.Values.ingress.tls }}s{{ end }}://{{ tpl $rule.host $ }}{{ .path }}
{{- end }}
{{- end }}
{{- else if eq "NodePort" .Values.service.type }}
Keycloak was installed with a Service of type NodePort.
{{ if .Values.service.httpNodePort }}
Get its HTTP URL with the following commands:
export NODE_PORT=$(kubectl get --namespace {{ include "keycloak.namespace" . }} service {{ include "keycloak.fullname" . }}-http --template='{{"{{ range .spec.ports }}{{ if eq .name \"http\" }}{{ .nodePort }}{{ end }}{{ end }}"}}')
export NODE_IP=$(kubectl get nodes --namespace {{ include "keycloak.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "http://$NODE_IP:$NODE_PORT"
{{- end }}
{{ if .Values.service.httpsNodePort }}
Get its HTTPS URL with the following commands:
export NODE_PORT=$(kubectl get --namespace {{ include "keycloak.namespace" . }} service {{ include "keycloak.fullname" . }}-http --template='{{"{{ range .spec.ports }}{{ if eq .name \"https\" }}{{ .nodePort }}{{ end }}{{ end }}"}}')
export NODE_IP=$(kubectl get nodes --namespace {{ include "keycloak.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo "http://$NODE_IP:$NODE_PORT"
{{- end }}
{{- else if eq "LoadBalancer" .Values.service.type }}
Keycloak was installed with a Service of type LoadBalancer
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ include "keycloak.namespace" . }} service -w {{ include "keycloak.fullname" . }}'
Get its HTTP URL with the following commands:
export SERVICE_IP=$(kubectl get service --namespace {{ include "keycloak.namespace" . }} {{ include "keycloak.fullname" . }}-http --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo "http://$SERVICE_IP:{{ .Values.service.httpPort }}"
Get its HTTPS URL with the following commands:
export SERVICE_IP=$(kubectl get service --namespace {{ include "keycloak.namespace" . }} {{ include "keycloak.fullname" . }}-http --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo "http://$SERVICE_IP:{{ .Values.service.httpsPort }}"
{{- else if eq "ClusterIP" .Values.service.type }}
Keycloak was installed with a Service of type ClusterIP
Create a port-forwarding with the following commands:
export POD_NAME=$(kubectl get pods --namespace {{ include "keycloak.namespace" . }} -l "app.kubernetes.io/name={{ include "keycloak.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o name)
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ include "keycloak.namespace" . }} port-forward "$POD_NAME" 8080
{{- end }}
@@ -0,0 +1,97 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "keycloak.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "keycloak.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "keycloak.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "keycloak.labels" -}}
helm.sh/chart: {{ include "keycloak.chart" . }}
{{ include "keycloak.selectorLabels" . }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | toString | trunc 63 | trimSuffix "-" | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- range $key, $value := .Values.commonLabels }}
{{ printf "%s: %s" $key (tpl $value $ | quote) }}
{{- end }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "keycloak.selectorLabels" -}}
app.kubernetes.io/name: {{ include "keycloak.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "keycloak.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "keycloak.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{/*
Create the service DNS name.
*/}}
{{- define "keycloak.serviceDnsName" -}}
{{ include "keycloak.fullname" . }}-headless.{{ include "keycloak.namespace" . }}.svc.{{ .Values.clusterDomain }}
{{- end }}
{{/*
Namespace for all resources. Callers can override via .Values.namespaceOverride.
*/}}
{{- define "keycloak.namespace" -}}
{{- default .Release.Namespace .Values.namespaceOverride -}}
{{- end }}
{{- define "keycloak.databasePasswordEnv" -}}
{{- if or .Values.database.password .Values.database.existingSecret -}}
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.database.existingSecret | default (printf "%s-database" (include "keycloak.fullname" . ))}}
key: {{ .Values.database.existingSecretKey | default "password" }}
{{- end }}
{{- end -}}
{{/*
Renders a complete tree, even values that contains template.
*/}}
{{- define "keycloak.render" -}}
{{- if typeIs "string" .value }}
{{- tpl .value .context }}
{{ else }}
{{- tpl (.value | toYaml) .context }}
{{- end }}
{{- end -}}
@@ -0,0 +1,14 @@
{{- if .Values.startupScripts }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "keycloak.fullname" . }}-startup
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.startupScripts }}
{{ $key }}: |
{{- tpl $value $ | nindent 4 }}
{{- end }}
{{- end -}}
@@ -0,0 +1,12 @@
{{- if and .Values.database.password (not .Values.database.existingSecret) -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "keycloak.fullname" $ }}-database
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" $ | nindent 4 }}
type: Opaque
stringData:
password: {{ .Values.database.password | quote }}
{{- end }}
@@ -0,0 +1,4 @@
{{- range .Values.extraManifests }}
---
{{ include "keycloak.render" (dict "value" . "context" $) }}
{{- end }}
@@ -0,0 +1,23 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.autoscaling.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: {{ include "keycloak.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- toYaml .Values.autoscaling.metrics | nindent 4 }}
behavior:
{{- toYaml .Values.autoscaling.behavior | nindent 4 }}
{{- end }}
@@ -0,0 +1,118 @@
{{- $httpRoute := .Values.httpRoute -}}
{{- if $httpRoute.enabled -}}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $httpRoute.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- with $httpRoute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- if $httpRoute.listenerSet.enabled }}
- kind: ListenerSet
name: {{ include "keycloak.fullname" $ }}
namespace: {{ $.Release.Namespace }}
{{- else }}
{{- with $httpRoute.parentRefs }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- if $httpRoute.listenerSet.enabled }}
{{- $hostnames := list }}
{{- range $httpRoute.listenerSet.listeners }}
{{- if .hostname }}
{{- $hostnames = append $hostnames .hostname }}
{{- end }}
{{- end }}
{{- with $hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- else }}
{{- with $httpRoute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
rules:
{{- range $httpRoute.rules }}
{{- with .matches }}
- matches:
{{- range . }}
{{- if .path }}
- path:
type: {{ .path.type }}
value: {{ tpl .path.value $ }}
{{- end }}
{{- else }}
{{ . | toYaml | nindent 8 }}
{{- end }}
{{- end }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 8 }}
{{- end }}
backendRefs:
- name: {{ include "keycloak.fullname" $ }}-http
port: {{ $httpRoute.servicePort }}
{{- end }}
{{- end }}
---
{{- if $httpRoute.console.enabled }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "keycloak.fullname" . }}-console
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $httpRoute.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- range $key, $value := $httpRoute.console.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- with $httpRoute.console.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRefs:
{{- with pluck "parentRefs" $httpRoute.console $httpRoute | first }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with pluck "hostnames" $httpRoute.console $httpRoute | first }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
{{- range $httpRoute.console.rules }}
{{- with .matches }}
- matches:
{{- range . }}
{{- if .path }}
- path:
type: {{ .path.type }}
value: {{ tpl .path.value $ }}
{{- end }}
{{- else }}
{{ . | toYaml | nindent 8 }}
{{- end }}
{{- end }}
{{- with .filters }}
filters:
{{- toYaml . | nindent 8 }}
{{- end }}
backendRefs:
- name: {{ include "keycloak.fullname" $ }}-http
port: {{ $httpRoute.servicePort }}
{{- end }}
{{- end }}
@@ -0,0 +1,116 @@
{{- $ingress := .Values.ingress -}}
{{- if $ingress.enabled -}}
{{- $apiVersion := "networking.k8s.io/v1" -}}
{{- $fullName := ( include "keycloak.fullname" . ) -}}
apiVersion: {{ $apiVersion }}
kind: Ingress
metadata:
name: {{ $fullName }}
namespace: {{ include "keycloak.namespace" . }}
{{- with $ingress.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $ingress.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
{{- if $ingress.ingressClassName }}
ingressClassName: {{ $ingress.ingressClassName }}
{{- end }}
{{- if $ingress.tls }}
tls:
{{- range $ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ tpl . $ | quote }}
{{- end }}
{{- with .secretName }}
secretName: {{ tpl . $ }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.rules }}
- host: {{ tpl .host $ | quote }}
http:
paths:
{{- range .paths }}
- path: {{ tpl .path $ | quote }}
pathType: {{ .pathType }}
backend:
service:
name: {{ default (printf "%s-http" $fullName) (.serviceName) }}
port:
name: {{ default ($ingress.servicePort) (.servicePort) }}
{{- end }}
{{- end }}
{{- if $ingress.console.enabled }}
---
apiVersion: {{ $apiVersion }}
kind: Ingress
metadata:
name: {{ $fullName }}-console
namespace: {{ include "keycloak.namespace" . }}
{{- with $ingress.console.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $ingress.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- range $key, $value := $ingress.console.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
{{- if $ingress.console.ingressClassName }}
ingressClassName: {{ $ingress.console.ingressClassName }}
{{- end }}
{{- if $ingress.console.tls }}
tls:
{{- range $ingress.console.tls }}
- hosts:
{{- range .hosts }}
- {{ tpl . $ | quote }}
{{- end }}
{{- with .secretName }}
secretName: {{ tpl . $ }}
{{- end }}
{{- end }}
{{ else if $ingress.tls }}
tls:
{{- range $ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ tpl . $ | quote }}
{{- end }}
{{- with .secretName }}
secretName: {{ tpl . $ }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.console.rules }}
- host: {{ tpl .host $ | quote }}
http:
paths:
{{- range .paths }}
- path: {{ tpl .path $ | quote }}
pathType: {{ .pathType }}
backend:
service:
name: {{ default (printf "%s-http" $fullName) (.serviceName) }}
port:
name: {{ default ($ingress.servicePort) (.servicePort) }}
{{- end }}
{{- end }}
{{- end -}}
{{- end -}}
@@ -0,0 +1,29 @@
{{- $httpRoute := .Values.httpRoute -}}
{{- $listenerSet := $httpRoute.listenerSet -}}
{{- if and $httpRoute.enabled $listenerSet.enabled -}}
apiVersion: gateway.networking.k8s.io/v1
kind: ListenerSet
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $httpRoute.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- range $key, $value := $listenerSet.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- with $listenerSet.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
parentRef:
name: {{ $listenerSet.parentRef.name }}
{{- with $listenerSet.parentRef.namespace }}
namespace: {{ . }}
{{- end }}
listeners:
{{- toYaml $listenerSet.listeners | nindent 4 }}
{{- end }}
@@ -0,0 +1,52 @@
{{- if .Values.networkPolicy.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "keycloak.fullname" . | quote }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.networkPolicy.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
policyTypes:
- Ingress
{{- if .Values.networkPolicy.egress }}
- Egress
{{- end}}
podSelector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 6 }}
ingress:
{{- with .Values.networkPolicy.extraFrom }}
- from:
{{- toYaml . | nindent 8 }}
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 8443
{{ range $.Values.extraPorts }}
- protocol: {{ default "TCP" .protocol }}
port: {{ .containerPort }}
{{- end }}
{{- end }}
- from:
- podSelector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 14 }}
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 8443
{{ range .Values.extraPorts }}
- protocol: {{ default "TCP" .protocol }}
port: {{ .containerPort }}
{{- end }}
{{- if .Values.networkPolicy.egress }}
egress:
{{- .Values.networkPolicy.egress | toYaml | nindent 4 }}
{{- end }}
{{- end }}
@@ -0,0 +1,14 @@
{{- if .Values.podDisruptionBudget -}}
apiVersion: {{ ternary "policy/v1" "policy/v1beta1" (semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version) }}
kind: PodDisruptionBudget
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 6 }}
{{- toYaml .Values.podDisruptionBudget | nindent 2 }}
{{- end -}}
@@ -0,0 +1,29 @@
{{- with .Values.prometheusRule -}}
{{- if .enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ include "keycloak.fullname" $ }}
{{- with .namespace }}
namespace: {{ . }}
{{- else }}
namespace: {{ include "keycloak.namespace" $ }}
{{- end }}
{{- with .annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" $ | nindent 4 }}
{{- range $key, $value := .labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
groups:
- name: {{ include "keycloak.fullname" $ }}
rules:
{{- toYaml .rules | nindent 8 }}
{{- end }}
{{- end -}}
@@ -0,0 +1,27 @@
{{- if and .Values.rbac.create .Values.rbac.rules }}
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
rules:
{{- toYaml .Values.rbac.rules | nindent 2 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "keycloak.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "keycloak.serviceAccountName" . }}
namespace: {{ include "keycloak.namespace" . | quote }}
{{- end }}
@@ -0,0 +1,39 @@
{{- $route := .Values.route -}}
{{- if $route.enabled -}}
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
{{- with $route.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := $route.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
{{- if $route.host }}
host: {{ tpl $route.host $ | quote }}
{{- end }}
path: {{ $route.path }}
port:
{{- if or (not $route.tls.enabled) (eq $route.tls.termination "edge") }}
targetPort: http
{{- else}}
targetPort: https
{{- end}}
to:
kind: Service
name: {{ include "keycloak.fullname" $ }}-http
weight: 100
{{- if $route.tls.enabled }}
tls:
insecureEdgeTerminationPolicy: {{ $route.tls.insecureEdgeTerminationPolicy }}
termination: {{ $route.tls.termination }}
{{- end }}
{{- end -}}
@@ -0,0 +1,30 @@
{{- range $nameSuffix, $values := .Values.secrets }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "keycloak.fullname" $ }}-{{ $nameSuffix }}
namespace: {{ include "keycloak.namespace" $ }}
{{- with $values.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" $ | nindent 4 }}
{{- range $key, $value := $values.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
type: {{ default "Opaque" $values.type }}
{{- with $values.data }}
data:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with $values.stringData }}
stringData:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 2 }}
{{- end }}
{{- end }}
{{- end -}}
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "keycloak.fullname" . }}-headless
namespace: {{ include "keycloak.namespace" . }}
{{- with .Values.serviceHeadless.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.serviceHeadless.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
app.kubernetes.io/component: headless
spec:
type: ClusterIP
clusterIP: None
ports:
- name: http
port: {{ .Values.service.httpPort }}
targetPort: http
protocol: TCP
{{- with .Values.serviceHeadless.extraPorts }}
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
{{- include "keycloak.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,65 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "keycloak.fullname" . }}-http
namespace: {{ include "keycloak.namespace" . }}
{{- with .Values.service.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.service.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
app.kubernetes.io/component: http
spec:
type: {{ .Values.service.type }}
{{- if and (eq "LoadBalancer" .Values.service.type) .Values.service.loadBalancerIP }}
loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}
{{- if and (eq "LoadBalancer" .Values.service.type) .Values.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges:
{{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
{{- end }}
{{- if or (eq "LoadBalancer" .Values.service.type) (eq "NodePort" .Values.service.type) }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
{{- end }}
{{- if .Values.service.internalTrafficPolicy }}
internalTrafficPolicy: {{ .Values.service.internalTrafficPolicy }}
{{- end }}
{{- if .Values.service.sessionAffinity }}
sessionAffinity: {{ .Values.service.sessionAffinity }}
{{- with .Values.service.sessionAffinityConfig }}
sessionAffinityConfig:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
ports:
- name: '{{ .Values.http.internalPort }}'
port: 9000
protocol: TCP
targetPort: '{{ .Values.http.internalPort }}'
- name: http
port: {{ .Values.service.httpPort }}
targetPort: http
{{- if and (or (eq "NodePort" .Values.service.type) (eq "LoadBalancer" .Values.service.type) ) .Values.service.httpNodePort }}
nodePort: {{ .Values.service.httpNodePort }}
{{- end }}
protocol: TCP
{{- if .Values.service.httpsPort }}
- name: https
port: {{ .Values.service.httpsPort }}
targetPort: https
{{- if and (or (eq "NodePort" .Values.service.type) (eq "LoadBalancer" .Values.service.type) ) .Values.service.httpsNodePort }}
nodePort: {{ .Values.service.httpsNodePort }}
{{- end }}
protocol: TCP
{{- end }}
{{- with .Values.service.extraPorts }}
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
{{- include "keycloak.selectorLabels" . | nindent 4 }}
@@ -0,0 +1,47 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "keycloak.serviceAccountName" . }}
namespace: {{ include "keycloak.namespace" . }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.serviceAccount.labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- with .Values.serviceAccount.imagePullSecrets }}
imagePullSecrets: {{ toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- if .Values.serviceAccount.allowReadPods }}
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jgroups-kubeping-pod-reader-{{ include "keycloak.namespace" . }}
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jgroups-kubeping-api-access-{{ include "keycloak.namespace" . }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jgroups-kubeping-pod-reader-{{ include "keycloak.namespace" . }}
subjects:
- kind: ServiceAccount
name: {{ include "keycloak.serviceAccountName" . }}
namespace: {{ include "keycloak.namespace" . }}
{{- end }}
{{- end }}
@@ -0,0 +1,54 @@
{{- range $key, $serviceMonitor := dict "keycloakx" .Values.serviceMonitor "extra" .Values.extraServiceMonitor }}
{{- with $serviceMonitor }}
{{- if .enabled }}
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "keycloak.fullname" $ }}-{{ $key }}
{{- with .namespace }}
namespace: {{ . }}
{{- else }}
namespace: {{ include "keycloak.namespace" $ }}
{{- end }}
{{- with .annotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" $ | nindent 4 }}
{{- range $key, $value := .labels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
{{- with .namespaceSelector }}
namespaceSelector:
{{- toYaml . | nindent 4 }}
{{- end }}
selector:
matchLabels:
{{- include "keycloak.selectorLabels" $ | nindent 6 }}
app.kubernetes.io/component: http
endpoints:
- port: {{ tpl .port $ | quote }}
path: {{ tpl .path $ | quote }}
scheme: {{ coalesce .scheme $.Values.http.internalScheme | lower }}
interval: {{ .interval }}
scrapeTimeout: {{ .scrapeTimeout }}
{{- with .relabelings }}
relabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .metricRelabelings }}
metricRelabelings:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .tlsConfig }}
tlsConfig:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -0,0 +1,253 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "keycloak.fullname" . }}
namespace: {{ include "keycloak.namespace" . }}
{{- with .Values.statefulsetAnnotations }}
annotations:
{{- range $key, $value := . }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
{{- end }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
{{- range $key, $value := .Values.statefulsetLabels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 6 }}
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicas }}
{{- end }}
serviceName: {{ include "keycloak.fullname" . }}-headless
podManagementPolicy: {{ .Values.podManagementPolicy }}
updateStrategy:
type: {{ .Values.updateStrategy }}
{{- if not (eq (.Values.revisionHistoryLimit | toString) "") }}
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
{{- end }}
template:
metadata:
annotations:
checksum/config-startup: {{ include (print .Template.BasePath "/configmap-startup.yaml") . | sha256sum }}
checksum/secrets: {{ tpl (toYaml .Values.secrets) . | sha256sum }}
{{- range $key, $value := .Values.podAnnotations }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 8 }}
{{- end }}
labels:
{{- include "keycloak.selectorLabels" . | nindent 8 }}
{{- range $key, $value := .Values.commonLabels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 8 }}
{{- end }}
{{- range $key, $value := .Values.podLabels }}
{{- printf "%s: %s" $key (tpl $value $ | quote) | nindent 8 }}
{{- end }}
spec:
{{- if not .Values.skipInitContainers }}
{{- if or .Values.dbchecker.enabled .Values.extraInitContainers }}
initContainers:
{{- if and .Values.dbchecker.enabled }}
- name: dbchecker
image: "{{ .Values.dbchecker.image.repository }}{{- if (.Values.dbchecker.image.digest) -}}@{{ .Values.dbchecker.image.digest }}{{- else -}}:{{ .Values.dbchecker.image.tag }} {{- end }}"
imagePullPolicy: {{ .Values.dbchecker.image.pullPolicy }}
securityContext:
{{- toYaml .Values.dbchecker.securityContext | nindent 12 }}
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
sleep 2;
done;
echo 'Database OK ✓'
resources:
{{- toYaml .Values.dbchecker.resources | nindent 12 }}
{{- end }}
{{- with .Values.extraInitContainers }}
{{- tpl . $ | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
containers:
- name: keycloak
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}{{- if (.Values.image.digest) -}}@{{ .Values.image.digest }}{{- else -}}:{{ .Values.image.tag | default .Chart.AppVersion }} {{- end }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- if .Values.command }}
command:
{{- toYaml .Values.command | nindent 12 }}
{{- end }}
{{- if .Values.args }}
args:
{{- toYaml .Values.args | nindent 12 }}
{{- end }}
{{- with .Values.lifecycleHooks }}
lifecycle:
{{- tpl . $ | nindent 12 }}
{{- end }}
env:
{{- if and (.Values.http.relativePath) (eq .Values.http.relativePath "/") }}
- name: KC_HTTP_RELATIVE_PATH
value: {{ tpl .Values.http.relativePath $ }}
{{ else }}
- name: KC_HTTP_RELATIVE_PATH
value: {{ tpl .Values.http.relativePath $ | trimSuffix "/" }}
{{- end }}
{{- if .Values.http.managementRelativePath }}
- name: KC_HTTP_MANAGEMENT_RELATIVE_PATH
{{- if eq .Values.http.managementRelativePath "/" }}
value: {{ tpl .Values.http.managementRelativePath $ }}
{{- else }}
value: {{ tpl .Values.http.managementRelativePath $ | trimSuffix "/" }}
{{- end }}
{{- end }}
{{- if eq .Values.cache.stack "default" }}
- name: KC_CACHE
value: "ispn"
- name: KC_CACHE_STACK
value: "jdbc-ping"
{{- end }}
{{- if .Values.proxy.enabled }}
- name: KC_PROXY_HEADERS
value: {{ .Values.proxy.mode }}
{{- end }}
{{- if .Values.proxy.http.enabled }}
- name: KC_HTTP_ENABLED
value: "true"
{{- end }}
{{- if .Values.database.vendor }}
- name: KC_DB
value: {{ .Values.database.vendor }}
{{- end }}
{{- if .Values.database.hostname }}
- name: KC_DB_URL_HOST
value: {{ .Values.database.hostname }}
{{- end }}
{{- if .Values.database.port }}
- name: KC_DB_URL_PORT
value: {{ .Values.database.port | quote }}
{{- 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 }}
{{- if or .Values.database.password .Values.database.existingSecret -}}
{{- include "keycloak.databasePasswordEnv" . | nindent 12 }}
{{- end }}
{{- if .Values.metrics.enabled }}
- name: KC_METRICS_ENABLED
value: "true"
{{- end }}
{{- if .Values.health.enabled }}
- name: KC_HEALTH_ENABLED
value: "true"
{{- end }}
{{- with .Values.extraEnv }}
{{- tpl . $ | nindent 12 }}
{{- end }}
envFrom:
{{- with .Values.extraEnvFrom }}
{{- tpl . $ | nindent 12 }}
{{- end }}
ports:
- name: http
containerPort: 8080
protocol: TCP
- name: '{{ .Values.http.internalPort }}'
containerPort: 9000
protocol: TCP
{{- if .Values.service.httpsPort }}
- name: https
containerPort: 8443
protocol: TCP
{{- end }}
{{- with .Values.extraPorts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.health.enabled }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- tpl . $ | nindent 12 }}
{{- end }}
{{- if .Values.metrics.enabled }}
{{- with .Values.readinessProbe }}
readinessProbe:
{{- tpl . $ | nindent 12 }}
{{- end }}
{{- end}}
{{- with .Values.startupProbe }}
startupProbe:
{{- tpl . $ | nindent 12 }}
{{- end }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
{{- with .Values.extraVolumeMounts }}
{{- tpl . $ | nindent 12 }}
{{- end }}
{{- with .Values.extraContainers }}
{{- tpl . $ | nindent 8 }}
{{- end }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "keycloak.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
{{- with .Values.hostAliases }}
hostAliases:
{{- toYaml . | nindent 8 }}
{{- end }}
enableServiceLinks: {{ .Values.enableServiceLinks }}
restartPolicy: {{ .Values.restartPolicy }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- tpl . $ | nindent 8 }}
{{- end }}
{{- with .Values.topologySpreadConstraints }}
topologySpreadConstraints:
{{- tpl . $ | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.priorityClassName }}
priorityClassName: {{ . }}
{{- end }}
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
volumes:
{{- with .Values.startupScripts }}
- name: startup
configMap:
name: {{ include "keycloak.fullname" $ }}-startup
defaultMode: 0555
items:
{{- range $key, $value := . }}
- key: {{ $key }}
path: {{ $key }}
{{- end }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- tpl . $ | nindent 8 }}
{{- end }}
{{- with .Values.volumeClaimTemplates }}
volumeClaimTemplates:
{{- tpl . $ | nindent 4 }}
{{- end }}
@@ -0,0 +1,67 @@
{{- if .Values.test.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "keycloak.fullname" . }}-test
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
annotations:
helm.sh/hook: test
helm.sh/hook-delete-policy: hook-succeeded
data:
test.sh: |
#!/usr/bin/env bash
set -eu
if ! python3 -c 'import selenium' &> /dev/null; then
echo 'Installing selenium module...'
python3 -m venv /tmp/test-venv
/tmp/test-venv/bin/pip install -q selenium
exec /tmp/test-venv/bin/python "$(dirname "$0")/test.py"
fi
python3 "$(dirname "$0")/test.py"
test.py: |
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support import expected_conditions
print('Creating chrome driver in headless mode')
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=chrome_options)
base_url = 'http://{{ include "keycloak.fullname" . }}-http{{ if ne 80 (int .Values.service.httpPort) }}:{{ .Values.service.httpPort }}{{ end }}'
print('Opening Keycloak...')
driver.get('{0}{{ tpl .Values.http.relativePath . | trimSuffix "/" }}/admin/'.format(base_url))
username = os.environ['KEYCLOAK_USER']
password = os.environ['KEYCLOAK_PASSWORD']
username_input = WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "username")))
password_input = WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "password")))
login_button = WebDriverWait(driver, 30).until(expected_conditions.presence_of_element_located((By.ID, "kc-login")))
print('Entering username...')
username_input.send_keys(username)
print('Entering password...')
password_input.send_keys(password)
print('Clicking login button...')
login_button.click()
WebDriverWait(driver, 30).until(lambda driver: '{{ tpl .Values.http.relativePath . | trimSuffix "/" }}/admin/master/console/' in driver.current_url)
print('Admin console visible. Login successful.')
driver.quit()
{{- end }}
@@ -0,0 +1,45 @@
{{- if .Values.test.enabled }}
apiVersion: v1
kind: Pod
metadata:
name: {{ include "keycloak.fullname" . }}-test
namespace: {{ include "keycloak.namespace" . }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
app.kubernetes.io/component: test
annotations:
helm.sh/hook: test
helm.sh/hook-delete-policy: {{ .Values.test.deletionPolicy }}
spec:
securityContext:
{{- toYaml .Values.test.podSecurityContext | nindent 4 }}
containers:
- name: keycloak-test
image: "{{ .Values.test.image.repository }}{{- if (.Values.test.image.digest) -}}@{{ .Values.test.image.digest }}{{- else -}}:{{ .Values.test.image.tag }} {{- end }}"
imagePullPolicy: {{ .Values.test.image.pullPolicy }}
securityContext:
{{- toYaml .Values.test.securityContext | nindent 8 }}
command:
- bash
args:
- /tests/test.sh
env:
- name: KEYCLOAK_USER
valueFrom:
secretKeyRef:
name: {{ include "keycloak.fullname" . }}-admin-creds
key: user
- name: KEYCLOAK_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "keycloak.fullname" . }}-admin-creds
key: password
volumeMounts:
- name: tests
mountPath: /tests
volumes:
- name: tests
configMap:
name: {{ include "keycloak.fullname" . }}-test
restartPolicy: Never
{{- end }}
@@ -0,0 +1,635 @@
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"required": [
"image"
],
"definitions": {
"httpRoute": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"enabled": {
"type": "boolean"
},
"labels": {
"type": "object"
},
"parentRefs": {
"type": "array",
"items": {
"properties": {
"name": {
"type": "string"
},
"sectionName": {
"type": "string"
}
}
}
},
"hostnames": {
"type": "array",
"items": {
"type": "string"
}
},
"rules": {
"type": "array",
"items": {
"$comment": "don't allow additionalProperties to make sure backendRefs isn't set by the user",
"additionalProperties": false,
"properties": {
"matches": {
"type": "array",
"items": {
"$comment": "don't allow additionalProperties, only path matcher supported",
"additionalProperties": false,
"properties": {
"path": {
"type": "object"
}
}
}
},
"filters": {
"type": "array"
}
}
}
}
}
},
"image": {
"type": "object",
"required": [
"repository",
"tag"
],
"properties": {
"pullPolicy": {
"type": "string",
"pattern": "^(Always|Never|IfNotPresent)$"
},
"repository": {
"type": "string"
},
"tag": {
"type": ["string", "integer"]
},
"digest": {
"type": "string"
}
}
},
"imagePullSecrets": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
}
},
"properties": {
"affinity": {
"type": "string"
},
"args": {
"type": "array"
},
"clusterDomain": {
"type": "string"
},
"command": {
"type": "array"
},
"commonLabels": {
"type": "object"
},
"enableServiceLinks": {
"type": "boolean"
},
"extraContainers": {
"type": "string"
},
"extraEnv": {
"type": "string"
},
"extraEnvFrom": {
"type": "string"
},
"extraInitContainers": {
"type": "string"
},
"extraPorts": {
"type": "array"
},
"extraVolumeMounts": {
"type": "string"
},
"extraVolumes": {
"type": "string"
},
"volumeClaimTemplates": {
"type": "string"
},
"fullnameOverride": {
"type": "string"
},
"hostAliases": {
"type": "array"
},
"http": {
"relativePath": "string",
"managementRelativePath": "string",
"internalPort": "string",
"internalScheme": "string"
},
"httpRoute": {
"allOf": [
{ "$ref": "#/definitions/httpRoute" },
{
"type": "object",
"properties": {
"servicePort": {
"type": "integer"
},
"console": {
"$ref": "#/definitions/httpRoute"
},
"listenerSet": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"annotations": {
"type": "object"
},
"labels": {
"type": "object"
},
"parentRef": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"namespace": {
"type": "string"
}
}
},
"listeners": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
}
]
},
"image": {
"$ref": "#/definitions/image"
},
"imagePullSecrets": {
"$ref": "#/definitions/imagePullSecrets"
},
"ingress": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"enabled": {
"type": "boolean"
},
"labels": {
"type": "object"
},
"rules": {
"type": "array",
"items": {
"type": "object",
"properties": {
"host": {
"type": "string"
},
"paths": {
"type": "array",
"items": {
"type": "object",
"properties": {
"path": {
"type": "string"
},
"pathType": {
"type": "string"
}
}
}
}
}
}
},
"servicePort": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"tls": {
"type": "array",
"items": {
"type": "object",
"properties": {
"hosts": {
"type": "array",
"items": {
"items": {
"type": "string"
}
},
"secretName": {
"type": "string"
}
}
}
}
}
},
"lifecycleHooks": {
"type": "string"
},
"livenessProbe": {
"type": "string"
},
"nameOverride": {
"type": "string"
},
"namespaceOverride": {
"type": "string"
},
"nodeSelector": {
"type": "object"
},
"dbchecker": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"image": {
"$ref": "#/definitions/image"
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
},
"requests": {
"type": "object",
"properties": {
"cpu": {
"type": "string"
},
"memory": {
"type": "string"
}
}
}
}
},
"securityContext": {
"type": "object"
}
}
},
"podAnnotations": {
"type": "object"
},
"podDisruptionBudget": {
"type": "object"
},
"podLabels": {
"type": "object"
},
"podManagementPolicy": {
"type": "string"
},
"updateStrategy": {
"type": "string"
},
"podSecurityContext": {
"type": "object"
},
"cache": {
"type": "object",
"properties": {
"stack": {
"type": "string"
}
}
},
"proxy": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"mode": {
"type": "string"
}
}
},
"metrics": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"health": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"database": {
"type": "object",
"properties": {
"vendor": {
"type": "string"
},
"hostname": {
"type": "string"
},
"port": {
"type": "integer"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"database": {
"type": "string"
},
"existingSecret": {
"type": "string"
}
}
},
"priorityClassName": {
"type": "string"
},
"prometheusRule": {
"type": "object"
},
"serviceMonitor": {
"type": "object"
},
"extraServiceMonitor": {
"type": "object"
},
"readinessProbe": {
"type": "string"
},
"replicas": {
"type": "integer"
},
"resources": {
"type": "object"
},
"restartPolicy": {
"type": "string"
},
"route": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"enabled": {
"type": "boolean"
},
"host": {
"type": "string"
},
"labels": {
"type": "object"
},
"path": {
"type": "string"
},
"tls": {
"type": "object"
}
}
},
"secrets": {
"type": "object"
},
"securityContext": {
"type": "object"
},
"service": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"extraPorts": {
"type": "array"
},
"loadBalancerSourceRanges": {
"type": "array"
},
"httpNodePort": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"httpPort": {
"type": "integer"
},
"httpsNodePort": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"httpsPort": {
"type": "integer"
},
"labels": {
"type": "object"
},
"nodePort": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
]
},
"type": {
"type": "string"
},
"loadBalancerIP": {
"type": "string"
},
"internalTrafficPolicy": {
"type": "string"
},
"sessionAffinity": {
"type": "string"
},
"sessionAffinityConfig": {
"type": "object"
}
}
},
"serviceHeadless": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"extraPorts": {
"type": "array"
},
"labels": {
"type": "object"
}
}
},
"serviceAccount": {
"type": "object",
"properties": {
"annotations": {
"type": "object"
},
"create": {
"type": "boolean"
},
"allowReadPods": {
"type": "boolean"
},
"imagePullSecrets": {
"$ref": "#/definitions/imagePullSecrets"
},
"labels": {
"type": "object"
},
"name": {
"type": "string"
},
"automountServiceAccountToken": {
"type": "boolean"
}
}
},
"rbac": {
"type": "object",
"properties": {
"create": {
"type": "boolean"
},
"rules": {
"type": "array"
}
}
},
"statefulsetAnnotations": {
"type": "object"
},
"statefulsetLabels": {
"type": "object"
},
"terminationGracePeriodSeconds": {
"type": "integer"
},
"autoscaling": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"labels": {
"type": "object"
},
"minReplicas": {
"type": "integer"
},
"maxReplicas": {
"type": "integer"
},
"metrics": {
"type": "array"
},
"behavior": {
"type": "object"
}
}
},
"test": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"image": {
"$ref": "#/definitions/image"
},
"podSecurityContext": {
"type": "object"
},
"securityContext": {
"type": "object"
}
}
},
"tolerations": {
"type": "array"
}
}
}
}
+691
View File
@@ -0,0 +1,691 @@
# Optionally override the fully qualified name
fullnameOverride: ""
# Optionally override the name
nameOverride: ""
# Optionally override the namespace for all resources. Useful for umbrella charts that
# deploy multiple aliased keycloak instances each into their own namespace.
namespaceOverride: ""
# The number of replicas to create (has no effect if autoscaling enabled)
replicas: 1
# Additional labels applied to every resource in this chart, and on the StatefulSet's pods
commonLabels: {}
image:
# The Keycloak image repository
repository: quay.io/keycloak/keycloak
# Overrides the Keycloak image tag whose default is the chart appVersion
tag: "26.7.2"
# Overrides the Keycloak image tag with a specific digest
digest: ""
# The Keycloak image pull policy
pullPolicy: IfNotPresent
# Image pull secrets for the Pod
imagePullSecrets: []
# - name: myRegistrKeySecretName
# Mapping between IPs and hostnames that will be injected as entries in the Pod's hosts files
hostAliases: []
# - ip: "1.2.3.4"
# hostnames:
# - "my.host.com"
# 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
# StatefulSet's revision history limit (number of old ReplicaSets to retain). Defaults to 10 if not set
revisionHistoryLimit: ""
# Pod restart policy. One of `Always`, `OnFailure`, or `Never`
restartPolicy: Always
serviceAccount:
# Specifies whether a ServiceAccount should be created
create: true
# Specifies whether the ServiceAccount can get and list pods
allowReadPods: false
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# Additional annotations for the ServiceAccount
annotations: {}
# Additional labels for the ServiceAccount
labels: {}
# Image pull secrets that are attached to the ServiceAccount
imagePullSecrets: []
# Automount API credentials for the Service Account
automountServiceAccountToken: true
rbac:
create: false
rules: []
# RBAC rules for KUBE_PING
# - apiGroups:
# - ""
# resources:
# - pods
# 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: |
# postStart:
# exec:
# command:
# - /bin/sh
# - -c
# - ls
# 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: []
# Additional environment variables for Keycloak
extraEnv: ""
# - 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:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 10 }}
matchExpressions:
- key: app.kubernetes.io/component
operator: NotIn
values:
- test
topologyKey: kubernetes.io/hostname
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
{{- include "keycloak.selectorLabels" . | nindent 12 }}
matchExpressions:
- key: app.kubernetes.io/component
operator: NotIn
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:
path: '{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ | trimSuffix "/" }}/health/live'
port: '{{ .Values.http.internalPort }}'
scheme: '{{ .Values.http.internalScheme }}'
initialDelaySeconds: 0
timeoutSeconds: 5
# Readiness probe configuration
readinessProbe: |
httpGet:
path: '{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ | trimSuffix "/" }}/health/ready'
port: '{{ .Values.http.internalPort }}'
scheme: '{{ .Values.http.internalScheme }}'
initialDelaySeconds: 10
timeoutSeconds: 1
# Startup probe configuration
startupProbe: |
httpGet:
path: '{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ | trimSuffix "/" }}/health'
port: '{{ .Values.http.internalPort }}'
scheme: '{{ .Values.http.internalScheme }}'
initialDelaySeconds: 15
timeoutSeconds: 1
failureThreshold: 60
periodSeconds: 5
# Pod resource requests and limits
resources: {}
# 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
# 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
# minAvailable: 1
# 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: {}
service:
# Annotations for HTTP service
annotations: {}
# Additional labels for HTTP Service
labels: {}
# key: value
# The Service type
type: ClusterIP
# Optional IP for the load balancer. Used for services of type LoadBalancer only
loadBalancerIP: ""
# The http Service port
httpPort: 80
# The HTTP Service node port if type is NodePort
httpNodePort: null
# The HTTPS Service port
httpsPort: 8443
# The HTTPS Service node port if type is NodePort
httpsNodePort: null
# Additional Service ports, e. g. for custom admin console
extraPorts: []
# When using Service type LoadBalancer, you can restrict source ranges allowed
# to connect to the LoadBalancer, e. g. will result in Security Groups
# (or equivalent) with inbound source ranges allowed to connect
loadBalancerSourceRanges: []
# When using Service type LoadBalancer or NodePort, you can preserve the source IP seen in the container
# by changing the default (Cluster) to be Local.
# See https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip
externalTrafficPolicy: "Cluster"
# Controls how traffic from internal sources is routed. Valid values: Cluster, Local.
# See https://kubernetes.io/docs/concepts/services-networking/service-traffic-policy/
internalTrafficPolicy: ""
# Session affinity
# See https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-userspace
sessionAffinity: ""
# Session affinity config
sessionAffinityConfig: {}
serviceHeadless:
# Annotations for headless service
annotations: {}
# Additional labels for headless service
labels: {}
# Add additional ports to the headless service, e. g. for admin console or exposing JGroups ports
extraPorts: []
# -- Expose the service via gateway-api HTTPRoute
# Requires Gateway API resources and suitable controller installed within the cluster
# (see: https://gateway-api.sigs.k8s.io/guides/)
httpRoute:
# HTTPRoute enabled.
enabled: false
# Additional HTTPRoute labels
labels: {}
# HTTPRoute annotations.
annotations: {}
# The Service port targeted by the HTTPRoute, MUST BE AN NUMBER
servicePort: 80
# Which Gateways this Route is attached to.
parentRefs:
- name: gateway
sectionName: http
# namespace: default
# Hostnames matching HTTP header.
hostnames:
- chart-example.local
# List of rules and filters applied.
rules:
- matches:
- path:
type: PathPrefix
value: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/'
# -- Create a ListenerSet resource to attach listeners to an existing Gateway
# without requiring write access to the Gateway resource itself. Useful for
# namespace-level configuration where app owners do not have Gateway write access.
# When enabled, the HTTPRoute parentRefs are auto-derived from the ListenerSet name,
# and hostnames are derived from listener hostnames; httpRoute.parentRefs and
# httpRoute.hostnames are unused.
listenerSet:
# If `true`, a ListenerSet resource is created alongside the HTTPRoute
enabled: false
# Additional ListenerSet labels
labels: {}
# ListenerSet annotations
annotations: {}
# The Gateway this ListenerSet attaches to
parentRef:
name: gateway
# namespace: envoy-gateway-system
# Listeners to attach to the Gateway. Passed through as-is.
# Listener hostnames are used to populate the HTTPRoute hostnames field.
listeners: []
# - name: http
# hostname: keycloak.example.com
# port: 80
# protocol: HTTP
# allowedRoutes:
# namespaces:
# from: Same
# HTTPRoute for console only (/auth/admin)
console:
# If `true`, an HTTPRoute is created for console path only
enabled: false
# Additional HTTPRoute labels
labels: {}
# HTTPRoute annotations.
annotations: {}
# Which Gateways this Route is attached to.
parentRefs:
- name: gateway
sectionName: http
# namespace: default
# Hostnames matching HTTP header.
hostnames:
- chart-example.local
# List of rules and filters applied.
rules:
- matches:
- path:
type: PathPrefix
value: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/admin'
ingress:
# If `true`, an Ingress is created
enabled: false
# The name of the Ingress Class associated with this ingress
ingressClassName: ""
# The Service port targeted by the 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
# Additional Ingress labels
labels: {}
# List of rules for the Ingress
rules:
-
# Ingress host
host: '{{ .Release.Name }}.keycloak.example.com'
# Paths for the host
paths:
- path: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/'
pathType: Prefix
# serviceName: "" # Optional: Override backend service name (e.g., for AWS ALB action annotations)
# servicePort: "" # Optional: Override backend service port name
# TLS configuration
tls: []
# - hosts:
# - keycloak.example.com
# secretName: ""
# ingress for console only (/auth/admin)
console:
# If `true`, an Ingress is created for console path only
enabled: false
# The name of Ingress Class associated with the console ingress only
ingressClassName: ""
# Ingress annotations for console ingress only
# Useful to set nginx.ingress.kubernetes.io/whitelist-source-range particularly
annotations: {}
# Additional Ingress labels for console path only
labels: {}
rules:
-
# Ingress host
host: '{{ .Release.Name }}.keycloak.example.com'
# Paths for the host
paths:
- path: '{{ tpl .Values.http.relativePath $ | trimSuffix "/" }}/admin'
pathType: Prefix
# serviceName: "" # Optional: Override backend service name (e.g., for AWS ALB action annotations)
# servicePort: "" # Optional: Override backend service port name
# 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:
# - to:
# - ipBlock:
# cidr: 192.168.1.30/32
# ports:
# - protocol: TCP
# port: 3306
egress: []
route:
# If `true`, an OpenShift Route is created
enabled: false
# Path for the Route
path: /
# Route annotations
annotations: {}
# Additional Route labels
labels: {}
# Host name for the Route
host: ""
# TLS configuration
tls:
# If `true`, TLS is enabled for the Route
enabled: true
# Insecure edge termination policy of the Route. Can be `None`, `Redirect`, or `Allow`
insecureEdgeTerminationPolicy: Redirect
# TLS termination of the route. Can be `edge`, `passthrough`, or `reencrypt`
termination: edge
dbchecker:
enabled: false
image:
# Docker image used to check Database readiness at startup
repository: docker.io/busybox
# Image tag for the dbchecker image
tag: 1.37
# Image pull policy for the dbchecker image
pullPolicy: IfNotPresent
# SecurityContext for the dbchecker container
securityContext:
allowPrivilegeEscalation: false
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
# Resource requests and limits for the dbchecker container
resources:
requests:
cpu: "20m"
memory: "32Mi"
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: ""
# E.g. dev-file, dev-mem, mariadb, mssql, mysql, oracle or postgres
vendor:
hostname:
port:
database:
username:
password:
cache:
# Use "custom" to disable automatic cache configuration
stack: default
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"
# Set the relative path for Keycloak's management interface (KC_HTTP_MANAGEMENT_RELATIVE_PATH).
# This controls the path prefix for health and metrics endpoints served on the management port (9000).
# When empty, the env var is not set and Keycloak inherits the value from `http.relativePath`.
# Set to "/" to serve management endpoints at the root (e.g. /health, /metrics).
managementRelativePath: ""
internalPort: http-internal
internalScheme: HTTP
serviceMonitor:
# If `true`, a ServiceMonitor resource for the prometheus-operator is created
enabled: false
# Optionally sets a target namespace in which to deploy the ServiceMonitor resource
namespace: ""
# Optionally sets a namespace for the ServiceMonitor
namespaceSelector: {}
# Annotations for the ServiceMonitor
annotations: {}
# Additional labels for the ServiceMonitor
labels: {}
# Interval at which Prometheus scrapes metrics
interval: 10s
# Timeout for scraping
scrapeTimeout: 10s
# Relabelings for the Servicemonitor
relabelings: []
# metricRelabelings for the Servicemonitor
metricRelabelings: []
# The path at which metrics are served
path: '{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ | trimSuffix "/" }}/metrics'
# The Service port at which metrics are served
port: '{{ .Values.http.internalPort }}'
# The scheme to use for scraping metrics ("http" or "https"); if not set, the `http.internalScheme` value is used
scheme: ""
extraServiceMonitor:
# If `true`, a ServiceMonitor resource for the prometheus-operator is created
enabled: false
# Optionally sets a target namespace in which to deploy the ServiceMonitor resource
namespace: ""
# Optionally sets a namespace for the ServiceMonitor
namespaceSelector: {}
# Annotations for the ServiceMonitor
annotations: {}
# Additional labels for the ServiceMonitor
labels: {}
# Interval at which Prometheus scrapes metrics
interval: 10s
# Timeout for scraping
scrapeTimeout: 10s
# Relabelings for the Servicemonitor
relabelings: []
# metricRelabelings for the Servicemonitor
metricRelabelings: []
# The path at which metrics are served
path: '{{ tpl (coalesce .Values.http.managementRelativePath .Values.http.relativePath) $ | trimSuffix "/" }}/metrics'
# The Service port at which metrics are served
port: '{{ .Values.http.internalPort }}'
# The scheme to use for scraping metrics ("http" or "https"); if not set, the `http.internalScheme` value is used
scheme: ""
prometheusRule:
# If `true`, a PrometheusRule resource for the prometheus-operator is created
enabled: false
# Optionally sets a target namespace in which to deploy the ServiceMonitor resource
namespace: ""
# Annotations for the PrometheusRule
annotations: {}
# Additional labels for the PrometheusRule
labels: {}
# List of rules for Prometheus
rules: []
# - alert: keycloak-IngressHigh5xxRate
# annotations:
# message: The percentage of 5xx errors for keycloak over the last 5 minutes is over 1%.
# expr: |
# (
# sum(
# rate(
# nginx_ingress_controller_response_duration_seconds_count{exported_namespace="mynamespace",ingress="mynamespace-keycloak",status=~"5[0-9]{2}"}[1m]
# )
# )
# /
# sum(
# rate(
# nginx_ingress_controller_response_duration_seconds_count{exported_namespace="mynamespace",ingress="mynamespace-keycloak"}[1m]
# )
# )
# ) * 100 > 1
# 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)
# This disables the `replicas` field in the StatefulSet
enabled: false
# Additional HorizontalPodAutoscaler labels
labels: {}
# The minimum and maximum number of replicas for the Keycloak StatefulSet
minReplicas: 3
maxReplicas: 10
# The metrics to use for scaling
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
# The scaling policy to use. This will scale up quickly but only scale down a single Pod per 5 minutes.
# This is important because caches are usually only replicated to 2 Pods and if one of those Pods is terminated this will give the cluster time to recover.
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 300
test:
# If `true`, test resources are created
enabled: false
image:
# The image for the test Pod
repository: docker.io/selenium/standalone-chromium
# The tag for the test Pod image
tag: "147.0"
# The image pull policy for the test Pod image
pullPolicy: IfNotPresent
# SecurityContext for the entire test Pod
podSecurityContext:
fsGroup: 1200 # UID of seluser in selenium/standalone-chromium
# SecurityContext for the test container
securityContext:
runAsUser: 1200 # UID of seluser in selenium/standalone-chromium
runAsNonRoot: true
# See https://helm.sh/docs/topics/charts_hooks/#hook-deletion-policies
deletionPolicy: before-hook-creation
## -- Extra Kubernetes objects to deploy with the helm chart
extraManifests: []
# - |
# apiVersion: v1
# kind: ConfigMap
# metadata:
# name: {{ include "keycloak.fullname" . }}-tpl
# data:
# foo: bar
# - apiVersion: v1
# kind: ConfigMap
# metadata:
# name: "{{ include \"keycloak.fullname\" . }}-tpl"
# data:
# foo: bar