← Übersicht  ·  Skripte & Dateien  ·  cloud-host · OpenShift · Build-Kette

Die Build-Kette in OpenShift

Das Modell steht in den Atlas-Diagrammen C1, C2, C4 und C5. Hier spielst du sie von Hand durch: oc new-app baut aus Quellcode ein Image per S2I, ein echter Webhook löst einen Build aus, ein Tag allein löst die Tag-Falle nicht, feature/develop/main bilden auf Test, QS und Prod ab – und ein winziger Helm-Chart deployt das Ergebnis wirklich. EX280-relevant.

Stand: 4. September 2026 OKD 4.x / OCP 4.x ns library

Voraussetzung: ein Cluster mit oc login als Admin, Namespace library. Die interne Registry muss eingeschaltet sein (Schritt 7 zeigt wie). Auf SNO/CRC steht sie per Default auf Removed.

1 Die Kette im Überblick

Vier Objektarten, ein Weg: Quellcode wird zu einem laufenden Container.

1 QuelleGit-Repo, Binary-Verzeichnis oder Dockerfile – der Input eines Builds
2 BuildConfigdas Rezept: welche Strategie (S2I/Docker/Custom), welcher Input, wohin
3 Build-Podein Lauf des Rezepts – baut das Image, pusht es
4 ImageStreamder clusterinterne Zeiger auf das Ergebnis – Digest statt Tag, das löst die Tag-Falle (Schritt 6)
5 Deploymentzieht über den ImageStream oder direkt aus der Registry (Schritt 7)

Nur Schritt 2 und 3 (BuildConfig, Build-Pod) sind optional – wer extern baut (wie die Bibliothek, Schritt 12), pusht direkt in Schritt 4 und überspringt den Build-Pod im Cluster komplett.

2 Mit oc new-app starten

oc new-app rät aus der Eingabe, was zu tun ist, und legt einen ganzen Satz Objekte auf einmal an.

Laptop · Bash
# zuerst nur ansehen, ohne etwas anzulegen:
oc new-app https://github.com/sclorg/nodejs-ex -n library --dry-run=client -o yaml --name=demo-build | head -40

# jetzt wirklich:
oc new-app https://github.com/sclorg/nodejs-ex -n library --name=demo-build -l app=demo-build

oc get is,bc,deploy,svc -n library -l app=demo-build

new-app legt typisch an: einen ImageStream (Schritt 6), ein BuildConfig (Schritt 3), ein Deployment und einen Service. Keine Route – die kommt separat mit oc expose svc/demo-build. Die Sprache wird an Dateien im Repo erkannt (package.json, pom.xml, go.mod…), das passende S2I-Builder-Image wird automatisch gewählt.

nur die Build-Seiteoc new-build legt nur ImageStream + BuildConfig an, kein Deployment – für den Fall, dass der Deploy woanders liegt (Helm, Schritt 12).

3 Das BuildConfig-Objekt

Das Rezept „wie entsteht dieses Image“ – jeder Lauf ist ein eigenes Build-Objekt in einem Build-Pod.

Strategiemacht
Source (S2I)Quellcode + Builder-Image → fertiges App-Image, kein Dockerfile nötig – das assemble-Skript des Builder-Images baut
Dockerbaut aus einem Dockerfile im Repo – intern mit Buildah, kein Docker-Daemon im Cluster
Customein eigenes Builder-Image bestimmt den ganzen Ablauf – selten, für Spezial-Tooling

Trigger am BuildConfig: ein Webhook (GitHub/GitLab/generic, eine URL im Repo), ImageChange am Builder-Image (holt Base-Image-Patches automatisch nach), ConfigChange. Lebenszyklus: New → Pending → Running → Complete (oder Failed/Error/Cancelled). runPolicy steuert, ob parallele Builds erlaubt sind: Serial, Parallel, SerialLatestOnly.

4 Einen Build laufen lassen

Ein manueller Start, live verfolgt.

Laptop · Bash
oc start-build demo-build -n library --follow
# oder ohne zu warten:
oc start-build demo-build -n library
oc get builds -n library
oc logs -f build/demo-build-2 -n library

# ein Build, der zu lange braucht oder haengt:
oc cancel-build demo-build-2 -n library

