Verteilter Monolith: der Rollout-Radius macht Kopplung messbar
03.08.2026
Thema: alle Beiträge zu Kubernetes
Ob eine Änderung lokal bleibt, kannst du zählen statt diskutieren. Wie du mit helm template misst, wie viele Services eine einzelne Zeile Konfiguration neu startet.
Inhaltsverzeichnis
- Zwei Grenzen, und nur über eine wird geredet
- Der Messwert und warum er belastbar ist
- Der Aufbau
- Die Messung
- Auf dem Cluster nachgestellt
- Der Reflex, der es schlimmer macht
- Der Umbau
- Was die Zahl entscheidet, und was nicht
- Warum diese Messung überhaupt möglich ist
- Fazit
Eberhard Wolff und Falk Sippach beschreiben im iSAQB-Interview ein Muster, das viele kennen: Teams verteilen ihr System auf viele kleine Services, bevor die Domänengrenzen geklärt sind, und werden dadurch nicht flexibler, sondern langsamer. Ihre Prüffrage dafür ist angenehm unaufgeregt: Bleiben Änderungen lokal?
Das ist als Architekturfrage gemeint. Sie hat aber eine Ebene darunter eine harte, nachrechenbare Entsprechung, und die wird selten benutzt.
Die Antwort vorweg: Ob eine Änderung lokal bleibt, musst du nicht diskutieren, du kannst es zählen. Der Messwert ist der Rollout-Radius, also die Anzahl der Deployments, deren Pod-Template sich durch eine einzelne Änderung ändert. Wer eine Zeile Konfiguration anfasst, die genau einem Service gehört, und damit drei Deployments neu startet, hat an dieser Stelle nicht drei Services, sondern einen, der in drei Prozessen läuft. Diese Zahl steht vor jedem Deploy fest und braucht keinen Cluster.
Zwei Grenzen, und nur über eine wird geredet
Domain-driven Design zieht die Grenze im Modell und danach im Code. Das ist die Grenze, über die in Architekturrunden gesprochen wird, und sie ist die wichtigere von beiden.
Es gibt aber eine zweite: die Auslieferungsgrenze. Sie entsteht nicht im Entwurf, sondern in Helm-Charts, ApplicationSets und Pipelines, meistens nebenbei und ohne Entscheidung. Wenn beide Grenzen deckungsgleich sind, stimmt die Architektur mit dem Betrieb überein. Wenn nicht, gewinnt immer die Auslieferungsgrenze, denn sie bestimmt, was tatsächlich zusammen neu startet, zusammen ausfällt und zusammen abgestimmt werden muss.
Der unangenehme Fall ist nicht der offensichtliche. Der Code kann sauber getrennt sein, die Bounded Contexts können sitzen, und trotzdem zieht die Auslieferung die Grenze wieder ein.
Der Messwert und warum er belastbar ist
Ein Deployment rollt in Kubernetes genau dann neu aus, wenn sich spec.template ändert. Der
Deployment-Controller berechnet daraus einen Hash, legt bei Abweichung ein neues ReplicaSet an und
fährt die alten Pods herunter. Das ist keine Heuristik und keine Faustregel, sondern das Kriterium
des Controllers.
Damit ist der Rollout-Radius sauber definiert:
Rollout-Radius einer Änderung = Anzahl der Deployments, deren gerendertes
spec.templatesich gegenüber dem vorherigen Stand unterscheidet.
Und er lässt sich vollständig lokal ausrechnen, weil das Rendern eine eigene Stufe ist. helm
template erzeugt die Manifeste, ohne einen Cluster anzufassen.
Der Aufbau
Drei Services in einem Chart, so wie es in sehr vielen Repositories aussieht. Eine gemeinsame ConfigMap für die Konfiguration:
apiVersion: v1
kind: ConfigMap
metadata:
name: shop-config
data:
LOG_LEVEL: {{ .Values.config.logLevel | quote }}
WAEHRUNG: {{ .Values.config.waehrung | quote }}
FEATURE_FLAGS: {{ .Values.config.featureFlags | quote }}
Dazu je Service ein Deployment. Die Annotation darin ist kein Sonderweg, sondern das von Helm selbst dokumentierte Vorgehen, damit Änderungen an der Konfiguration überhaupt ankommen:
apiVersion: apps/v1
kind: Deployment
metadata:
name: katalog
spec:
replicas: 2
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
containers:
- name: katalog
image: {{ .Values.katalog.image | quote }}
envFrom:
- configMapRef: { name: shop-config }
Warenkorb und Bezahlung sehen genauso aus. Bis hierhin hat niemand etwas falsch gemacht: drei getrennte Deployments, drei getrennte Images, drei getrennte Teams denkbar.
Die Messung
Der Radius braucht nur zwei Renderläufe und je Deployment einen Fingerabdruck des Pod-Templates:
podtemplates() {
helm template shop ./shop "$@" \
| yq -r 'select(.kind == "Deployment") | [.metadata.name, (.spec.template | tojson)] | @tsv' \
| while IFS=$'\t' read -r name tpl; do
printf '%s %s\n' "$name" "$(printf '%s' "$tpl" | sha256sum | cut -c1-12)"
done
}
Das yq hier ist die jq-kompatible Variante, also python-yq. Der Ausgangsstand:
bezahlung 96e6c5bc1db6
katalog 920c8e1f9167
warenkorb 7c36afc5600f
Erster Fall, ein neues Image für genau einen Service:
podtemplates --set katalog.image=registry.example.org/katalog:1.4.3
Genau ein Fingerabdruck ändert sich, katalog auf 942b29aeace8. Radius 1. Das Werkzeug ist
also nicht kaputt, und der Normalfall funktioniert.
Zweiter Fall, LOG_LEVEL von info auf debug. Ein Wert, den nur der Katalog liest:
podtemplates --set config.logLevel=debug
bezahlung ae5a1ec34317
katalog 182a3c947761
warenkorb cb85534ef297
Alle drei. Radius 3.
Auf dem Cluster nachgestellt
Damit das nicht nur eine Aussage über Textdateien bleibt, dasselbe auf einem frischen k3d-Cluster. Vor dem Upgrade laufen sechs Pods aus drei ReplicaSets. Dann dieselbe eine Zeile:
helm upgrade shop ./shop --set config.logLevel=debug --wait
katalog: 2 generations, 2 beobachtet
warenkorb: 2 generations, 2 beobachtet
bezahlung: 2 generations, 2 beobachtet
bezahlung-856b569547 0
bezahlung-85fb9cb8d7 2
katalog-854f467fc5 2
katalog-bf644cdf5 0
warenkorb-669cdb4c9 0
warenkorb-68595c9bc9 2
Drei neue ReplicaSets, sechs neue Pods, drei alte ReplicaSets auf null. Die Bezahlung startet neu, weil der Katalog mehr loggen soll. Genau das ist der Punkt aus dem Interview, nur eben als Vorgang im Cluster statt als Diagramm: Die Änderung bleibt nicht lokal.
Nüchtern betrachtet ist damit jede Konfigurationsänderung ein Ereignis für alle. Sie braucht ein gemeinsames Wartungsfenster, sie bringt für alle Beteiligten Rollout-Risiko mit, und sie erzeugt genau den Abstimmungsaufwand, den die Verteilung eigentlich abschaffen sollte. Die Betriebskosten der Verteilung sind bezahlt, die Rollout-Semantik ist die des Monolithen geblieben.
Der Reflex, der es schlimmer macht
Wer das entdeckt, will typischerweise als Erstes die Annotation loswerden. Ohne sie ist der Radius tatsächlich 0. Nur ist das keine Entkopplung, sondern ein zweiter Fehler:
ConfigMap im Cluster: debug
Pod-Env nach dem Upgrade: info
Die Pods laufen unverändert weiter. Über envFrom eingebundene Werte werden beim Start des
Containers gelesen, eine spätere Änderung der ConfigMap erreicht den Prozess nicht. Aus sichtbarer
Kopplung wird damit eine stille Abweichung zwischen dem, was im Cluster steht, und dem, was
tatsächlich läuft. Sie fällt beim nächsten unabhängigen Grund für einen Neustart auf, also
irgendwann, bei irgendeinem Pod.
Die Annotation ist nicht der Fehler. Sie macht die Kopplung nur sichtbar, die vorher schon da war.
Der Umbau
Der eigentliche Fehler ist die geteilte ConfigMap. Sie ist ein gemeinsamer Zustand, an dem drei Services hängen, und sie ist im Architekturdiagramm nicht zu sehen. Die Auflösung ist unspektakulär: eine ConfigMap je Service, und die Prüfsumme nur über den Teil, der diesem Service gehört.
annotations:
checksum/config: {{ .Values.katalog.config | toYaml | sha256sum }}
Die Prüfsumme läuft hier bewusst über den Werte-Teilbaum und nicht mehr über die gerenderte Datei. Liegen ConfigMap und Deployment eines Services in derselben Vorlage, würde sich diese Datei sonst über die Annotation selbst einbinden.
Dieselbe Änderung, LOG_LEVEL des Katalogs auf debug:
Radius: 1
katalog b74fafa2dd78
Radius 1. Ohne neuen Architekturstil, ohne Migration, ohne dass ein einziger Service umgezogen ist. Die Auslieferungsgrenze liegt jetzt dort, wo die Domänengrenze ohnehin schon war.
Was die Zahl entscheidet, und was nicht
Der Rollout-Radius sagt nicht, ob ein Modulith oder Microservices richtig sind. Er sagt, ob der gewählte Stil im Betrieb tatsächlich existiert.
Ein gut geschnittener Modulith hat konstruktionsbedingt Radius 1, weil er eine einzige Auslieferungseinheit ist. Das ist ehrlich: Er verspricht keine unabhängige Auslieferung und liefert sie auch nicht. Ein verteiltes System mit Radius 3 verspricht sie, kassiert die Betriebskosten der Verteilung und liefert die Rollout-Semantik des Monolithen. Von beiden Optionen ist die zweite die teurere.
Deshalb passt die Messung so gut zu dem Rat aus dem Interview, mit einem gut strukturierten Modulith zu beginnen und später Teile herauszulösen. Beim Herauslösen ist der Radius die Abnahmebedingung: Der herausgelöste Service ist fertig herausgelöst, wenn eine Änderung an ihm niemanden sonst neu startet. Vorher steht die Grenze auf dem Papier.
Ehrlich dazugesagt: Der Rollout-Radius misst eine Kopplung, nicht alle. Eine geteilte Datenbank, synchrone Aufrufketten, eine gemeinsame Bibliothek im Basis-Image und Readiness-Probes, die auf Nachbarn zeigen, sieht er nicht. Er ist nur die mit Abstand billigste Messung: zwei Renderläufe, kein Cluster, unter einer Minute. Genau deshalb gehört er in die CI, als Regel, die einen Merge-Request markiert, sobald eine service-lokale Änderung mehr als ein Deployment bewegt.
Warum diese Messung überhaupt möglich ist
Ein Detail daran ist keine Kleinigkeit. Der Radius ist nur deshalb bestimmbar, weil das Rendern
eine offene, eigene Stufe ist. helm template gibt dir vollständig heraus, was ausgerollt würde,
bevor es jemand ausrollt. Kubernetes verrät dir, nach welchem Kriterium neu gestartet wird.
Bei einer geschlossenen Plattform, die aus deinem Repository heraus deployt und dir das Ergebnis nicht als Manifest zeigt, hast du diese Zahl schlicht nicht. Du kannst dann behaupten, deine Services seien unabhängig, aber du kannst es nicht nachweisen.
Das ist der Punkt, an dem die beiden Säulen ineinandergreifen: Verstehen setzt voraus, dass man nachsehen darf. Nachsehen dürfen ist eine Frage der Werkzeuge und ihrer Lizenz.
Fazit
- Die Frage aus dem iSAQB-Interview, ob Änderungen lokal bleiben, hat im Cluster eine exakte Entsprechung: den Rollout-Radius.
- Kubernetes rollt genau dann neu aus, wenn sich
spec.templateändert. Damit ist der Radius vor jedem Deploy berechenbar, ohne Cluster, mit zweihelm template-Läufen. - Im gemessenen Beispiel kostet ein neues Image Radius 1, eine Zeile geteilter Konfiguration dagegen Radius 3. Auf dem Cluster sind das sechs neue Pods für eine Zeile.
- Die
checksum/config-Annotation zu entfernen senkt den Radius auf 0 und ersetzt sichtbare Kopplung durch stille Abweichung. Die ConfigMap sagt danndebug, der Prozessinfo. - Die Ursache ist geteilter Zustand in der Auslieferung. Eine ConfigMap je Service und eine Prüfsumme je Service bringen den Radius auf 1 zurück, ohne Architekturwechsel.
- Die Zahl entscheidet nicht zwischen Modulith und Microservices. Sie entscheidet, ob der gewählte Stil im Betrieb existiert, und taugt als Abnahmebedingung beim Herauslösen.
Die Domänengrenze zieht die Architektur. Ob sie im Betrieb ankommt, entscheidet die Plattform. Und das ist der Teil, den man nicht diskutieren muss, sondern nachrechnen kann.
© 2026 Trutz Software Consulting GmbH. CC BY 4.0: Dieser Text darf mit Namensnennung weiterverwendet und bearbeitet werden, auch kommerziell. Code-Beispiele darin stehen unter Apache 2.0. Ausgenommen sind Marke und Logo sowie gekennzeichnete Zitate Dritter. Was das genau heißt.
Newsletter
Erhalte praxisnahe Tutorials, Best Practices und echte Lösungen für die häufigsten DevOps-Probleme direkt in Dein Postfach.
Datenschutz: Deine E-Mail-Adresse wird DSGVO-konform in Deutschland gespeichert. Keine Spam, nur relevante Inhalte. Eine E-Mail alle 1 bis 2 Wochen.