← Übersicht  ·  Skripte & Dateien  ·  cloud-host · OpenShift · Workloads

Workload-Feinschliff in OpenShift

Das Modell steht in den Atlas-Diagrammen B2, B6, B7 und B8. Vier Bausteine, die jeden Alltag betreffen: warum Deployment und nicht DeploymentConfig, wie ein Pod gezielt (oder gar nicht) platziert wird, warum eine Quota ohne LimitRange alles blockiert – und der Unterschied zwischen env und Mount, live gesehen. EX280-relevant.

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

Voraussetzung: ein Cluster mit oc login als Admin, Namespace library.

1 Deployment vs DeploymentConfig

Beide fahren einen Rollout – nur einer ist Kubernetes-Standard.

DeploymentDeploymentConfig
APIapps/v1, Kubernetes-Standardapps.openshift.io/v1, OpenShift-eigen, wird ausgephast
Kind-ObjektReplicaSet (B1)ReplicationController
Rollout-Motorder Controller selbst, in-processein eigener deployer-Pod (<dc>-<n>-deploy, SA deployer) – langsamer
Triggerkeine eingebauten – Annotation image.openshift.io/triggerseingebaut: ImageChange, ConfigChange
HooksHelm-Hooks, initContainer, eigener Jobeingebaut: pre, mid (nur bei Recreate), post
StrategienRollingUpdate, RecreateRolling, Recreate, Custom (eigenes Deployer-Image)
Laptop · Bash
# seit OCP 4.14 legt oc new-app ein Deployment an - hier zum Vergleich beide anlegen:
oc create deployment demo-deploy -n library --image=registry.access.redhat.com/ubi9/httpd-24
oc rollout status deployment/demo-deploy -n library
oc rollout undo deployment/demo-deploy -n library
oc rollout restart deployment/demo-deploy -n library

# ein DeploymentConfig existiert nur noch als Altlast - sichtbar mit:
oc get dc -A
oc explain deploymentconfig.spec.strategy.type

Kernsatz: Deployment ist passiv (du oder Helm lösen einen Rollout aus), DeploymentConfig ist aktiv (beobachtet Image und Config selbst und reagiert). Neu anlegen immer ein Deployment – ein DeploymentConfig begegnet dir noch in echten Legacy-Migrationen (H).

2 Scheduling: Taint, Toleration, Affinity

Der Scheduler filtert (welche Nodes können?), bewertet (welcher ist am besten?), setzt dann spec.nodeName.

nodeSelectorPod nur auf einem Node mit passendem Label – harte Kurzform, kein Match heisst Pending
Taint/TolerationTaint am Node weist ab, wer nicht toleriert – Effekte NoSchedule, PreferNoSchedule, NoExecute
AffinitynodeAffinity (wie nodeSelector, mit required/preferred), podAffinity/podAntiAffinity (nah bei/fern von anderen Pods, immer mit topologyKey)
Requeststreiben die Platzierung – nicht Limits, nicht der Ist-Verbrauch. Ein Pod ohne Requests ist BestEffort und packt schlecht

Der Node-Controller taintet automatisch bei disk-pressure, memory-pressure, not-ready – Control-Plane-Nodes sind per Default getaintet. topologySpreadConstraints ist der moderne Weg, Replicas gleichmässig zu verteilen (maxSkew, topologyKey, whenUnsatisfiable). Eine PriorityClass kann niedriger priorisierte Pods verdrängen (Preemption).

3 Einen Pod gezielt platzieren

Auf SNO ist alles einerlei (ein Node) – hier siehst du trotzdem live, wie ein nodeSelector ohne Match einen Pod hängen lässt.

Laptop · Bash
cat <<'YAML' | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-schedule
  namespace: library
spec:
  replicas: 1
  selector: { matchLabels: { app: demo-schedule } }
  template:
    metadata: { labels: { app: demo-schedule } }
    spec:
      nodeSelector:
        kubernetes.io/hostname: nonexistent-node
      containers:
        - name: web
          image: registry.access.redhat.com/ubi9/httpd-24