der Haken auf SNOBuild-Pods brauchen echte Cluster-CPU – auf einer Single-Node konkurrieren sie direkt mit allen laufenden Diensten. Ein Build mitten in einer Demo kann spürbar bremsen.

Fertig gebaut landet das Image automatisch als neuer Eintrag im ImageStream (Schritt 6) – oc get istag demo-build:latest zeigt den frischen Digest.

5 Webhook live auslösen

oc new-app hat in Schritt 2 automatisch zwei Webhook-Trigger mit zufälligem Secret angelegt – die sieht man sich einmal live an, statt sie nur als Konzept zu kennen.

Laptop · Bash
oc describe bc/demo-build -n library | grep -A2 "Webhook"
# Webhook Generic:
#     URL:  https://api.sno.<ip>.nip.io:6443/apis/build.openshift.io/v1/namespaces/library/buildconfigs/demo-build/webhooks/<secret>/generic
# Webhook GitHub:
#     URL:  https://api.sno.<ip>.nip.io:6443/.../webhooks/<secret>/github

# die generic-URL genuegt sich selbst - einfach POSTen loest einen Build aus:
WEBHOOK_URL=$(oc describe bc/demo-build -n library | awk '/Webhook Generic/{getline; print $2}')
curl -sk -X POST "$WEBHOOK_URL"
oc get builds -n library -w

Ein echtes GitHub-/GitLab-/Bitbucket-Repo trägt genau so eine URL unter Settings → Webhooks ein – ein Push landet dann automatisch hier. Der Unterschied zur generic-URL: die drei Provider-Typen parsen den Payload und prüfen darin den Branch gegen spec.source.git.ref – passt der Branch nicht, wird der Build still ausgelassen (der Webhook antwortet trotzdem mit 200). generic prüft nur das Secret in der URL, keinen Branch, ausser du schickst selbst einen git.ref im JSON-Body mit.

wo das Secret herkommtBei oc new-app generiert OpenShift es automatisch. Von Hand: oc set triggers bc/demo-build --from-webhook --webhook-secret=<secret>, oder in der YAML unter spec.triggers[].generic.secretReference.

6 ImageStream, Digest, die Tag-Falle

Ein ImageStream ist ein clusterinterner, namespaced Zeiger auf Images – er speichert keine Bytes, sondern je Tag die Digest-Historie.

Laptop · Bash
oc get is demo-build -n library
oc describe is demo-build -n library     # zeigt die Digest-Historie je Tag
oc get istag demo-build:latest -n library -o jsonpath='{.image.dockerImageReference}{"\n"}'

die Tag-FalleMit reinem Kubernetes bleibt die Pod-Vorlage bei demo-build:latest – ein erneuter Push ändert sie nicht, es passiert einfach nichts. OpenShift löst das auf zwei Arten: ein Image-Trigger (C6) trägt den neuen Digest automatisch in die Pod-Vorlage ein – oder man pusht von vornherein unter einem unveränderlichen Tag (so macht es die Bibliothek, Schritt 12).

Ein externes Image in den Cluster holen: oc import-image demo --from=registry.access.redhat.com/ubi9/ubi:latest --confirm, importPolicy.scheduled: true importiert periodisch nach. lookupPolicy.local: true lässt ein reines Deployment den Tag über den Stream auflösen, statt die Registry direkt zu nennen. Pods ziehen mit dem Token der default-/builder-ServiceAccount – kein Extra-Secret für die interne Registry nötig.

7 Die interne Registry von Hand

Der image-registry-ClusterOperator verwaltet sie im Namespace openshift-image-registry – auf SNO/CRC per Default auf Removed.

Laptop · Bash
# einschalten (Storage-PVC vorher als RWO anlegen, siehe G3):
oc patch configs.imageregistry.operator.openshift.io/cluster --type=merge \
  -p '{"spec":{"managementState":"Managed","defaultRoute":true}}'

oc get co image-registry -w

# anmelden - das Token landet nicht in der Shell-History:
HOST=$(oc get route default-route -n openshift-image-registry -o jsonpath='{.spec.host}')
oc whoami -t | podman login -u kubeadmin --password-stdin --tls-verify=false "$HOST"

# ein Image von aussen pushen:
podman tag registry.access.redhat.com/ubi9/ubi-minimal:latest "$HOST/library/handtest:v1"
podman push --tls-verify=false "$HOST/library/handtest:v1"

