avatarBill WANG

Summary

This context provides a detailed guide on how to set up a Keycloak cluster with Docker Compose for high availability and scalability.

Abstract

The context discusses the benefits of using a Keycloak cluster, such as fault tolerance, load distribution, and scalability. It provides a step-by-step guide on setting up a Keycloak cluster with Docker Compose, including the required files, environment preparation, and configuration. The guide also covers the explanation of important environment variables and how to test the cluster for failover and reliability. The context emphasizes the importance of using DNS-ready setups for Keycloak services and provides a sample configuration for a two-node cluster.

Bullet points

  • Keycloak cluster provides fault tolerance, load distribution, and scalability.
  • The guide uses Docker Compose to set up the Keycloak cluster.
  • Three files are required: Dockerfile, nginx.conf, and docker-compose-cluster.yml.
  • The environment must have Docker and Docker Compose installed.
  • The latest Docker version should be used due to the detected vulnerability CVE-2024-21626.
  • The guide explains important environment variables such as JGROUPS_DISCOVERY_PROTOCOL, JGROUPS_DISCOVERY_EXTERNAL_IP, CACHE_OWNERS_COUNT, CACHE_OWNERS_AUTH_SESSIONS_COUNT, and JGROUPS_DISCOVERY_PROPERTIES.
  • The guide provides instructions on how to test the cluster for failover and reliability.
  • The context emphasizes the importance of using DNS-ready setups for Keycloak services.
  • The guide provides a sample configuration for a two-node cluster.

Keycloak Cluster with Docker Compose — Up and Running in Seconds

Follow up on my keycloak blogs about

In this blog, I’d like to show you how you can run Keycloak with cluster in seconds. This post shares the solutions to setup Keycloak cluster in docker compose on same host.

It already supports DNS name access, so the same solution should be suitable for cross-data center (DC), Docker cross-host, Kubernetes cluster, and other similar scenarios.

Understanding Keycloak Cluster Architecture

Keycloak with cluster is great for seamless authentication service with fault tolerance, load distribution, and scalability, it is High Availability (HA) solution for you.

https://excalidraw.com/#json=0QhCmAOTuHKrvtDhUDRI_,nQTh-o_RTEWqBq8HtCt-Vw

Preparing the Environment

To simplify the setting, I made all in one docker compose file. You can easily take a reference and re-write for your own environment if you want, for example, to extend the solution to Kubernetes.

Make sure you have installed and enabled Docker service and install docker compose as well.

Due to the latest Vulnerability CVE-2024–21626 detected in Feb 2024, follow my another blog for detail (Illustrate runC Escape Vulnerability CVE-2024–21626 with my tests) , please update your docker to later version ASAP.

Three files you required for this experiment, copy and put them in same folder.

  • Dockerfile
  • nginx.conf
  • docker-compose-cluster.yml

Dockerfile (Please go through Run Keycloak locally with Docker compose, I explained, why we can’t directly use the official docker image quay.io/keycloak/keycloak )

# Documentation:
#  https://www.keycloak.org/server/containers

ARG KEYCLOAK_VERSION

FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION as builder

# Configure postgres database vendor
ENV KC_DB=postgres

ENV KC_FEATURES="token-exchange,scripts,preview"

WORKDIR /opt/keycloak

# If run the image in kubernetes, switch and active below line.
# RUN /opt/keycloak/bin/kc.sh build --cache=ispn --cache-stack=kubernetes --health-enabled=true --metrics-enabled=true
RUN /opt/keycloak/bin/kc.sh build --cache=ispn --health-enabled=true --metrics-enabled=true

FROM quay.io/keycloak/keycloak:$KEYCLOAK_VERSION

LABEL image.version=$KEYCLOAK_VERSION

COPY --from=builder /opt/keycloak/ /opt/keycloak/

# If any themes
# COPY themes/<nice-themes> /opt/keycloak/themes/<nice-themes>

# https://github.com/keycloak/keycloak/issues/19185#issuecomment-1480763024
USER root
RUN sed -i '/disabledAlgorithms/ s/ SHA1,//' /etc/crypto-policies/back-ends/java.config
USER keycloak

RUN /opt/keycloak/bin/kc.sh show-config

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

nginx.conf (As the load balancer)

upstream backend {
    server kc1:8080 fail_timeout=2s;
    server kc2:8080 fail_timeout=2s;
}

server {
    listen       8180;
    server_name  localhost;

    location / {
        proxy_set_header    Host               $host;
        proxy_set_header    X-Real-IP          $remote_addr;
        proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Host   $host;
        proxy_set_header    X-Forwarded-Server $host;
        proxy_set_header    X-Forwarded-Port   $server_port;
        proxy_set_header    X-Forwarded-Proto  $scheme;

        proxy_pass              http://backend;
        proxy_connect_timeout   2s;

        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;
    }
}

docker-compose-cluster.yml

# Make sure you have added "127.0.0.1 keycloak.com.au" into your local /etc/hosts file"

version: "3.9"
services:
  postgres:
    container_name: db
    # for production, recommend postgres v15+
    image: "postgres:15.5"
    healthcheck:
      test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "root" ]
      timeout: 45s
      interval: 10s
      retries: 10
    volumes:
      - postgres_data:/var/lib/postgresql/data
      #- ./sql:/docker-entrypoint-initdb.d/:ro # turn it on, if you need run init DB
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: keycloak
      POSTGRES_HOST: postgres
    networks:
      - local
    ports:
      - "5432:5432"

  kc1:
    container_name: kc1 
    build:
      context: .
      args:
        # test well with 21.0.0 and 22.0.0
        KEYCLOAK_VERSION: latest
    command: ['start', '--optimized']
    depends_on:
      - "postgres"
    environment:
      JAVA_OPTS_APPEND: -Dkeycloak.profile.feature.upload_scripts=enabled
      KC_DB_PASSWORD: postgres
      KC_DB_URL: jdbc:postgresql://postgres/keycloak
      KC_DB_USERNAME: postgres
      KC_HEALTH_ENABLED: 'true'
      KC_HTTP_ENABLED: 'true'
      KC_METRICS_ENABLED: 'true'
      # KC_HOSTNAME: keycloak.com.au
      # KC_HOSTNAME_PORT: 8180
      KC_HOSTNAME_URL: http://keycloak.com.au:8180
      KC_PROXY: reencrypt
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: password
      PROXY_ADDRESS_FORWARDING: "true"
      CACHE_OWNERS_COUNT: 2
      CACHE_OWNERS_AUTH_SESSIONS_COUNT: 2
      JGROUPS_DISCOVERY_PROTOCOL: JDBC_PING
      JGROUPS_DISCOVERY_EXTERNAL_IP: keycloak.com.au
      JGROUPS_DISCOVERY_PROPERTIES: "datasource_jndi_name=java:jboss/datasources/KeycloakDS,initialize_sql=\"CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name))\",remove_all_data_on_view_change=true"
    networks:
      - local

  kc2:
    container_name: kc2
    build:
      context: .
      args:
        KEYCLOAK_VERSION: latest
    command: ['start', '--optimized']
    depends_on:
      - "postgres"
    environment:
      JAVA_OPTS_APPEND: -Dkeycloak.profile.feature.upload_scripts=enabled
      KC_DB_PASSWORD: postgres
      KC_DB_URL: jdbc:postgresql://postgres/keycloak
      KC_DB_USERNAME: postgres
      KC_HEALTH_ENABLED: 'true'
      KC_HTTP_ENABLED: 'true'
      KC_METRICS_ENABLED: 'true'
      # KC_HOSTNAME: keycloak.com.au
      # KC_HOSTNAME_PORT: 8180
      KC_HOSTNAME_URL: http://keycloak.com.au:8180
      KC_PROXY: reencrypt
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: password
      PROXY_ADDRESS_FORWARDING: "true"
      CACHE_OWNERS_COUNT: 2
      CACHE_OWNERS_AUTH_SESSIONS_COUNT: 2
      JGROUPS_DISCOVERY_PROTOCOL: JDBC_PING
      JGROUPS_DISCOVERY_EXTERNAL_IP: keycloak.com.au
      JGROUPS_DISCOVERY_PROPERTIES: "datasource_jndi_name=java:jboss/datasources/KeycloakDS,initialize_sql=\"CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name))\",remove_all_data_on_view_change=true"
    networks:
      - local

  lb:
    container_name: kc_lb
    image: nginx:alpine
    volumes:
      - ${PWD}/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "8180:8180"
    depends_on:
      - kc1
      - kc2
    networks:
      - local

networks:
  local:
    name: local
    driver: bridge

volumes:
  postgres_data:

Explanation

  • JGROUPS_DISCOVERY_PROTOCOL — JGroups configuration. Its usage has been explained in Keycloak and JDBC Ping
  • JGROUPS_DISCOVERY_EXTERNAL_IP — to make sure you can run with external DNS name, not localhost only
  • CACHE_OWNERS_COUNT and CACHE_OWNERS_AUTH_SESSIONS_COUNT —The number of owners in Infinispan means “how many copies of data” will you have in your cluster. Whenever a node is added to cluster (or removed), Infinispan rebalances the cluster. If you scale down your cluster quickly, you may lose some data. In that case, Keycloak users will have to re-authenticate.
  • JGROUPS_DISCOVERY_PROPERTIES — Another JGroups configuration, just copy it :-)
  • PROXY_ADDRESS_FORWARDING — for cluster, you need enable it to true

Save, and put them under same folder

Let’s start the service

Step 1

update /etc/hosts , add below lines

# keycloak
127.0.0.1 keycloak.com.au

On Windows, the file path is usually: c:\Windows\System32\Drivers\etc\hosts

In many online documents and videos, Keycloak experts often recommend starting the Keycloak service on localhost with a specific port. However, this practice is not advisable, especially when working in a real environment. Instead, it’s more practical to configure Keycloak with a DNS-ready setup. This also allows you to test HTTPS access with SSL certifications later on if needed.

Step 2

docker compose -f docker-compose-cluster.yml up -d
  • Check the health
docker ps -a
  • Check logs with Cluster events
docker logs -f <kc1 or kc2 container id>
  • Check the cluster logs, there should be two members in cluster pool now

Received **new cluster view for channel** ISPN: [b31f28d4c94a-31765|1] (2) [b31f28d4c94a-31765, bc873530c08b-24274] Starting rebalance with members [b31f28d4c94a-31765, bc873530c08b-24274]

Step 3

access http://keycloak.com.au:8180

go with Administration Console, then login with admin / password

Step 4

Test fail over and cluster realiable.

  • kill one keycloak container
docker ps -a
docker rm -f keycloak-compose-kc2

Check logs, you will only see one member in Cluster pool now.

> Updating cache members list [b31f28d4c94a-31765], topology id 6

When you refresh the website http://keycloak.com.au:8180, it takes about 5~10 seconds at first time, then work as normal

Step 5

Restore all services

$ docker compose -f docker-compose-cluster.yml up -d

 ✔ Container db RunningContainer kc1 Started # because I killed it beforeContainer kc2 RunningContainer kc_lb Running

Check logs again, two members in cluster pool now.

> Starting rebalance with members [b31f28d4c94a-31765, 462ae7fcf1a3–41736], phase READ_OLD_WRITE_ALL, topology id 7 > Finished rebalance with members [b31f28d4c94a-31765, 462ae7fcf1a3–41736], topology id 10

If you access http://keycloak.com.au:8180, it still works fine

Conclusion

By following these step-by-step instructions and utilizing the provided code snippets, you can successfully deploy and maintain a Keycloak cluster in seconds. With less adjustment, you can meet the authentication needs of your organization. Embrace the power of clustering to enhance the availability, scalability, and security of your authentication infrastructure.

Codes, please

Yes, I have put these codes together at here

Reference

Keycloak Cluster Setup by Keycloak.org

KEYCLOAK Cluster — Up and Running in Seconds | Niko Köbler (@dasniko)

Keycloak and JDBC Ping

jgroups.org — JDBC_PING

Learning is fun

# keycloak # Security # Cluster # Keyclaok cluster # container # Docker # Docker compose # solution # DevOps

Keycloak
Clustering
DevOps
Docker
Security
Recommended from ReadMedium