Voraussetzung: ein Cluster mit oc login
als Admin, Namespace library.
1 Deployment vs DeploymentConfig
Beide fahren einen Rollout – nur einer ist Kubernetes-Standard.
Deployment | DeploymentConfig | |
|---|---|---|
| API | apps/v1, Kubernetes-Standard | apps.openshift.io/v1, OpenShift-eigen, wird ausgephast |
| Kind-Objekt | ReplicaSet (B1) | ReplicationController |
| Rollout-Motor | der Controller selbst, in-process | ein eigener deployer-Pod (<dc>-<n>-deploy, SA deployer) – langsamer |
| Trigger | keine eingebauten – Annotation image.openshift.io/triggers | eingebaut: ImageChange, ConfigChange |
| Hooks | Helm-Hooks, initContainer, eigener Job | eingebaut: pre, mid (nur bei Recreate), post |
| Strategien | RollingUpdate, Recreate | Rolling, Recreate, Custom (eigenes Deployer-Image) |
# 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.
PendingNoSchedule, PreferNoSchedule, NoExecutenodeAffinity (wie nodeSelector, mit required/preferred), podAffinity/podAntiAffinity (nah bei/fern von anderen Pods, immer mit topologyKey)BestEffort und packt schlechtDer 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.
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 | |
|---|---|
LimitRange | je Namespace – füllt fehlende requests/limits je Container mit Default-Werten auf, setzt Min/Max (ein Admission-Plugin mutiert den Pod) |
ResourceQuota | je 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.
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.
| Weg | bei einer Änderung |
|---|---|
env / envFrom | bei 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.
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
| Symptom | meist |
|---|---|
Pod bleibt Pending | kein Node passt – oc describe pod nennt den Grund in den Events |
Deploy scheitert mit must specify requests.cpu | eine Quota existiert, aber keine LimitRange füllt die Requests auf |
| App liest alten Wert nach ConfigMap-Änderung | der Wert kam per env – der ist eingefroren, ein Mount wäre aktuell |
| Pod stirbt mit Exit 137 | OOMKilled – Memory-Limit zu knapp, oder BestEffort ohne Limits stirbt zuerst bei Knappheit |
| DeploymentConfig-Rollout hängt | der deployer-Pod selbst hängt – oc logs dc/<name>-<n>-deploy |
9 Wie die Bibliothek es macht
| Baustein | real |
|---|---|
| Deployment | alle 8 Dienste sind Deployment (aus dem Helm-Chart, B11) – einen DeploymentConfig triffst du nur im Novaris-Legacy-Block (H) |
| Scheduling | podAntiAffinity 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 |
| Quota | oc new-project library bringt aus dem project-request-Template (A2) schon eine Quota und eine LimitRange mit |
| Ressourcenbudget | OKD + 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/Secret | App-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
DeploymentConfig ist aktiv, OpenShift-eigen, wird ausgephast