feat(keycloak): install keycloak with official operator

This commit is contained in:
Masaki Yatsu
2025-09-18 11:46:10 +09:00
parent df345d47b0
commit d576eaec97
4 changed files with 148 additions and 75 deletions

2
keycloak/.gitignore vendored
View File

@@ -1 +1 @@
keycloak-values.yaml keycloak-cr.yaml

View File

@@ -1,10 +1,10 @@
set fallback := true set fallback := true
# Keycloak Helm chart info: # Keycloak Operator info:
# helm show chart oci://registry-1.docker.io/bitnamicharts/keycloak # https://www.keycloak.org/operator/installation
export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak") export KEYCLOAK_NAMESPACE := env("KEYCLOAK_NAMESPACE", "keycloak")
export KEYCLOAK_CHART_VERSION := env("KEYCLOAK_CHART_VERSION", "25.0.2") export KEYCLOAK_OPERATOR_VERSION := env("KEYCLOAK_OPERATOR_VERSION", "26.3.4")
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "") export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "")
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "") export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s") export K8S_OIDC_CLIENT_ID := env('K8S_OIDC_CLIENT_ID', "k8s")
@@ -52,23 +52,37 @@ create-credentials:
echo "Waiting for ExternalSecret to sync..." echo "Waiting for ExternalSecret to sync..."
kubectl wait --for=condition=Ready externalsecret/keycloak-credentials \ kubectl wait --for=condition=Ready externalsecret/keycloak-credentials \
-n ${KEYCLOAK_NAMESPACE} --timeout=60s -n ${KEYCLOAK_NAMESPACE} --timeout=60s
# Create bootstrap admin secret for Keycloak Operator (username/password format)
kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \
--from-literal=username="${admin_user}" \
--from-literal=password="${password}"
else else
echo "External Secrets Operator not found. Creating secret directly..." echo "External Secrets Operator not found. Creating secrets directly..."
if kubectl get secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} &>/dev/null; then # Delete existing secrets
kubectl delete --ignore-not-found secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
fi kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
# Create keycloak-credentials secret (admin-user/password format for legacy scripts)
kubectl create secret generic keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \ kubectl create secret generic keycloak-credentials -n ${KEYCLOAK_NAMESPACE} \
--from-literal=admin-user="${admin_user}" \ --from-literal=admin-user="${admin_user}" \
--from-literal=password="${password}" --from-literal=password="${password}"
# Create bootstrap admin secret for Keycloak Operator (username/password format)
kubectl create secret generic keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} \
--from-literal=username="${admin_user}" \
--from-literal=password="${password}"
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then
just put-admin-credentials-to-vault "${admin_user}" "${password}" just put-admin-credentials-to-vault "${admin_user}" "${password}"
fi fi
fi fi
# Delete Keycloak secret # Delete Keycloak secrets
delete-credentials: delete-credentials:
@kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete secret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
@kubectl delete secret keycloak-bootstrap-admin -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
@kubectl delete externalsecret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete externalsecret keycloak-credentials -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
# Create Keycloak database secret # Create Keycloak database secret
@@ -89,31 +103,51 @@ create-database-secret:
delete-database-secret: delete-database-secret:
@kubectl delete secret database-config -n ${KEYCLOAK_NAMESPACE} --ignore-not-found @kubectl delete secret database-config -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
# Install Keycloak # Install Keycloak Operator
install-operator:
#!/bin/bash
set -euo pipefail
just create-namespace
echo "Installing Keycloak Operator CRDs..."
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
kubectl apply -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
echo "Installing Keycloak Operator in ${KEYCLOAK_NAMESPACE} namespace..."
kubectl apply -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml
kubectl wait --for=condition=available deployment/keycloak-operator -n ${KEYCLOAK_NAMESPACE} --timeout=300s
# Install Keycloak instance
install: install:
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
# Setup vault environment once at the beginning if vault is enabled just install-operator
just create-credentials just create-credentials
just postgres::create-db keycloak just postgres::create-db keycloak
just create-database-secret just create-database-secret
just create-namespace
KEYCLOAK_ADMIN_USER=$(just admin-username) \ KEYCLOAK_ADMIN_USER=$(just admin-username) \
gomplate -f keycloak-values.gomplate.yaml -o keycloak-values.yaml gomplate -f keycloak-cr.gomplate.yaml -o keycloak-cr.yaml
helm upgrade --cleanup-on-fail --install \ kubectl apply -f keycloak-cr.yaml
keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \ kubectl wait --for=condition=Ready keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=600s
--version ${KEYCLOAK_CHART_VERSION} -n ${KEYCLOAK_NAMESPACE} --wait \
-f keycloak-values.yaml
# Uninstall Keycloak # Uninstall Keycloak instance
uninstall delete-db='true': uninstall delete-db='true':
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
helm uninstall keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found --wait kubectl delete keycloak keycloak -n ${KEYCLOAK_NAMESPACE} --ignore-not-found
kubectl wait --for=delete keycloak/keycloak -n ${KEYCLOAK_NAMESPACE} --timeout=300s || true
just delete-namespace just delete-namespace
if [ "{{ delete-db }}" = "true" ]; then if [ "{{ delete-db }}" = "true" ]; then
just postgres::delete-db keycloak just postgres::delete-db keycloak
fi fi
# Uninstall Keycloak Operator
uninstall-operator:
#!/bin/bash
set -euo pipefail
kubectl delete -n ${KEYCLOAK_NAMESPACE} -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/kubernetes.yml --ignore-not-found
kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml --ignore-not-found
kubectl delete -f https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/${KEYCLOAK_OPERATOR_VERSION}/kubernetes/keycloaks.k8s.keycloak.org-v1.yml --ignore-not-found
# Create Keycloak realm # Create Keycloak realm
create-realm create-client-for-k8s='true' access_token_lifespan='3600' refresh_token_lifespan='14400' sso_session_idle_timeout='7200': create-realm create-client-for-k8s='true' access_token_lifespan='3600' refresh_token_lifespan='14400' sso_session_idle_timeout='7200':
#!/bin/bash #!/bin/bash

View File

@@ -0,0 +1,96 @@
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: keycloak
namespace: {{ .Env.KEYCLOAK_NAMESPACE }}
spec:
instances: 1
image: quay.io/keycloak/keycloak:26.3.4
startOptimized: false
# Database configuration for external PostgreSQL
db:
vendor: postgres
host: postgres-cluster-rw.postgres
port: 5432
database: keycloak
usernameSecret:
name: database-config
key: user
passwordSecret:
name: database-config
key: password
# Hostname configuration
hostname:
hostname: {{ .Env.KEYCLOAK_HOST }}
strict: false
strictBackchannel: false
# HTTP configuration
http:
httpEnabled: true
httpPort: 8080
httpsPort: 8443
# Proxy configuration for edge proxy
proxy:
headers: xforwarded
# Additional options and admin configuration
additionalOptions:
- name: http-enabled
value: "true"
- name: hostname-strict
value: "false"
- name: hostname-strict-https
value: "false"
- name: proxy
value: edge
# Bootstrap admin configuration
bootstrapAdmin:
user:
secret: keycloak-bootstrap-admin
# Resources
resources:
requests:
memory: "1.5Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
# Ingress configuration (disabled - using separate Ingress resource)
ingress:
enabled: false
---
# Separate Ingress resource for custom configuration
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak-ingress
namespace: {{ .Env.KEYCLOAK_NAMESPACE }}
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
tls:
- hosts:
- {{ .Env.KEYCLOAK_HOST }}
secretName: keycloak-tls
rules:
- host: {{ .Env.KEYCLOAK_HOST }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keycloak-service
port:
number: 8080

View File

@@ -1,57 +0,0 @@
global:
security:
allowInsecureImages: true
production: true
# Enable HTTP for health checks in production mode
extraEnvVars:
- name: KC_HTTP_ENABLED
value: "true"
auth:
adminUser: {{ .Env.KEYCLOAK_ADMIN_USER }}
existingSecret: keycloak-credentials
passwordSecretKey: password
postgresql:
enabled: false
externalDatabase:
host: postgres-cluster-rw.postgres
port: 5432
database: keycloak
existingSecret: database-config
existingSecretUserKey: user
existingSecretPasswordKey: password
tls:
enabled: true
autoGenerated:
enabled: true
engine: helm
# Keycloak pod may not start with the default memory limits
resources:
limits:
memory: 2Gi
requests:
memory: 1.5Gi
image:
registry: docker.io
repository: bitnamilegacy/keycloak
# tag: 26.0.5-debian-12-r0
# debug: true
# logging:
# level: DEBUG
ingress:
enabled: true
ingressClassName: traefik
hostname: {{ .Env.KEYCLOAK_HOST }}
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
tls: true