oc get is handtest -n library

Zwei Adressen: innen image-registry.openshift-image-registry.svc:5000 (ClusterIP, zum Ziehen und für Build-Pods), aussen die Route aus defaultRoute: true – dahin schiebt man vom Laptop oder aus der CI. Das Zertifikat ist auf OKD/SNO selbstsigniert, darum --tls-verify=false oder die Router-CA vertrauen. Cross-namespace ziehen braucht eine Extra-Rolle: oc policy add-role-to-user system:image-puller system:serviceaccount:<ns>:default -n library.

8 Freigabe durch Um-Taggen

Ein Release ist nur ein Zeiger – atomar und umkehrbar, kein Rebuild.

Laptop · Bash
oc tag library/demo-build:latest library/demo-build:prod
oc get istag demo-build:prod -n library

# ein Rollback ist derselbe Befehl, rueckwaerts:
oc tag library/demo-build:v-vorherige library/demo-build:prod

Ein Deployment, das demo-build:prod referenziert (mit lookupPolicy.local: true), sieht den neuen Digest sofort – ohne Rebuild, ohne Push. Genau das nutzen Image-Trigger (C6), um einen Rollout auszulösen, sobald sich ein Tag bewegt.

9 Branch, Trigger, Umgebung

Das klassische Muster: feature/… baut nur, develop deployt nach Test/QS, main oder release/… deployt nach Prod – jeder Branch löst über denselben Webhook-Mechanismus (Schritt 5) etwas anderes aus.

