Aller au contenu
medium

Déployer Keycloak sur Kubernetes avec l'Operator

16 min de lecture

Logo Keycloak

Ce guide déploie Keycloak sur Kubernetes avec l'Operator officiel. Vous installez l'Operator et ses CRD, provisionnez la base PostgreSQL qu'il n'embarque pas, déclarez une instance via la ressource Keycloak, importez un realm de façon déclarative, et diagnostiquez les pannes de démarrage. Il s'adresse aux profils intermédiaires à avancés qui connaissent déjà kubectl. Toutes les commandes et sorties viennent d'un cluster réel sous Keycloak 26.7.0.

  • Installer l'Operator Keycloak et ses quatre CRD sans casser les permissions.
  • Déployer une instance avec PostgreSQL et vérifier ses conditions de statut.
  • Importer un realm en déclaratif, et connaître la limite qui piège tout le monde.
  • Diagnostiquer un pod bloqué : hostname, port des health checks, mémoire.
  • Choisir entre l'Operator et un chart Helm en connaissance de cause.
  • un cluster Kubernetes fonctionnel et kubectl configuré (un cluster local type k3d suffit) ;
  • environ 2 Go de RAM disponibles pour le pod Keycloak, qui demande 1700 Mio par défaut ;
  • des notions de Keycloak : si vous découvrez l'outil, commencez par l'installation avec Docker, bien plus simple.

Un Operator est un contrôleur qui étend l'API Kubernetes avec vos propres types d'objets. Au lieu d'écrire vous-même un StatefulSet, un Service et une douzaine de variables d'environnement, vous déclarez une ressource Keycloak, et l'Operator fabrique et surveille tout le reste. Si le concept est neuf pour vous, la page sur les opérateurs Kubernetes pose les bases.

L'Operator Keycloak 26 installe quatre CRD dans le groupe k8s.keycloak.org :

CRDRôle
KeycloakL'instance elle-même (réplicas, base, hostname, TLS)
KeycloakRealmImportCréer un realm à partir d'un manifeste
KeycloakOIDCClientDéclarer un client OIDC (nouveauté 26)
KeycloakSAMLClientDéclarer un client SAML (nouveauté 26)

Retenez ce chiffre de quatre, il explique la première panne du guide.

L'installation se fait avec Kustomize, en épinglant la version. C'est la méthode officielle, et ce n'est pas un détail de confort.

Fenêtre de terminal
kubectl create namespace keycloak
kubectl apply -k 'github.com/keycloak/keycloak-k8s-resources/kubernetes?ref=26.7.0'

Vérifiez que les quatre CRD sont bien enregistrées, puis que l'Operator tourne :

Fenêtre de terminal
kubectl get crd | grep keycloak
# keycloakoidcclients.k8s.keycloak.org
# keycloakrealmimports.k8s.keycloak.org
# keycloaks.k8s.keycloak.org
# keycloaksamlclients.k8s.keycloak.org
kubectl get pods -n keycloak
# NAME READY STATUS RESTARTS AGE
# keycloak-operator-849dc9ddcd-xttb2 1/1 Running 0 11s

L'Operator ne gère aucune base de données. C'est écrit noir sur blanc dans la documentation, et c'est la première surprise : Keycloak tourne en mode production sous l'Operator, il lui faut donc une base externe. À vous de la fournir.

Pour un lab, un PostgreSQL éphémère suffit. En production, utilisez une base gérée ou un operator dédié.

postgres.yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-db-secret
namespace: keycloak
type: Opaque
stringData:
username: keycloak
password: keycloak-lab-password
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: keycloak
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:17-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: keycloak
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: keycloak-db-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-db-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: keycloak
spec:
selector:
app: postgres
ports:
- port: 5432
Fenêtre de terminal
kubectl apply -f postgres.yaml
kubectl wait --for=condition=ready pod -l app=postgres -n keycloak --timeout=180s
# pod/postgres-0 condition met

Voici le manifeste minimal qui fonctionne pour un lab. Chaque champ compte.

