Install Keycloak with the Operator

Austin Cunningham
Keycloak
Published in
5 min readSep 15, 2023

--

In this post, you will learn how to deploy Keycloak to the Openshift cluster using the Keycloak Operator. I am using version 21.1.1 for the Keycloak Operator. For the datastore, use the Postgresql ephemeral instance for the Keycloak instance. I will also show, exposing Keycloak service through the Openshift route, which will make the Keycloak access through a route. Let’s get started by creating a project in the Openshift cluster.

Create a project

oc new-project keycloak

I will install the operator via OperatorHub in Openshift, which uses OLM(Operator Lifecycle Manager). Select Operator/OperatorHub from the sidebar, search for keycloak, and install it in the keycloak namespace.

The installation should be finished.

I will go through the requirements now. This will not be a production setup.

Install a DB

I will install an Ephemeral PostgreSQL DB as this is just a demo. Create a YAML file called example-postgres.yaml with the following content.

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgresql-db
spec:
serviceName: postgresql-db-service
selector:
matchLabels:
app: postgresql-db
replicas: 1
template:
metadata:
labels:
app: postgresql-db
spec:
containers:
- name: postgresql-db
image: postgres:latest
env:
- name: POSTGRES_PASSWORD
value: postgres #<-- change this
- name: POSTGRES_USER
value: postgres #<-- change this
- name: PGDATA
value: /data/pgdata
- name: POSTGRES_DB
value: keycloak
---
apiVersion: v1
kind: Service
metadata:
name: postgres-db
spec:
selector:
app: postgresql-db
ports:
- port: 5432
targetPort: 5432

And then apply the file to the cluster

oc apply -f example-postgres.yaml

The result

statefulset.apps/postgresql-db created
service/postgres-db created

After checking the Postgres pod logs, I see the following error

mkdir: cannot create directory ‘/data’: Permission denied

Nothing is ever easy. This is because Openshift blocks containers being run as root. To get this to work, we need to add a service account with the correct permissions and add that service account to the statefulset. This is a good reference blog for more details. Here are the commands I used

# create the service account
~ oc create sa postgres-sa
serviceaccount/postgres-sa created
# add policy
~ oc adm policy add-scc-to-user anyuid -z postgres-sa
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "postgres-sa"
# set the policy on the namespace
~ oc adm policy add-scc-to-user anyuid -z postgres-sa -n keycloak
clusterrole.rbac.authorization.k8s.io/system:openshift:scc:anyuid added: "postgres-sa"
# add the service account to the statefulset
~ oc set sa statefulset postgresql-db postgres-sa
statefulset.apps/postgresql-db serviceaccount updated
# delete the failing existing pod
oc delete po postgresql-db-0
# check the pods
~ oc get po
NAME READY STATUS RESTARTS AGE
keycloak-operator-5659f58f4b-vtrm9 1/1 Running 0 29m
postgresql-db-0 1/1 Running 0 8m55s

Ok that’s working

Hostname

We need a resolvable domain name. You can add any domain to your Openshift cluster using a customDomain. As I couldn’t be bothered setting up an actual domain name here or a valid cert, this is a quick hack for creating a resolvable domain name on Openshift.

# create a self signed cert 
openssl req -subj '/CN=apps.austin.me/O=Test Keycloak./C=US' -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
# create cert project
oc new-project certs
# create a secret from that cert in the cert namespace
oc create secret tls austin-me-tls --cert=austin.me.crt --key=austin.me.key -n certs
# create a custom domain based on the cert
oc apply -f - <<EOF
---
apiVersion: managed.openshift.io/v1alpha1
kind: CustomDomain
metadata:
name: cunningham
spec:
domain: apps.austin.me
scope: External
certificate:
name: austin-me-tls
namespace: certs
EOF
# Wait for the custom domain to become ready
oc get customdomains
NAME ENDPOINT DOMAIN STATUS
cunningham oeffrs.cunningham.aucunnin.lpi0.s1.devshift.org apps.austin.me Ready

All pretty standard at this point, but the domain apps.austin.me doesn’t exist. So here is the hack, edit the custom domain oc edit customdomain cunningham and replace the spec.host with the endpoint.

apiVersion: managed.openshift.io/v1alpha1
kind: CustomDomain
metadata:
name: cunningham
spec:
certificate:
name: austin-me-tls
namespace: certs
domain: oeffrs.cunningham.aucunnin.lpi0.s1.devshift.org #<---- was apps.austin.me now points at the endpoint
loadBalancerType: Classic
scope: External

So, our domain going forward is the endpoint oeffrs.cunningham.aucunnin.lpi0.s1.devshift.org

Create a DB secret

oc project keycloak
# change these to match the secret used in your statefulset
kubectl create secret generic keycloak-db-secret \
--from-literal=username=postgres \
--from-literal=password=postgres

Create the Keycloak CR

The CR(Custom Resource) creates an instance of the Keycloak UI. Create a YAML file called example-kc.yaml

apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: example-kc
spec:
instances: 1
db:
vendor: postgres
host: postgres-db
usernameSecret:
name: keycloak-db-secret
key: username
passwordSecret:
name: keycloak-db-secret
key: password
http:
tlsSecret: austin-me-tls
hostname:
hostname: oeffrs.cunningham.aucunnin.lpi0.s1.devshift.org

And then apply the file

kubectl apply -f example-kc.yaml

We can check the CR status of keycloak; it will look like this when it is type: Ready status: "True"

# using https://mikefarah.gitbook.io/yq/
~ oc get keycloak example-kc -oyaml | yq '.status'
conditions:
- lastTransitionTime: "2023-09-15T08:08:56.665783288Z"
message: ""
observedGeneration: 1
status: "True" #<------ This is what we are looking for I hate these condition to difficult to read a first glance
type: Ready
- lastTransitionTime: "2023-09-15T08:08:26.147242224Z"
message: ""
observedGeneration: 1
status: "False"
type: HasErrors
- lastTransitionTime: "2023-09-15T08:08:26.147242224Z"
message: ""
observedGeneration: 1
status: "False"
type: RollingUpdate
instances: 1
observedGeneration: 1
selector: app=keycloak,app.kubernetes.io/managed-by=keycloak-operator,app.kubernetes.io/instance=example-kc

That looks to have been completed and if we port-forward the service, it looks to be working

~ oc port-forward service/example-kc-service 8443:8443
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
Handling connection for 8443
Handling connection for 8443
Handling connection for 8443
Handling connection for 8443
Handling connection for 8443

And if we check the route, we can open the keycloak landing page.

oc get routes --namespace keycloak
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
example-kc-ingress-76p8j oeffrs.cunningham.aucunnin.lpi0.s1.devshift.org ... 1 more example-kc-service https passthrough/Redirect None

--

--