feature/*Push löst höchstens Tests/Lint aus (CI ausserhalb, E1) – kein BuildConfig hört auf diesen Branch, also auch kein Deploy
developein BuildConfig mit spec.source.git.ref: develop hört auf genau diesen Branch – Build → Tag :test → Deploy mit dem Test-Overlay
main / releaseein zweites BuildConfig mit spec.source.git.ref: main – Build → Tag :prod → Deploy mit dem Prod-Overlay, oft mit manueller Freigabe davor

Der Trick liegt in Schritt 5: die drei Provider-Webhooks prüfen den Branch aus dem Payload gegen spec.source.git.ref – ein Push auf feature/x trifft die Webhook-URL des develop-BuildConfigs zwar auch (dieselbe Repo-Konfiguration meldet an alle passenden Hooks), löst dort aber keinen Build aus, weil der Branch nicht passt. Ein BuildConfig pro Umgebung ist deshalb der einfachste Weg, Branch und Umgebung fest zu verdrahten – die Alternative ist ein CI-System aussenrum (Jenkins Multibranch, Tekton mit einem CEL-Interceptor-Filter auf body.ref), das selbst entscheidet, welche Pipeline für welchen Branch läuft.

Konfiguration je Umgebung

Zwei übliche Wege, beide schon in dieser Anleitung gezeigt: ein ImageStream-Tag je Stufe (:test/:prod, Schritt 8 – Promotion ist Um-Taggen) und ein Helm-Values-Overlay je Stufe (values-test.yaml/ values-prod.yaml, unterscheidet DB-Host, Replica-Zahl, Ressourcen-Limits, Feature-Flags – der Chart und die Image-Digest bleiben gleich).

klassisches Branch-per-Stagewie die Bibliothek es vereinfacht
ein BuildConfig je Umgebung, gebunden an einen Branchkein BuildConfig im Cluster – Jenkins baut extern (Schritt 12)
Tag/Overlay je Stufe (:test/:prod)ein unveränderlicher Tag je Build, der Git-SHA – kein :latest, keine Stufen-Tags
Namespace- oder Cluster-pro-Stufeein Namespace library auf einer SNO
Promotion = nächstes Overlay deployen, ausgelöst durch Gitlokal (Spring dev, H2) → CRC (volles OpenShift lokal) → SNO (die Demo-Prod) – von Hand angestossen

Kein Widerspruch: das klassische Muster lohnt sich, sobald mehrere Leute an mehreren Umgebungen gleichzeitig arbeiten. Bei einem Ein-Personen-Demoprojekt mit einer SNO ist der direkte Weg schneller und ehrlicher – mehr Umgebungen vorzutäuschen als es gibt, hilft niemandem.

10 Vom Tag zum Deploy: Helm

Schritt 9 hat erklärt, welches Overlay welche Umgebung bekommt – hier folgt der Befehl, der es wirklich tut. Ein winziger Chart reicht, um die Verbindung zu zeigen.

Laptop · Bash
mkdir -p demo-build-chart/templates

cat > demo-build-chart/Chart.yaml <<'YAML'
apiVersion: v2
name: demo-build-chart
version: 0.1.0
YAML

cat > demo-build-chart/values.yaml <<'YAML'
image:
  repository: image-registry.openshift-image-registry.svc:5000/library/demo-build
  tag: latest
replicaCount: 1
YAML

cat > demo-build-chart/templates/deployment.yaml <<'YAML'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-build-helm
spec:
  replicas: {{ .Values.replicaCount }}
  selector: { matchLabels: { app: demo-build-helm } }
  template:
    metadata: { labels: { app: demo-build-helm } }
    spec:
      containers:
        - name: app
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports: [{ containerPort: 8080 }]
YAML

cat > values-test.yaml <<'YAML'
image: { tag: latest }
replicaCount: 1
YAML

cat > values-prod.yaml <<'YAML'
image: { tag: prod }
replicaCount: 2
YAML

# Test-Umgebung: das :latest-Image aus Schritt 4, ein Replica
helm upgrade --install demo-build ./demo-build-chart -n library -f values-test.yaml
helm list -n library

# "Promotion": derselbe Chart, das naechste Overlay, der Tag aus Schritt 8
helm upgrade --install demo-build ./demo-build-chart -n library -f values-prod.yaml
oc get pods -n library -l app=demo-build-helm

Genau das ist die Zeile aus der Tabelle in Schritt 9: derselbe Chart, nur das Overlay und der Image-Tag wechseln – kein neuer Build, kein neuer Push, nur ein anderer Zeiger und andere Werte. Das Release-Objekt (ein Secret im Namespace) trägt den Verlauf, helm rollback demo-build 1 -n library geht zurück. Das volle Modell – Release, Diff, Rollback, wo Helm aufhört – steht in helm-am-bibliothek-chart.html, diese Anleitung zeigt nur die Verbindung zur Build-Kette.

11 Aufräumen: Pruning

Jeder Push schreibt eine neue Zeile in die Tag-Historie – ohne Aufräumen läuft der Storage der Registry irgendwann voll.

Laptop · Bash
# erst eine Vorschau, nichts wird geloescht:
oc adm prune images --keep-tag-revisions=3 --keep-younger-than=60m

# nach Pruefung wirklich loeschen:
oc adm prune images --keep-tag-revisions=3 --keep-younger-than=60m --confirm

Automatisch läuft das über den imagePruner-CronJob, den der Operator konfiguriert – von Hand ist er für den seltenen Fall, dass der Storage schon eng wird.

12 Wie die Bibliothek es macht

Bausteinreal
BuildConfigkeins im Cluster – Jenkins baut die 9 Images extern mit podman build (Maven im Container gegen die Host-Engine), kein Build-Pod, keine Cluster-CPU dafür verbraucht
PushJenkins pusht mit podman push unter library/<dienst>:<git-sha> in die interne Registry – der Pfad legt automatisch einen ImageStream je Dienst an
Tag-Strategieein unveränderlicher Tag je Build (der Git-SHA) – kein :latest, darum kein Image-Trigger nötig, das Helm-Chart referenziert den SHA direkt
Rechtedie SA pipeline pusht mit einem Dauer-Token, Rollen system:image-builder + registry-editor (A5)
Bekannte Störungnach sno.ps1 up hängt der Operator oft an einer veralteten Leader-Lease – der Post-up-Hook (sno-wsl.sh remediate) räumt das, sonst bekommt der erste podman login ein connection refused

oc new-app/new-build nutzt die Bibliothek höchstens zum Skizzieren – für echten Deploy kommt Helm (B11), nicht der generierte Satz aus Schritt 2.

+ Die Kurzfassung

BuildBuildConfig = Rezept, Build = ein Lauf davon – optional, extern bauen geht genauso gut
ImageStreamZeiger auf Digests, nicht auf Bytes – löst die Tag-Falle per Image-Trigger oder unveränderlichem Tag
Registryzwei Adressen (intern Service, extern Route) – Pods ziehen ohne Extra-Secret, du pusht mit deinem Token