diff --git a/Dockerfile b/Dockerfile index 8f7ad429..752181fd 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,13 +16,20 @@ 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 +# Install required packages for running the Liberty MustGather (linperf.sh) script. USER 0 -RUN command -v yum && pkgcmd=yum || pkgcmd=microdnf && ($pkgcmd update -y && $pkgcmd install -y procps-ng net-tools ncurses hostname) +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/ +# 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-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/keycloak-realm.json b/dev/keycloak-realm.json new file mode 100644 index 00000000..fec6432d --- /dev/null +++ b/dev/keycloak-realm.json @@ -0,0 +1,44 @@ +{ + "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", + "enabled": true, + "publicClient": false, + "secret": "metrics-secret", + "directAccessGrantsEnabled": true, + "defaultClientScopes": ["profile", "email"] + } + ], + "users": [ + { + "username": "metrics", + "enabled": true, + "emailVerified": true, + "requiredActions": [], + "credentials": [ + { + "type": "password", + "value": "metrics", + "temporary": false + } + ] + } + ] +} 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 diff --git a/dev/sso.xml b/dev/sso.xml new file mode 100644 index 00000000..f145b6ab --- /dev/null +++ b/dev/sso.xml @@ -0,0 +1,20 @@ + + + + + + + + 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 7f92a611..21dec806 100644 --- a/src/main/liberty/config/server.xml +++ b/src/main/liberty/config/server.xml @@ -6,15 +6,21 @@ mpMetrics-5.1 mpHealth-4.0 mpConfig-3.1 + + mpJwt-2.1 - + + + + + + - - +