Voraussetzung: Zugang zum Namespace
library. Statt eines echten IBM-WAS-Images stehen generische
Red-Hat-UBI-Images stellvertretend für „die kommerzielle Middleware“
– die Kubernetes-Mechanik dahinter ist real und identisch. Schritt 2 liest nur
echte Cluster-Objekte, ohne sie zu ändern.
1 Die Lizenz-Schranke live provozieren
Kommerzielle Images prüfen im Entrypoint eine Lizenz-Variable, bevor der Server startet.
Kommerzielle IBM-Images (WAS, MQ, Db2) haben im Entrypoint eine Lizenz-Schranke: ohne LICENSE=accept gibt der Container die Lizenzbedingungen aus und beendet sich mit einem Fehlercode. Auf OpenShift sieht man das als CrashLoopBackOff, in den Logs steht der Lizenztext. Der Fix ist eine einzige Environment-Variable – die Frage dahinter ist, wer die Zustimmung verantwortet.
LICENSE, bevor der App-Server startet. Ohne LICENSE=accept wird der Lizenztext ausgegeben und der Container beendet sich mit Fehlercode – in Kubernetes ein CrashLoopBackOff, die Diagnose steht als eine Zeile in oc logs. Der Fix ist ein env-Eintrag im Deployment. Die Frage dahinter ist organisatorisch: die Zustimmung ist rechtlich bindend, sie gehört sichtbar ins Repo und muss von jemandem verantwortet werden.- kommerzielle IBM-Images (WAS, MQ, Db2) haben im Entrypoint eine Lizenz-Schranke – sie läuft vor jedem Server-Start
- ohne
LICENSE=acceptgibt der Container die Lizenzbedingungen aus und beendet sich mit Fehlercode LICENSE=viewdruckt nur die Bedingungen und beendet mit Code 0 – zum Nachlesen vor der Zustimmung- in Kubernetes sieht man das als
CrashLoopBackOffmit steigendemrestartCount, die Readiness-Probe wird nie grün - die ganze Diagnose ist eine Zeile in
oc logs:You must accept the license terms ... Set LICENSE=accept - typischer Irrweg: man sucht einen Konfig-, Port- oder Speicherfehler statt zuerst in die Logs zu schauen
- tritt oft erst bei der Übersetzung von
docker run -e LICENSE=acceptins Deployment auf – die Variable wird vergessen - Fix: im Deployment unter
envein Eintragname: LICENSE/value: accept - alternativ als Build-Argument
--build-arg LICENSE=acceptbeim abgeleiteten Image oder über eine Datei, die der Entrypoint liest - abzugrenzen von
ImagePullBackOff(I2) – dort startet das Image gar nicht – und vonOOMKilledmit eigenemReason - der eigentliche Punkt: die Zustimmung ist rechtlich bindend – Betrieb, Einkauf oder Recht muss sie verantworten, nicht der Entwickler nebenbei
- die Lizenzbedingungen und die IBM-Lizenz-URL gehören ins Repo, neben das Deployment
LICENSE=acceptper Default in ein Basis-Image zu backen nimmt dem Betreiber die bewusste Entscheidung – besser explizit im Deployment- Concordia: das WAS-Image von IBM –
LICENSE=acceptsteht im Deployment und ist im Merge-Request dokumentiert - verwandt: MQ und Db2 nutzen dasselbe
LICENSE, MS SQL nutztACCEPT_EULA=Y, UBI-Images brauchen nichts davon
# der Entrypoint einer typischen kommerziellen Middleware, nachgebaut:
cat <<'EOF' | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-concordia
namespace: library
spec:
replicas: 1
selector: { matchLabels: { app: demo-concordia } }
template:
metadata: { labels: { app: demo-concordia } }
spec:
containers:
- name: was
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command:
- sh
- -c
- |
if [ "$LICENSE" != "accept" ]; then
echo "You must accept the license terms to start this container.";
echo "Set LICENSE=accept in the environment.";
exit 1;
fi
echo "Lizenz akzeptiert, Server startet"; sleep 3600
EOF
oc get pods -n library -l app=demo-concordia -w
# CrashLoopBackOff, restartCount steigt
oc logs -n library -l app=demo-concordia
# "You must accept the license terms..." - die ganze Diagnose in einer Zeile
oc set env deployment/demo-concordia -n library LICENSE=accept
oc rollout status deployment/demo-concordia -n library
oc logs -n library -l app=demo-concordia
# "Lizenz akzeptiert, Server startet"
Genau das Symptom aus dem Atlas: CrashLoopBackOff mit steigendem
restartCount, die Readiness-Probe wird nie grün, und die ganze Diagnose
steht in einer Zeile oc logs. Der typische Irrweg ist, zuerst
einen Konfig- oder Portfehler zu suchen, statt zuerst in die Logs zu schauen.
Wer entscheidetDie Zustimmung ist rechtlich bindend – sie gehört explizit ins Deployment und ins Repo, nicht in ein Basis-Image gebacken. Betrieb, Einkauf oder Recht verantworten sie, nicht der Entwickler nebenbei.
2 Pull-Secret und Entitlement prüfen
Volle RHEL-Pakete brauchen zwei getrennte Dinge – ein Pull-Secret und ein Entitlement. UBI braucht keins von beidem.
Ein Build, der volle RHEL-Pakete braucht (nicht nur UBI), muss sich bei Red Hat ausweisen. Das Basis-Image aus registry.redhat.io braucht ein Pull-Secret mit Zugangsdaten von console.redhat.com, und ein dnf install aus den vollen RHEL-Repos braucht ein Entitlement-Zertifikat unter /etc/pki/entitlement. UBI-Images brauchen keins von beidem.
registry.redhat.io braucht einen registry.redhat.io-Eintrag im Cluster-Pull-Secret (Token von console.redhat.com) – sonst scheitert schon der FROM-Pull mit unauthorized. Ein dnf install aus den vollen RHEL-Repos braucht zusätzlich das Entitlement-Zertifikat unter /etc/pki/entitlement, das die BuildConfig in den Build-Container mountet – sonst Repository ... is not available. UBI-Images brauchen keins von beidem. Bei Concordia liegt das WAS-Image in icr.io mit eigenem Key, die eigenen Layer bleiben auf UBI.- ein Build, der nur UBI-Inhalte braucht, kommt ohne alles aus – Images unter
registry.access.redhat.comsind frei ziehbar - die
dnf-Reposubi,ubi-baseos,ubi-appstreambrauchen keine Zugangsdaten – das deckt die meisten Java-/Python-Workloads ab - wenn UBI reicht, nimm UBI –
ubi-minimalmitmicrodnfist ~40 MB und ein guter Runtime-Layer (C3, I3) - volle RHEL-Pakete brauchen zwei getrennte Dinge: ein Pull-Secret und ein Entitlement
- 1 · Pull-Secret: das Cluster-Secret
openshift-config/pull-secretbraucht einen Eintrag fürregistry.redhat.io - die Zugangsdaten sind ein Service-Account-Token von
console.redhat.com– fehlt der Eintrag, scheitert schon derFROM-Pull - 2 · Entitlement: der Cluster stellt die Zertifikate als
etc-pki-entitlementinopenshift-config-managedbereit - die
BuildConfigmountet sie nach/etc/pki/entitlement– dann findetdnfdie vollen RHEL-Repos - Symptom
unauthorized: authentication requiredbeimFROM→ Pull-Secret-Eintrag fürregistry.redhat.iofehlt (B10) - Symptom
Repository ... is not availableoderUnable to find a matchbeimdnf-Schritt → Entitlement-Mount fehlt - abzugrenzen:
manifest unknownist ein falscher Tag, nicht Auth –no space leftist Node-Disk (I3) - Concordia: das WAS-Liberty-Image liegt in
icr.iomit eigenem Entitlement-Key – eigener Pull-Secret-Eintrag - die eigenen Build-Layer von Concordia bleiben auf UBI und brauchen kein RHEL-Entitlement
- OCP-Unterschied: der Entitlement-Weg ist OpenShift-spezifisch – auf reinem Kubernetes legt man die Zertifikate selbst als Secret an
# 1 - Pull-Secret: steht ein Eintrag fuer registry.redhat.io drin?
oc get secret pull-secret -n openshift-config \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d \
| grep -o '"registry.redhat.io"' || echo "kein Eintrag fuer registry.redhat.io"
# 2 - Entitlement: liegen die Zertifikate im Cluster bereit?
oc get secret etc-pki-entitlement -n openshift-config-managed --ignore-not-found
oc get secret etc-pki-entitlement -n openshift-config-managed \
-o jsonpath='{.data}' 2>/dev/null | grep -o '"[^"]*\.pem"' || echo "kein Entitlement-Secret gefunden"
# zum Vergleich: ein reiner UBI-Pull braucht nichts davon
oc run demo-ubi-pull -n library --image=registry.access.redhat.com/ubi9/ubi-minimal:latest \
--restart=Never -- true
oc get pod demo-ubi-pull -n library
# Completed, ganz ohne Pull-Secret-Eintrag fuer registry.redhat.io
Fehlt der Pull-Secret-Eintrag, scheitert schon der FROM-Pull mit
unauthorized: authentication required. Fehlt der Entitlement-Mount, scheitert
erst der dnf-Schritt mit Repository ... is not available –
der Build kommt also weiter, bevor er bricht. Beide Fehler abzugrenzen von
manifest unknown (ein falscher Tag) und no space left
(Node-Disk, Schritt 3).
3 Image-Größen real vergleichen
Ein WAS-traditional-Image ist 1,5 bis 2,5 GB, Open Liberty 200 bis 400 MB, ein UBI-Fat-Jar rund 150 MB – ohne eigene Builds real nachprüfbar.
Ein WAS-traditional-Image ist 1,5 bis 2,5 GB. Open Liberty liegt bei 200 bis 400 MB, ein Spring-Boot-Fat-Jar auf UBI bei etwa 150 MB. Die Größe kostet an vier Stellen: langsamer Pull, Disk-Druck auf den Nodes, jeder Rollout schiebt Gigabytes pro Node, die Registry läuft voll. Der größte Hebel ist die Layer-Reihenfolge – das Stabile nach unten, den Code nach oben.
DiskPressure-Eviction auf vollen Nodes, jeder Rollout schiebt fast das ganze Image an jeden Ziel-Node, die Registry wächst mit jeder Build-Nummer. Der größte Hebel ist die Layer-Reihenfolge: Basis-OS und Runtime nach unten, die WAR nach oben, dann trifft ein Commit nur den obersten kleinen Layer. Danach: Open Liberty statt WAS traditional, gezielte Feature-Liste, eine ubi-minimal-Runtime-Stage (C3), alte Tags räumen.- ein WAS-traditional-Image ist 1,5 bis 2,5 GB – Open Liberty 200 bis 400 MB, ein Spring-Boot-Fat-Jar auf UBI etwa 150 MB
- die eigene App sind meist unter 30 MB – der Rest ist der Application Server
- Folge 1 – langsamer Pull: ein neuer Node oder Cache-Miss zieht das ganze Image, bevor der Pod startet, das verlängert Rollout und Scale-out
- Folge 2 – Disk-Druck: viele große Images füllen den Image-Store, ab der Schwelle Kubelet-GC und im Extremfall
EvictedwegenDiskPressure - Folge 3 – Rollout-Kosten: jede Version schiebt die geänderten Layer an jeden Ziel-Node – bei einem Monolith-Layer fast das ganze Image
- Folge 4 – Registry füllt sich: jeder Build ein neues Manifest, ohne Aufräum-Policy wächst der Storage linear mit der Build-Zahl (C5)
- größter Hebel – Layer-Reihenfolge: Stabiles (OS, Runtime, JDK) nach unten, den Code (WAR, Fat Jar) nach oben
- Dependencies vor dem App-Code kopieren (
pom.xmlzuerst, dannmvn, dannsrc) – dann trifft ein Commit nur den obersten Layer imagePullPolicy: IfNotPresentstattAlwayslässt den Node-Cache wirken – sicher mit unveränderlichen Tags (Digest oder:git-sha)- auf Open Liberty wechseln und in Liberty nur die genutzten Features aktivieren statt
javaee-8komplett - chained build: eine
ubi-minimal-Runtime-Stage mit nur JRE plus App – die Build-Werkzeuge bleiben draußen (C3) - kritische Nodes vor-pullen (DaemonSet oder Node-Image-Preload), damit der erste Pod nicht auf 2 GB wartet
- alte Tags räumen mit
skopeo deleteoder dem Registry-Pruner,.dockerignorehält.gitund Build-Caches aus dem Image (C5) - Concordia: Liberty-Image, Feature-Liste auf
jaxrs-2.1, cdi-2.0, jpa-2.2reduziert, die App-WAR ist der oberste Layer und wenige MB - OCP-Unterschied: keiner an den Mechanismen – die integrierte Registry und der
Image-Pruner sind OpenShift-Komfort
# reale, oeffentlich ziehbare Images vergleichen (kein Pull noetig, nur Metadaten):
for img in \
registry.access.redhat.com/ubi9/ubi-minimal:latest \
registry.access.redhat.com/ubi9/openjdk-17:latest \
icr.io/appcafe/open-liberty:full-java17-openj9-ubi
do
echo "== $img =="
oc image info "$img" 2>/dev/null | grep -E '^(Digest|Size)' \
|| skopeo inspect "docker://$img" 2>/dev/null | grep -E '"Digest"|"Size"' \
|| echo " (Registry nicht erreichbar von hier - Werte laut Hersteller-Doku pruefen)"
done
Der größte Hebel bleibt die Layer-Reihenfolge: Stabiles (OS,
Runtime, JDK) nach unten, den Code (WAR, Fat Jar) nach oben, Dependencies vor dem
App-Code kopieren – dann trifft ein Commit nur den obersten Layer. Ein
chained build mit einer ubi-minimal-Runtime-Stage
(build-kette-openshift.html)
lässt die Build-Werkzeuge draußen. Alte Tags räumt
skopeo delete oder der Registry-Pruner auf.
4 Die Admin-Konsole per passthrough erreichen
Eine Admin-Konsole mit selbstsigniertem Zertifikat bricht an einer
edge-Route – passthrough reicht TLS unverändert
durch.
Die WAS-Admin-Konsole läuft auf 9043/9443 mit einem selbstsignierten Zertifikat. Man kann sie über eine passthrough-Route erreichbar machen – aber die Konsole schreibt Konfiguration in den laufenden Server, skaliert nicht und ist nicht reproduzierbar. Der richtige Weg ist wsadmin-Scripting zur Build- oder Init-Zeit: Konfiguration als Code.
passthrough-Route – der Router reicht die TLS-Verbindung unverändert an den Pod durch, weil edge am selbstsignierten Konsolen-Zertifikat bricht und passthrough kein Pfad-Routing kann. Aber was du in der Konsole klickst, kennt nur ein Pod und nur bis zum nächsten Neustart (H2). Der richtige Weg ist wsadmin-Scripting zur Build- oder Init-Zeit, in Liberty die server.xml plus configDropins (H5) – im Git-Repo, mit Review und Historie (E3).- die WAS-Admin-Konsole läuft auf
9043/9443mit einem selbstsignierten Zertifikat, das der Server selbst terminiert - eine
edge- oderreencrypt-Route bricht am self-signed Cert und an der Client-Cert-Erwartung der Konsole - also braucht es eine
passthrough-Route:termination: passthrough,targetPort: 9443, der Router reicht TLS unverändert durch passthroughkann kein Pfad-Routing – es kennt nur den Host aus der SNI, bevor entschlüsselt wird- Nachteil: die Konsole schreibt in die Config eines laufenden Servers – bei mehreren Replicas trifft man einen zufälligen Pod (H3)
- Nachteil: die Änderung ist beim nächsten Pod-Neustart weg, weil das Dateisystem nicht persistent ist (H2)
- Nachteil: keine Historie, kein Review, kein Rollback – und zwei Admins, die gleichzeitig speichern, überschreiben sich
- Nachteil: die Konsole hält selbst JVM-State – sie skaliert nicht und ist nicht reproduzierbar
- legitim: ein einmaliger Blick auf Laufzeit-MBeans, ein Thread-Dump, akute Analyse – mit
NetworkPolicyeinsperren - richtiger Weg –
wsadmin: Jython- oder Jacl-Skripte machen dieselben Config-Änderungen wie die Konsole - die Skripte laufen als
RUN-Schritt beim Image-Bau oder als init-Jobvor dem Rollout – jeder frische Pod ist gleich konfiguriert - in Liberty noch direkter:
server.xmlplusconfigDropins/overridessind schon die Konfiguration als Code, Werte aus ConfigMap und Secret (H5) - beiden gemeinsam: die Dateien liegen im Git-Repo, jede Änderung ist ein Merge-Request mit Review und Historie (E3)
- Concordia: Konsole nur als
passthrough-Route mitNetworkPolicyfür den Notfall, die Konfiguration ist ein Satz Jython-Skripte im init-Job - OCP-Unterschied:
passthroughist ein OpenShift-Route-Begriff – auf Ingress-NGINX heißt esssl-passthrough
# ein selbstsignierter HTTPS-Testserver, steht fuer die WAS-Admin-Konsole:
cat <<'EOF' | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-console
namespace: library
spec:
replicas: 1
selector: { matchLabels: { app: demo-console } }
template:
metadata: { labels: { app: demo-console } }
spec:
containers:
- name: tls-testserver
image: registry.access.redhat.com/ubi9/ubi-minimal:latest
command:
- sh
- -c
- |
microdnf install -y openssl >/dev/null 2>&1
openssl req -x509 -newkey rsa:2048 -keyout /tmp/key.pem -out /tmp/cert.pem \
-days 1 -nodes -subj "/CN=demo-console" 2>/dev/null
openssl s_server -key /tmp/key.pem -cert /tmp/cert.pem -accept 9443 -www
ports:
- containerPort: 9443
EOF
oc expose deployment/demo-console -n library --port=9443 --target-port=9443
cat <<'EOF' | oc apply -f -
apiVersion: route.openshift.io/v1
kind: Route
metadata:
name: demo-console
namespace: library
spec:
to: { kind: Service, name: demo-console }
port: { targetPort: 9443 }
tls: { termination: passthrough }
EOF
HOST=$(oc get route demo-console -n library -o jsonpath='{.spec.host}')
curl -sk "https://$HOST/" | head -5
# openssl s_server antwortet mit dem eigenen, selbstsignierten Zertifikat -
# genau das, was eine edge-Route nicht durchlassen wuerde
passthrough kennt nur den Host aus der SNI, bevor entschlüsselt wird
– deshalb kann es kein Pfad-Routing. Der Nachteil bleibt derselbe wie bei jeder
Admin-Konsole mit mehreren Replicas: sie schreibt in einen laufenden Pod, die
Änderung ist beim nächsten Neustart weg
(H2/H3),
keine Historie, kein Review. Legitim nur für einen einmaligen Blick – mit einer
NetworkPolicy eingesperrt.
5 Typische Fallen
| Symptom | meist |
|---|---|
CrashLoopBackOff ohne offensichtlichen Grund | zuerst oc logs prüfen – oft steht die Ursache in der ersten Zeile (Lizenz, fehlende Env) |
unauthorized: authentication required beim FROM | Pull-Secret-Eintrag für registry.redhat.io fehlt |
Repository ... is not available beim dnf-Schritt | Entitlement-Mount fehlt, nicht das Pull-Secret |
| Route liefert einen Zertifikatsfehler für die Admin-Konsole | edge/reencrypt statt passthrough gewählt |
| Konsolen-Änderung nach Rollout wieder weg | erwartet – die Konsole schreibt in einen nicht-persistenten Pod, kein Ersatz für Konfiguration als Code |
6 Wie das Projekt es macht
Die Bibliothek nutzt keine kommerzielle Middleware – alle Images
bauen auf UBI, brauchen weder Lizenz-Variable noch Entitlement
(build-kette-openshift.html).
Diese Anleitung zeigt das Muster für den Fall, dass doch einmal ein kommerzielles
Image dazukommt: LICENSE=accept explizit im Deployment, dokumentiert im
Merge-Request – die Concordia-Referenz im Atlas macht genau das. Die eigenen
Build-Layer bleiben dabei immer auf UBI, nur das Basis-Image der Middleware selbst
braucht das Entitlement.