YAML

oc get pod -n library -l app=demo-schedule
# -> Pending

oc describe pod -n library -l app=demo-schedule | grep -A3 Events
# -> 0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector

# reparieren - Selector entfernen, damit der Pod wirklich startet:
oc patch deployment demo-schedule -n library --type=json \
  -p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
oc get pod -n library -l app=demo-schedule -w

Genau dieses Muster – Pending plus die Events-Zeile – ist der erste Griff bei jedem Scheduling-Problem, egal ob nodeSelector, ein zu enger Taint oder zu wenig freie Ressourcen die Ursache sind.

4 ResourceQuota und LimitRange

Zwei Objekte, ein häufig übersehener Zusammenhang: die Reihenfolge, in der sie greifen.

wirkt
LimitRangeje Namespace – füllt fehlende requests/limits je Container mit Default-Werten auf, setzt Min/Max (ein Admission-Plugin mutiert den Pod)
ResourceQuotaje Namespace – Obergrenze für alles zusammen: Summe requests.cpu/limits.memory, Anzahl pods/services/secrets/pvc

der HakenEine Quota auf requests.* lässt nur Pods durch, bei denen jeder Container einen Request hat – ohne LimitRange werden Deploys mit der Fehlermeldung must specify requests.cpu abgelehnt. Darum erst die LimitRange, dann die Quota – sonst brechen alte Deploys ohne eigene Requests.

QoS-Klassen sind abgeleitet, nicht gesetzt: Guaranteed (requests == limits überall) stirbt zuletzt, Burstable dazwischen, BestEffort (nichts gesetzt) stirbt zuerst. CPU über Limit wird gedrosselt, Memory über Limit wird OOMKilled (Exit 137).

5 Eine Quota live gegen einen Deploy laufen lassen

Erst ohne LimitRange scheitern lassen, dann die Reihenfolge korrigieren.

Laptop · Bash
oc create quota demo-quota -n library --hard=requests.cpu=1,requests.memory=512Mi,pods=5

# ohne Requests im Deploy - scheitert an der Quota:
oc create deployment demo-quota-test -n library --image=registry.access.redhat.com/ubi9/httpd-24
oc get events -n library --field-selector involvedObject.name=demo-quota-test | grep -i quota
# -> failed quota: demo-quota: must specify requests.cpu,requests.memory

# jetzt eine LimitRange mit Default-Requests nachruesten:
cat <<'YAML' | oc apply -f -
apiVersion: v1
kind: LimitRange
metadata:
  name: demo-limits
  namespace: library
spec:
  limits:
    - type: Container
      defaultRequest: { cpu: 100m, memory: 128Mi }
      default: { cpu: 300m, memory: 256Mi }
YAML

# derselbe Deploy funktioniert jetzt, weil die LimitRange die Requests ergaenzt:
oc rollout restart deployment/demo-quota-test -n library
oc describe quota demo-quota -n library

oc describe quota -n library zeigt Used gegen Hard, oc describe limitrange -n library die konfigurierten Defaults. Eine ClusterResourceQuota (OpenShift-eigen) macht dasselbe über mehrere Projects per Label-Selektor.

6 ConfigMap und Secret: env vs Mount

Zwei Konsumwege, ein wichtiger Unterschied im Verhalten bei Änderungen.

Wegbei einer Änderung
env / envFrombei Pod-Start eingefroren – eine spätere Änderung an der ConfigMap/dem Secret erreicht den laufenden Pod nie
Datei-Mount (volumeMounts)aktualisiert sich im laufenden Pod nach etwa einer Minute – ausser bei subPath, dann auch eingefroren

Keiner von beiden löst einen Rollout aus – eine Änderung an ConfigMap/Secret rollt den Pod nicht automatisch neu. Üblich: eine checksum/config-Annotation (Helm rollt dann bei Änderung) oder von Hand oc rollout restart. Secret-Typen erzwingen ihre Schlüssel: Opaque, kubernetes.io/tls, kubernetes.io/dockerconfigjson, service-account-token, basic-auth, ssh-auth.

7 Den Unterschied live sehen

Ein ConfigMap-Wert per env, derselbe Wert per Mount – dann ändern und vergleichen.

Laptop · Bash
oc create configmap demo-cfg -n library --from-literal=GREETING=hallo

cat <<'YAML' | oc apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-config-consumer
  namespace: library
spec:
  replicas: 1
  selector: { matchLabels: { app: demo-config-consumer } }
  template:
    metadata: { labels: { app: demo-config-consumer } }
    spec:
      containers:
        - name: web
          image: registry.access.redhat.com/ubi9/ubi-minimal
          command: ["sleep", "3600"]
          envFrom:
            - configMapRef: { name: demo-cfg }
          volumeMounts:
            - name: cfg
              mountPath: /etc/demo-cfg
      volumes:
        - name: cfg
          configMap: { name: demo-cfg }
YAML

POD=$(oc get pod -n library -l app=demo-config-consumer -o jsonpath='{.items[0].metadata.name}')
oc exec -n library "$POD" -- sh -c 'echo ENV=$GREETING; cat /etc/demo-cfg/GREETING'

# jetzt die ConfigMap aendern, OHNE den Pod neu zu starten:
oc set data configmap/demo-cfg -n library --from-literal=GREETING=servus
sleep 70
oc exec -n library "$POD" -- sh -c 'echo ENV=$GREETING; echo MOUNT=$(cat /etc/demo-cfg/GREETING)'
# ENV zeigt noch "hallo" (eingefroren), MOUNT zeigt "servus" (aktualisiert)

was zu sehen istGenau der Unterschied aus Schritt 6, live: die Env-Variable bleibt beim alten Wert stehen, die gemountete Datei hat sich ohne Neustart aktualisiert.

8 Typische Fallen

Symptommeist
Pod bleibt Pendingkein Node passt – oc describe pod nennt den Grund in den Events
Deploy scheitert mit must specify requests.cpueine Quota existiert, aber keine LimitRange füllt die Requests auf
App liest alten Wert nach ConfigMap-Änderungder Wert kam per env – der ist eingefroren, ein Mount wäre aktuell
Pod stirbt mit Exit 137OOMKilled – Memory-Limit zu knapp, oder BestEffort ohne Limits stirbt zuerst bei Knappheit
DeploymentConfig-Rollout hängtder deployer-Pod selbst hängt – oc logs dc/<name>-<n>-deploy

9 Wie die Bibliothek es macht

Bausteinreal
Deploymentalle 8 Dienste sind Deployment (aus dem Helm-Chart, B11) – einen DeploymentConfig triffst du nur im Novaris-Legacy-Block (H)
SchedulingpodAntiAffinity auf catalog-service (2 Replicas auf verschiedene Nodes), Postgres per nodeSelector auf einen Storage-Node – auf der SNO wirkungslos, auf einem echten Multi-Node greift es
Quotaoc new-project library bringt aus dem project-request-Template (A2) schon eine Quota und eine LimitRange mit
RessourcenbudgetOKD + 8 JVMs + Postgres/Redis/Kafka/Keycloak zusammen ~15,6 GB – darum läuft die SNO auf cpx52 (24 GB). Ohne LimitRange würden mehrere der 8 Deploys abgelehnt
Config/SecretApp-Config je Dienst als ConfigMap, DB-Passwort/Keycloak-Client-Secret/Kafka-Credentials als Secret – das Frontend-env.js ist eine als Datei gemountete ConfigMap (J4)

+ Die Kurzfassung

Deploymentpassiv, Kubernetes-Standard – DeploymentConfig ist aktiv, OpenShift-eigen, wird ausgephast
SchedulingRequests treiben die Platzierung, nicht Limits – nodeSelector/Taint/Affinity steuern sie gezielt
Quota + Configerst LimitRange, dann Quota – env ist eingefroren, ein Mount aktualisiert sich, keiner von beiden triggert einen Rollout