keycloak.yaml
apiVersion: k8s.keycloak.org/v2beta1
kind: Keycloak
metadata:
name: keycloak-lab
namespace: keycloak
spec:
instances: 1
db:
vendor: postgres
host: postgres
database: keycloak
usernameSecret:
name: keycloak-db-secret
key: username
passwordSecret:
name: keycloak-db-secret
key: password
http:
httpEnabled: true
hostname:
strict: false
ingress:
enabled: false

Détaillons les choix :

  • apiVersion: k8s.keycloak.org/v2beta1. Attention, la documentation officielle utilise encore v2alpha1 dans plusieurs exemples. Depuis Keycloak 26, la version de stockage est v2beta1 : c'est celle qu'il faut écrire.
  • db.host: postgres pointe le Service PostgreSQL. Les identifiants sont lus dans le Secret, jamais en clair.
  • http.httpEnabled: true active l'écouteur HTTP en clair. Sans cela, Keycloak sert du HTTPS avec un certificat auto-signé, ce qui complique inutilement un lab.
  • hostname.strict: false désactive la validation stricte du hostname. Keycloak 26 est intraitable sur ce point : en mode production, il exige soit un hostname explicite, soit cette désactivation, sinon il refuse de démarrer.
  • ingress.enabled: false évite que l'Operator crée un Ingress dont nous n'avons pas besoin ici.

Appliquez, puis surveillez les conditions de la ressource :

Fenêtre de terminal
kubectl apply -f keycloak.yaml
kubectl get keycloaks/keycloak-lab -n keycloak \
-o go-template='{{range .status.conditions}}{{.type}}={{.status}}{{"\n"}}{{end}}'
# Ready=True
# HasErrors=False
# RollingUpdate=False

Comptez environ 70 secondes avant Ready=True : Keycloak est lent à démarrer. L'Operator a créé un StatefulSet et deux Services :

Fenêtre de terminal
kubectl get statefulset,svc -n keycloak
# statefulset.apps/keycloak-lab 1/1
# service/keycloak-lab-service ClusterIP 8080/TCP,9000/TCP
# service/keycloak-lab-discovery ClusterIP None 7800/TCP

Notez le Service -discovery, headless, sur le port 7800 : c'est par lui que les instances se découvrent pour former le cluster de cache Infinispan quand vous montez instances au-delà de 1.

L'Operator génère un compte administrateur temporaire dans un Secret nommé d'après votre ressource :

Fenêtre de terminal
kubectl get secret keycloak-lab-initial-admin -n keycloak \
-o jsonpath='{.data.username}' | base64 -d
# temp-admin
kubectl get secret keycloak-lab-initial-admin -n keycloak \
-o jsonpath='{.data.password}' | base64 -d

Ouvrez ensuite un tunnel vers le Service :

Fenêtre de terminal
kubectl port-forward -n keycloak service/keycloak-lab-service 8080:8080

La console est sur http://localhost:8080. Si le port 8080 est déjà pris sur votre machine (par une autre instance Keycloak en Docker, par exemple), changez le port local : kubectl port-forward ... 8081:8080.

La ressource KeycloakRealmImport crée un realm à partir d'un manifeste. Le champ realm accepte une RealmRepresentation complète, c'est-à-dire le même objet JSON que produit un export de realm.

realm.yaml
apiVersion: k8s.keycloak.org/v2beta1
kind: KeycloakRealmImport
metadata:
name: intranet-realm
namespace: keycloak
spec:
keycloakCRName: keycloak-lab
realm:
id: intranet
realm: intranet
displayName: Intranet
enabled: true

L'Operator lance un Job qui pousse le realm dans l'instance :

Fenêtre de terminal
kubectl apply -f realm.yaml
kubectl get jobs -n keycloak
# NAME STATUS COMPLETIONS DURATION
# intranet-realm Complete 1/1 19s
kubectl get keycloakrealmimport intranet-realm -n keycloak \
-o go-template='{{range .status.conditions}}{{.type}}={{.status}} {{end}}'
# Done=True Started=False HasErrors=False

Keycloak sur Kubernetes échoue presque toujours pour l'une de ces cinq raisons. Le tableau donne le symptôme tel que vous le verrez, pas le message d'erreur théorique.

