From 82f836357aaf6a4a216633f70c8427d57f952492 Mon Sep 17 00:00:00 2001 From: Kirby Chin <37311900+kabicin@users.noreply.github.com> Date: Mon, 17 Aug 2026 12:58:07 -0400 Subject: [PATCH 1/5] Disable httpPort in server.xml --- Dockerfile | 11 +++++++---- src/main/liberty/config/server.xml | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8f7ad429..f032decb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM icr.io/appcafe/open-liberty:kernel-slim-java11-openj9-ubi-minimal ARG VERSION=1.0 ARG REVISION=SNAPSHOT +ARG SKIP_LINPERF=false LABEL \ org.opencontainers.image.authors="Alasdair Nottingham" \ @@ -15,10 +16,12 @@ LABEL \ summary="Sample app running on Open Liberty that uses Eclipse MicroProfile" \ description="This image contains a sample application that displays the Java system properties and demonstrates MicroProfile Config, Health and Metrics." -# Install required packages to run linperf.sh -USER 0 -RUN command -v yum && pkgcmd=yum || pkgcmd=microdnf && ($pkgcmd update -y && $pkgcmd install -y procps-ng net-tools ncurses hostname) -USER 1001 +# Install required packages for running the Liberty MustGather (linperf.sh) script. +RUN if [ "$SKIP_LINPERF" != "true" ]; then \ + PKG_MGR=$(command -v dnf || command -v microdnf) && \ + $PKG_MGR install -y procps-ng net-tools ncurses hostname && \ + $PKG_MGR clean all; \ + fi COPY --chown=1001:0 src/main/liberty/config/ /config/ diff --git a/src/main/liberty/config/server.xml b/src/main/liberty/config/server.xml index 7f92a611..118d712c 100644 --- a/src/main/liberty/config/server.xml +++ b/src/main/liberty/config/server.xml @@ -8,13 +8,16 @@ mpConfig-3.1 - + + + + + - - + From 90fdd4e07b1600af037276be666e5438b1896806 Mon Sep 17 00:00:00 2001 From: Kirby Chin <37311900+kabicin@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:59:34 -0400 Subject: [PATCH 2/5] Temporary grant root access for installing packages --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index f032decb..95ba9a86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,11 +17,13 @@ LABEL \ description="This image contains a sample application that displays the Java system properties and demonstrates MicroProfile Config, Health and Metrics." # Install required packages for running the Liberty MustGather (linperf.sh) script. +USER 0 RUN if [ "$SKIP_LINPERF" != "true" ]; then \ PKG_MGR=$(command -v dnf || command -v microdnf) && \ $PKG_MGR install -y procps-ng net-tools ncurses hostname && \ $PKG_MGR clean all; \ fi +USER 1001 COPY --chown=1001:0 src/main/liberty/config/ /config/ From e56916b3d0092e0fe7b83b8abf9034a5aecb1a21 Mon Sep 17 00:00:00 2001 From: Kirby Chin <37311900+kabicin@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:25:48 -0400 Subject: [PATCH 3/5] Add mpJwt config for mpMetrics --- Dockerfile | 3 ++ dev/getting-started-pod.yaml | 87 ++++++++++++++++++++++++++++++ dev/keycloak-realm.json | 27 ++++++++++ dev/sso.xml | 21 ++++++++ docker-compose.yml | 37 +++++++++++++ src/main/liberty/config/server.xml | 5 +- 6 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 dev/getting-started-pod.yaml create mode 100644 dev/keycloak-realm.json create mode 100644 dev/sso.xml create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 95ba9a86..752181fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,9 @@ USER 1001 COPY --chown=1001:0 src/main/liberty/config/ /config/ +# Bundle the dev SSO for mpMetrics. You can override at deploy time by mounting a different sso.xml +COPY --chown=1001:0 dev/sso.xml /config/configDropins/overrides/sso.xml + RUN features.sh COPY --chown=1001:0 target/*.war /config/apps/ diff --git a/dev/getting-started-pod.yaml b/dev/getting-started-pod.yaml new file mode 100644 index 00000000..4b810ed6 --- /dev/null +++ b/dev/getting-started-pod.yaml @@ -0,0 +1,87 @@ +--- +# Development Pod: Liberty app + Keycloak sidecar +# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080. +# +# To deploy: +# kubectl create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json +# kubectl apply -f dev/getting-started-pod.yaml +# +# To get a token and hit /metrics (after port-forwarding): +# kubectl port-forward pod/getting-started 9443:9443 8080:8080 +# +# TOKEN=$(curl -s -X POST \ +# http://localhost:8080/realms/liberty/protocol/openid-connect/token \ +# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \ +# | jq -r .access_token) +# +# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics +# +# In production: remove the keycloak container and sso-override volume, and provide +# a Secret containing MP_JWT_ISSUER + MP_JWT_JWKS_URI pointing at your real IdP. +apiVersion: v1 +kind: Pod +metadata: + name: getting-started + labels: + app: getting-started +spec: + initContainers: + # Wait for Keycloak to finish importing the realm before Liberty starts + - name: wait-for-keycloak + image: curlimages/curl:8.8.0 + command: + - sh + - -c + - | + until curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration; do + echo "Waiting for Keycloak realm..."; sleep 5; + done + + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:25.0 + args: ["start-dev", "--import-realm"] + env: + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + value: admin + volumeMounts: + - name: realm-config + mountPath: /opt/keycloak/data/import + ports: + - containerPort: 8080 + + - name: app + image: icr.io/appcafe/open-liberty/samples/getting-started:latest + volumeMounts: + # Mount the dev SSO include; in production replace with a real sso.xml via Secret/ConfigMap + - name: sso-override + mountPath: /config/configDropins/overrides/sso.xml + subPath: sso.xml + ports: + - containerPort: 9443 + + volumes: + - name: realm-config + configMap: + name: keycloak-realm + - name: sso-override + configMap: + name: liberty-sso-dev +--- +# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost +apiVersion: v1 +kind: ConfigMap +metadata: + name: liberty-sso-dev +data: + sso.xml: | + + + + + diff --git a/dev/keycloak-realm.json b/dev/keycloak-realm.json new file mode 100644 index 00000000..ff7b6a29 --- /dev/null +++ b/dev/keycloak-realm.json @@ -0,0 +1,27 @@ +{ + "realm": "liberty", + "enabled": true, + "clients": [ + { + "clientId": "metrics-client", + "enabled": true, + "publicClient": false, + "secret": "metrics-secret", + "directAccessGrantsEnabled": true, + "defaultClientScopes": ["profile", "email"] + } + ], + "users": [ + { + "username": "metrics", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "metrics", + "temporary": false + } + ] + } + ] +} diff --git a/dev/sso.xml b/dev/sso.xml new file mode 100644 index 00000000..063a78d9 --- /dev/null +++ b/dev/sso.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..2ce345d7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +services: + + keycloak: + image: quay.io/keycloak/keycloak:25.0 + command: start-dev --import-realm + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + volumes: + - ./dev/keycloak-realm.json:/opt/keycloak/data/import/realm.json:ro + ports: + - "8080:8080" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration || exit 1"] + interval: 10s + timeout: 5s + retries: 12 + + app: + build: . + volumes: + # Mount the dev SSO include file into the Liberty config includes directory + - ./dev/sso.xml:/config/configDropins/overrides/sso.xml:ro + ports: + - "9443:9443" + depends_on: + keycloak: + condition: service_healthy + +# To get a token and hit /metrics: +# +# TOKEN=$(curl -s -X POST \ +# http://localhost:8080/realms/liberty/protocol/openid-connect/token \ +# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \ +# | jq -r .access_token) +# +# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics diff --git a/src/main/liberty/config/server.xml b/src/main/liberty/config/server.xml index 118d712c..21dec806 100644 --- a/src/main/liberty/config/server.xml +++ b/src/main/liberty/config/server.xml @@ -6,13 +6,16 @@ mpMetrics-5.1 mpHealth-4.0 mpConfig-3.1 + + mpJwt-2.1 - + + From 8b61ef53dee68a624dee1381449fdb74ca3b2749 Mon Sep 17 00:00:00 2001 From: Kirby Chin <37311900+kabicin@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:20:04 -0400 Subject: [PATCH 4/5] Add SSO sample with Open Liberty Operator --- dev/getting-started-app.yaml | 68 ++++++++++++++++++++++++++++ dev/getting-started-pod.yaml | 87 ------------------------------------ dev/keycloak-realm.json | 17 +++++++ dev/sso.xml | 7 ++- 4 files changed, 88 insertions(+), 91 deletions(-) create mode 100644 dev/getting-started-app.yaml delete mode 100644 dev/getting-started-pod.yaml diff --git a/dev/getting-started-app.yaml b/dev/getting-started-app.yaml new file mode 100644 index 00000000..6acf08ef --- /dev/null +++ b/dev/getting-started-app.yaml @@ -0,0 +1,68 @@ +--- +# Development OpenLibertyApplication: Liberty app + Keycloak sidecar +# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080. +# +# Prerequisites: +# oc create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json +# oc apply -f dev/getting-started-app.yaml +# +# To hit the /metrics endpoint run: +# ./dev/metrics.sh +# +# In production: remove sidecarContainers, and provide a Secret +# containing the real sso.xml mounted at /config/configDropins/overrides/sso.xml. +apiVersion: apps.openliberty.io/v1 +kind: OpenLibertyApplication +metadata: + name: getting-started +spec: + applicationImage: icr.io/appcafe/open-liberty/samples/getting-started:latest + replicas: 1 + + service: + port: 9443 + + # Dev Keycloak sidecar — shares localhost with the Liberty container + sidecarContainers: + - name: keycloak + image: quay.io/keycloak/keycloak:25.0 + args: ["start-dev", "--import-realm"] + env: + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + value: admin + volumeMounts: + - name: realm-config + mountPath: /opt/keycloak/data/import + ports: + - containerPort: 8080 + + # Mount the dev sso.xml; in production replace with a Secret mount pointing at your real IdP + volumeMounts: + - name: sso-override + mountPath: /config/configDropins/overrides/sso.xml + subPath: sso.xml + + volumes: + - name: realm-config + configMap: + name: keycloak-realm + - name: sso-override + configMap: + name: liberty-sso-dev +--- +# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost +apiVersion: v1 +kind: ConfigMap +metadata: + name: liberty-sso-dev +data: + sso.xml: | + + + + + diff --git a/dev/getting-started-pod.yaml b/dev/getting-started-pod.yaml deleted file mode 100644 index 4b810ed6..00000000 --- a/dev/getting-started-pod.yaml +++ /dev/null @@ -1,87 +0,0 @@ ---- -# Development Pod: Liberty app + Keycloak sidecar -# Containers in the same Pod share localhost — Keycloak is reachable at localhost:8080. -# -# To deploy: -# kubectl create configmap keycloak-realm --from-file=realm.json=dev/keycloak-realm.json -# kubectl apply -f dev/getting-started-pod.yaml -# -# To get a token and hit /metrics (after port-forwarding): -# kubectl port-forward pod/getting-started 9443:9443 8080:8080 -# -# TOKEN=$(curl -s -X POST \ -# http://localhost:8080/realms/liberty/protocol/openid-connect/token \ -# -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \ -# | jq -r .access_token) -# -# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/metrics -# -# In production: remove the keycloak container and sso-override volume, and provide -# a Secret containing MP_JWT_ISSUER + MP_JWT_JWKS_URI pointing at your real IdP. -apiVersion: v1 -kind: Pod -metadata: - name: getting-started - labels: - app: getting-started -spec: - initContainers: - # Wait for Keycloak to finish importing the realm before Liberty starts - - name: wait-for-keycloak - image: curlimages/curl:8.8.0 - command: - - sh - - -c - - | - until curl -sf http://localhost:8080/realms/liberty/.well-known/openid-configuration; do - echo "Waiting for Keycloak realm..."; sleep 5; - done - - containers: - - name: keycloak - image: quay.io/keycloak/keycloak:25.0 - args: ["start-dev", "--import-realm"] - env: - - name: KEYCLOAK_ADMIN - value: admin - - name: KEYCLOAK_ADMIN_PASSWORD - value: admin - volumeMounts: - - name: realm-config - mountPath: /opt/keycloak/data/import - ports: - - containerPort: 8080 - - - name: app - image: icr.io/appcafe/open-liberty/samples/getting-started:latest - volumeMounts: - # Mount the dev SSO include; in production replace with a real sso.xml via Secret/ConfigMap - - name: sso-override - mountPath: /config/configDropins/overrides/sso.xml - subPath: sso.xml - ports: - - containerPort: 9443 - - volumes: - - name: realm-config - configMap: - name: keycloak-realm - - name: sso-override - configMap: - name: liberty-sso-dev ---- -# ConfigMap holding the dev sso.xml — points mpJwt at the Keycloak sidecar on localhost -apiVersion: v1 -kind: ConfigMap -metadata: - name: liberty-sso-dev -data: - sso.xml: | - - - - - diff --git a/dev/keycloak-realm.json b/dev/keycloak-realm.json index ff7b6a29..fec6432d 100644 --- a/dev/keycloak-realm.json +++ b/dev/keycloak-realm.json @@ -1,6 +1,21 @@ { "realm": "liberty", "enabled": true, + "requiredCredentials": ["password"], + "attributes": { + "userProfileEnabled": "false" + }, + "requiredActions": [ + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": false, + "defaultAction": false, + "priority": 90, + "config": {} + } + ], "clients": [ { "clientId": "metrics-client", @@ -15,6 +30,8 @@ { "username": "metrics", "enabled": true, + "emailVerified": true, + "requiredActions": [], "credentials": [ { "type": "password", diff --git a/dev/sso.xml b/dev/sso.xml index 063a78d9..f145b6ab 100644 --- a/dev/sso.xml +++ b/dev/sso.xml @@ -1,5 +1,5 @@ + jwksUri="${MP_JWT_JWKS_URI}"/> From a9a49cd091f3a6f506b7e860d936993de1e10322 Mon Sep 17 00:00:00 2001 From: Kirby Chin <37311900+kabicin@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:24:20 -0400 Subject: [PATCH 5/5] Create metrics.sh --- dev/metrics.sh | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100755 dev/metrics.sh diff --git a/dev/metrics.sh b/dev/metrics.sh new file mode 100755 index 00000000..d43d9905 --- /dev/null +++ b/dev/metrics.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# dev/metrics.sh — fetch the mpMetrics endpoint from the getting-started pod +# +# Usage: +# ./dev/metrics.sh # all metrics (default) +# ./dev/metrics.sh /metrics/base # base metrics +# ./dev/metrics.sh /metrics/application # application metrics + +set -uo pipefail # no -e: port-forward drops are handled explicitly + +METRICS_PATH="${1:-/metrics}" +LABEL="app.kubernetes.io/name=getting-started" +LIBERTY_PORT=9443 +KEYCLOAK_PORT=8080 +PF_PID="" + +cleanup() { [[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null; } +trap cleanup EXIT + +start_portforward() { + [[ -n "$PF_PID" ]] && kill "$PF_PID" 2>/dev/null + oc port-forward "$POD" "${LIBERTY_PORT}:${LIBERTY_PORT}" "${KEYCLOAK_PORT}:${KEYCLOAK_PORT}" \ + 2>/dev/null & + PF_PID=$! +} + +# ── 1. Resolve pod name ────────────────────────────────────────────────────── +POD=$(oc get pod -l "$LABEL" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) +if [[ -z "$POD" ]]; then + echo "ERROR: no pod found with label $LABEL" >&2 + exit 1 +fi +echo "Using pod: $POD" + +# ── 2. Wait for Keycloak container to be ready ─────────────────────────────── +echo "Waiting for Keycloak container to be ready..." +oc wait pod "$POD" --for=condition=Ready --timeout=300s 2>/dev/null || true + +# ── 3. Port-forward and wait for Keycloak OIDC endpoint ───────────────────── +echo "Waiting for Keycloak realm..." +start_portforward +for i in $(seq 1 90); do + # Restart port-forward if it died + if ! kill -0 "$PF_PID" 2>/dev/null; then + sleep 3 + start_portforward + fi + curl -sf "http://localhost:${KEYCLOAK_PORT}/realms/liberty/.well-known/openid-configuration" \ + -o /dev/null 2>/dev/null && break + sleep 3 +done + +# ── 4. Obtain JWT from Keycloak ────────────────────────────────────────────── +echo "Obtaining token..." +TOKEN=$(curl -sf -X POST \ + "http://localhost:${KEYCLOAK_PORT}/realms/liberty/protocol/openid-connect/token" \ + -d "grant_type=password&client_id=metrics-client&client_secret=metrics-secret&username=metrics&password=metrics" \ + | jq -r .access_token) + +if [[ -z "$TOKEN" ]] || [[ "$TOKEN" == "null" ]]; then + echo "ERROR: failed to obtain token — check Keycloak logs with:" >&2 + echo " oc logs $POD -c keycloak | grep -i error" >&2 + exit 1 +fi +echo "Token obtained." + +# ── 5. Fetch metrics ───────────────────────────────────────────────────────── +echo "" +echo "GET https://localhost:${LIBERTY_PORT}${METRICS_PATH}" +echo "────────────────────────────────────────────────────" +HTTP_CODE=$(curl -sk -o /tmp/metrics_response.txt -w "%{http_code}" \ + -H "Authorization: Bearer $TOKEN" \ + "https://localhost:${LIBERTY_PORT}${METRICS_PATH}") +echo "HTTP $HTTP_CODE" +cat /tmp/metrics_response.txt