SymptômeCauseCorrectif
Operator en CrashLoopBackOff, pile Java illisibleManifestes posés en apply -f : CRD manquantes, RoleBindings cassésRéinstaller avec apply -k
Pod Keycloak refuse de démarrer, log sur le hostnameKeycloak 26 exige un hostname explicite en mode productionhostname.strict: false (lab) ou hostname.hostname (prod)
Pod jamais Ready, probe en connection refusedLes health checks ont migré sur le port 9000Sonder :9000, ouvrir ce port dans vos NetworkPolicy
Pod tué au démarrage, OOMKilledLimite mémoire rabotée « pour un lab »Laisser les 1700 Mio de requête, ou monter la limite
Boucle de redirection, Invalid parameter: redirect_uriKeycloak ne fait pas confiance aux en-têtes du reverse proxyproxy.headers: xforwarded

Depuis Keycloak 25, les endpoints de santé ont quitté le port applicatif pour un port de management dédié, le 9000. C'est vérifiable en une commande :

Fenêtre de terminal
# Sur le port 9000 : la santé répond
curl -s http://keycloak-lab-service:9000/health/ready
# {"status": "UP", "checks": [ ...
# Sur le port 8080 : rien
curl -s -o /dev/null -w "%{http_code}\n" http://keycloak-lab-service:8080/health/ready
# 404

Toute sonde, tout Ingress ou toute NetworkPolicy qui interroge encore 8080/health/ready marquera le pod comme défaillant alors qu'il fonctionne parfaitement. C'est la cause numéro un des pods « jamais Ready » après une montée de version.

On lit souvent que la sonde de vivacité abattrait Keycloak avant qu'il ait fini de démarrer. Avec l'Operator, c'est faux : la startupProbe par défaut laisse dix minutes de grâce. Si votre pod meurt au démarrage, cherchez plutôt du côté de la mémoire (OOMKilled) ou du hostname. Réduire la limite mémoire à 512 Mio « parce que c'est un lab » est le meilleur moyen de se créer ce problème.

Derrière un Ingress qui termine le TLS, Keycloak se croit en HTTP simple et fabrique des URL de redirection en http://. Résultat : boucle de redirection ou Invalid parameter: redirect_uri. Le correctif tient en une ligne dans la ressource :

spec:
proxy:
headers: xforwarded

Méfiez-vous des tutoriels encore en ligne qui recommandent proxy: edge ou KC_PROXY=edge : cette option n'existe plus depuis Keycloak 24. Sa présence dans un guide est le meilleur indicateur qu'il est périmé.

Ce lab tient sur une instance. Trois points changent la donne dès que vous en voulez plusieurs.

Les sessions vivent dans le cache. Keycloak stocke les sessions d'authentification dans un cache Infinispan local à chaque pod. Sans configuration, un utilisateur routé vers un autre pod perd sa session. Le Service -discovery headless sert justement à ce que les instances se trouvent.

Les sticky sessions ont un piège. Coller la session sur le cookie AUTH_SESSION_ID provoque une boucle de redirection infinie. C'est un classique, il faut coller sur un autre cookie.

Un upgrade coupe le service. L'Operator fait du rolling update quand la configuration change, mais bascule en recréation avec coupure dès que l'image change, car les migrations de schéma exigent qu'une seule instance touche la base. Trois réplicas ne vous garantissent donc pas une montée de version sans interruption.

Sur le durcissement (TLS, en-têtes, journaux d'audit), les principes valent pour toute installation : voyez la page sécurité opérationnelle.

  • Aucun chart Helm officiel n'existe et il n'en existera pas : l'Operator est la voie soutenue.
  • Installez avec kubectl apply -k ...?ref=26.7.0. Poser les manifestes à la main casse les CRD et les permissions.
  • L'Operator ne fournit pas de base de données : PostgreSQL est à provisionner à part.
  • L'apiVersion courante est k8s.keycloak.org/v2beta1, malgré ce qu'écrivent encore certains exemples officiels.
  • Aucun champ du manifeste n'est obligatoire : un CR invalide est accepté puis échoue. Surveillez la condition HasErrors.
  • Les health checks sont sur le port 9000, plus sur 8080. C'est la panne qui suit tous les upgrades.
  • KeycloakRealmImport ne met jamais à jour un realm existant, et le fait sans le dire. Pour du GitOps, passez par keycloak-config-cli ou Terraform.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn