0 Treffer
Tipp: Auf iPhone/Safari bleiben Navigation und Inhalte ohne JavaScript lesbar; die Buttons sind nur Komfort.

Enterprise Platform Lab

Neu aus Roh-Markdown konsolidiert: logisch gegliedert, offlinefähig, mit sichtbarer Hauptteil-Übersicht, stabiler Navigation, sauberem Text, JetBrains-Dark-Codeblöcken und Inline-SVGs.

Schnellübersicht: alle Hauptteile

Finale Leselogik: Das Buch ist thematisch aufgebaut. Die Rohschritte bleiben im ZIP erhalten, die HTML folgt aber einer fachlichen Reihenfolge.

Für schnelles Lernen zuerst Teil A bis E lesen, danach Praxisfälle und Glossar als Nachschlagewerk verwenden.

Lesepfad ArchitekturTeil A, B, C, H und I: Zielbild, Infrastruktur, Security, Praxisfälle und Glossar.
Lesepfad EntwicklerTeil D, E, F und G: Anwendungen, Datenfluss, Codebeispiele und End-to-End-Sprint.
Lesepfad BetriebTeil B, C, H und Anhang K: Plattform, Observability, DR, Qualität und Abnahme.
Lesepfad PrüfungGlossar, Runbooks, Übungen und Quality-Report für Wiederholung und Kontrolle.

Diese Übersicht ist bewusst sichtbar, damit auf iPhone/Safari nicht nur ein später Anhang wirkt. Die Inhalte selbst bleiben unten klappbar und standardmäßig geschlossen.

Enterprise Platform Lab

Enterprise Platform Lab

PlanZielbildBuildUmsetzungRunBetriebEnterprise Platform Lab: Architektur, Umsetzung und Betrieb im Zusammenhang

Konsolidiertes Masterbuch aus Roh-Markdown

Diese Fassung wurde neu aus den Roh-Markdown-Schritten konsolidiert. Die Struktur folgt nicht mehr der Entstehungsreihenfolge, sondern einer fachlichen Lernlogik: Zielbild, Infrastruktur, Plattform, Security, Anwendungen, Daten, Betrieb, DevOps, Beispielprojekt, Praxisfälle, Übungen und Glossar.

Die Build-, Abnahme- und Pflegekapitel stehen nicht als normale Hauptkapitel im Lernweg. Sie sind im Anhang gebündelt, damit das Buch nicht wie eine Chat-Historie wirkt.

Strukturprinzipien

  • Fachliche Kapitel statt künstlicher Schrittfolge.
  • Praxis direkt im passenden Kontext.
  • Glossar und Begriffe als Referenz, nicht als Ersatz für Inhalt.
  • Code, YAML, SQL, Terraform, Bash und Konfigurationsbeispiele bleiben erhalten.
  • HTML-Ausgabe ist offlinefähig, mit zentralem JetBrains-Dark-Codeblock-Stil und klappbarer Navigation.

Hauptteile

  • Teil A: Orientierung, Unternehmenskontext und Zielarchitektur
  • Teil B: Hybrid-Infrastruktur: Netzwerk, Cloud, VM, Bare Metal und OpenShift
  • Teil C: Identity, Security, Governance und Plattformregeln
  • Teil D: Moderne Anwendungen, Datenflüsse, Messaging und Integration
  • Teil E: Observability, Betrieb, Backup, Disaster Recovery und Resilienz
  • Teil F: DevOps, GitOps, Release Management und AI-Unterstützung
  • Teil G: Komplettes Beispielprojekt und große Codebasis
  • Teil H: Praxisfälle, Betriebsstörungen und End-to-End-Szenarien
  • Teil I: Übungen, Runbooks, Musterlösungen, kompakte SVGs und Fachglossar
  • Anhang K: Build-, Abnahme- und Pflegeprozess

---

Teil A: Orientierung, Unternehmenskontext und Zielarchitektur

Teil A: Orientierung, Unternehmenskontext und Zielarchitektur

PlanZielbildBuildUmsetzungRunBetriebTeil A: Orientierung, Unternehmenskontext und Zielarchitektur: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 1.

Unternehmenskontext und Zielbild

Hybrid-Plattform auf einen Blick
Kompakte Themenübersicht
NutzerExternInternFrontendsPortalAdminOpenShiftOrderBillingAPIsCloudS3LBVMBatchToolsBare MetalDBFilesHTTPSREST/OIDCObjekteReportsI/O / DB

1. Warum dieses Projekt existiert

Ein modernes Enterprise-System entsteht selten auf einer grünen Wiese. In vielen Unternehmen gibt es bereits Anwendungen, Datenbanken, Server, Batchjobs, Integrationen, Berechtigungsmodelle und Betriebsprozesse. Manche Systeme laufen auf virtuellen Maschinen, manche auf Bare-Metal-Servern, manche bereits in Kubernetes oder OpenShift, andere teilweise in der Cloud. Genau daraus entsteht die zentrale Herausforderung dieses Lernprojekts.

Das Ziel ist nicht, eine einzelne kleine Demo-Anwendung zu bauen. Das Ziel ist, eine realistische Enterprise-Landschaft zu verstehen und daraus Schritt für Schritt eine moderne Plattform abzuleiten. Diese Plattform soll mehrere Infrastrukturarten gleichzeitig nutzen: Cloud, virtuelle Maschinen, Bare Metal und Kubernetes/OpenShift. Moderne Anwendungen sollen nicht isoliert betrachtet werden, sondern als Teil eines größeren Betriebsmodells.

In einem echten Unternehmen reicht es nicht, nur einen Spring Boot Service zu starten. Man muss wissen, wie dieser Service authentifiziert wird, wie er Logs schreibt, wie er Metriken liefert, wie er deployt wird, wie Secrets verwaltet werden, wie Daten gesichert werden, wie Störungen behandelt werden und wie Änderungen nachvollziehbar dokumentiert werden. Genau deshalb verbindet dieses Projekt Architektur, Infrastruktur, Entwicklung, Security, Betrieb und DevOps.

Der wichtigste Lernpunkt lautet:

Vorlage / Textauszug

Eine moderne Enterprise-Plattform ist nicht nur Code.

Sie ist das Zusammenspiel aus Anwendungen, Infrastruktur, Security, Betrieb, Deployment, Monitoring, Dokumentation und klaren Entscheidungen.

Dieses Projekt soll zeigen, wie man eine solche Plattform fachlich sauber plant und technisch nachvollziehbar umsetzt.

2. Das Beispielunternehmen

Für das Lernprojekt verwenden wir ein fiktives Unternehmen:

Vorlage / Textauszug

Name: Alpine Retail & Services Group

Branche: Handel, Service, Kundenportal, Abrechnung und Partnerintegration

Standorte: Österreich, Deutschland, Schweiz

Betriebsmodell: Hybrid

Die Alpine Retail & Services Group betreibt mehrere digitale Systeme. Kunden bestellen Produkte und Dienstleistungen über ein modernes Portal. Interne Mitarbeiter bearbeiten Aufträge, prüfen Zahlungen, verwalten Kunden und analysieren Kennzahlen. Partnerunternehmen liefern Daten über Schnittstellen. Einige Prozesse laufen in Echtzeit, andere als nächtliche Verarbeitung.

Die bestehende IT-Landschaft ist historisch gewachsen. Es gibt alte Anwendungen auf virtuellen Maschinen, einige Datenbanken auf Bare-Metal-Servern, einzelne Cloud-Dienste und erste Container-Plattformen. Die Organisation möchte nicht alles auf einmal ersetzen. Stattdessen soll eine moderne Plattform entstehen, die neue Anwendungen sauber aufnimmt und alte Systeme kontrolliert integriert.

Das Unternehmen hat folgende Ziele:

Vorlage / Textauszug
  1. Neue Anwendungen schneller entwickeln
  2. Deployment-Prozesse automatisieren
  3. Sicherheit nachvollziehbar verbessern
  4. Betrieb und Monitoring vereinheitlichen
  5. Alte Systeme kontrolliert anbinden
  6. Cloud, VM, Bare Metal und OpenShift sinnvoll kombinieren
  7. Wissen dokumentieren und wiederholbar machen

Dieses Unternehmen ist bewusst realistisch gewählt. Es gibt keine perfekte Ausgangslage. Genau dadurch wird das Projekt nützlich.

3. Ausgangslage der IT-Landschaft

Die Ausgangslandschaft besteht aus mehreren Bereichen.

Es gibt klassische virtuelle Maschinen für interne Anwendungen. Diese Systeme sind stabil, aber oft schwer automatisierbar. Updates werden teilweise manuell durchgeführt. Konfigurationen liegen nicht immer vollständig in Git. Monitoring existiert, aber nicht überall einheitlich.

Es gibt Bare-Metal-Server für datenintensive oder besonders performante Systeme. Dazu gehören große Datenbanken, File-Verarbeitung und Backup-Systeme. Diese Systeme sind wichtig, weil nicht jede Enterprise-Last sinnvoll in Kubernetes verschoben wird. Manche Workloads brauchen direkten Zugriff auf Storage, spezielle Hardware oder besonders planbare Performance.

Es gibt erste Cloud-Komponenten. Diese werden für Object Storage, Testumgebungen, externe APIs oder skalierbare Web-Komponenten genutzt. Die Cloud bringt Flexibilität, aber auch neue Risiken: Kostenkontrolle, Identitätsmanagement, Netzwerkdesign und Datenklassifizierung müssen sauber geregelt werden.

Außerdem gibt es eine OpenShift-Plattform. Auf dieser Plattform sollen moderne Services laufen: Order Service, Billing Service, Customer Portal, Admin Portal, Reporting-Komponenten und Integrationsservices. OpenShift dient als standardisierte Laufzeitumgebung für containerisierte Anwendungen.

Die Ausgangslage ist also nicht:

Vorlage / Textauszug

Alles läuft in Kubernetes.

sondern:

Vorlage / Textauszug

Ein Teil läuft in Kubernetes.

Ein Teil läuft auf VMs.

Ein Teil läuft auf Bare Metal.

Ein Teil nutzt Cloud-Dienste.

Alle Teile müssen zusammen funktionieren.

Das ist der Kern des Projekts.

4. Warum Hybrid statt nur Cloud oder nur Kubernetes?

Viele Lernprojekte zeigen eine sehr einfache Welt: Anwendung bauen, Dockerfile schreiben, Kubernetes Deployment erstellen, fertig. Das ist für den Einstieg gut, aber nicht ausreichend für Enterprise-Architektur.

In echten Unternehmen gibt es Gründe, warum nicht alles in die Cloud oder nach Kubernetes wandert.

Eine relationale Hauptdatenbank kann auf Bare Metal bleiben, weil sie sehr große Datenmengen verarbeitet, spezielle Backup-Strategien nutzt oder bestehende Lizenzmodelle hat. Ein altes Reporting-System kann auf einer VM bleiben, weil es noch nicht modernisiert wurde, aber weiterhin gebraucht wird. Ein File Processing Service kann bewusst außerhalb von Kubernetes laufen, weil er sehr große Dateien verarbeitet und direkten Zugriff auf ein internes Dateisystem benötigt.

Gleichzeitig ist Kubernetes oder OpenShift sehr sinnvoll für moderne Services. Neue APIs, Web-Portale, Event Consumer, Backend-for-Frontend-Komponenten und Integrationsservices profitieren von Containerisierung, automatisierten Deployments, Health Checks, Skalierung und GitOps.

Cloud-Dienste sind wiederum stark bei Object Storage, globaler Verfügbarkeit, temporären Umgebungen, Managed Services und externer Integration.

Die Architekturentscheidung lautet daher:

Vorlage / Textauszug

Nicht jede Anwendung muss auf dieselbe Infrastruktur.

Jede Anwendung soll auf die Infrastruktur, die zu ihren Anforderungen passt.

Das Ziel ist keine ideologische Plattformentscheidung, sondern eine fachlich begründete Zuordnung.

5. Zielplattform des Projekts

Die Zielplattform heißt:

Vorlage / Textauszug

Enterprise Platform Lab

Sie besteht aus folgenden Bausteinen:

Vorlage / Textauszug
  1. Moderne Anwendungen auf OpenShift
  2. Datenbanken teilweise auf Bare Metal oder Managed Cloud
  3. interne Systeme auf virtuellen Maschinen
  4. Object Storage in Cloud oder MinIO
  5. zentrale Identität über Keycloak und LDAP/Active Directory
  6. Messaging über Kafka oder RabbitMQ
  7. GitOps mit Argo CD
  8. CI/CD mit GitHub Actions, GitLab CI oder Jenkins
  9. Observability mit Prometheus, Grafana, Loki und Tempo
  10. Secret Management mit Vault oder External Secrets
  11. Backup und Restore mit klaren Runbooks
  12. Dokumentation über README, ADRs und Betriebsanleitungen

Diese Plattform ist kein einzelnes Produkt. Sie ist ein Betriebsmodell.

6. Die wichtigsten Anwendungen im Lab

Customer Portal

Das Customer Portal ist die Weboberfläche für Kunden. Es zeigt Bestellungen, Rechnungen, Statusinformationen und Benachrichtigungen. Es läuft als moderne Frontend-Anwendung, zum Beispiel mit React oder Angular. Die Anwendung nutzt OIDC für Login und ruft Backend-APIs auf.

Das Customer Portal eignet sich gut für OpenShift oder Cloud Hosting, weil es skaliert werden kann und häufige Releases möglich sind.

Order Service

Der Order Service verwaltet Bestellungen. Er nimmt neue Bestellungen entgegen, validiert sie, speichert sie in PostgreSQL und veröffentlicht Events. Dieser Service ist ein typischer moderner Backend-Service.

Er läuft auf OpenShift, weil er gut containerisierbar ist, Health Checks unterstützt und über GitOps deployt werden kann.

Billing Service

Der Billing Service verarbeitet abrechnungsrelevante Events. Er erstellt Rechnungsentwürfe, prüft Zahlungsstatus und schreibt Audit-Informationen. Er konsumiert Events aus Kafka oder RabbitMQ.

Dieser Service zeigt, wie eventgetriebene Architektur funktioniert.

Reporting Service

Der Reporting Service erzeugt Auswertungen. Er kann teilweise als Kubernetes CronJob laufen, teilweise aber auch auf einer VM, wenn er alte Reporting-Werkzeuge oder spezielle Treiber benötigt.

Dieses Beispiel zeigt bewusst, dass nicht jeder Prozess automatisch ein dauerhafter Kubernetes-Service sein muss.

File Processing Service

Der File Processing Service verarbeitet große Dateien von Partnern. Er prüft Dateiformate, extrahiert Inhalte, schreibt Fehlerprotokolle und übergibt gültige Daten an interne Services.

Dieser Service kann auf Bare Metal oder VM laufen, wenn er sehr große Dateien verarbeitet oder direkten Zugriff auf Storage benötigt. Alternativ kann er modernisiert und als containerisierter Worker betrieben werden.

Admin Portal

Das Admin Portal ist für interne Mitarbeiter. Es erlaubt manuelle Korrekturen, Statusprüfungen und administrative Aktionen. Wegen sensibler Funktionen braucht es starke Authentifizierung, Rollenmodell, Audit Logging und klare Berechtigungen.

Dieses Portal zeigt, warum Security nicht am Ende angeklebt werden darf.

7. Zielarchitektur im Überblick

Skizze / Textauszug
Kunden
  ↓
Customer Portal
  ↓
API Gateway / Ingress / Route
  ↓
Order Service
  ↓
PostgreSQL
  ↓
Outbox Events
  ↓
Kafka / RabbitMQ
  ↓
Billing Service
  ↓
Invoice Store / Audit Log

Parallel dazu gibt es interne Mitarbeiter:

Skizze / Textauszug
Mitarbeiter
  ↓
Admin Portal
  ↓
Keycloak Login
  ↓
interne APIs
  ↓
Order / Billing / Reporting

Und es gibt Partner:

Skizze / Textauszug
Partnerunternehmen
  ↓
SFTP / API / Object Storage
  ↓
File Processing
  ↓
Validierung
  ↓
Events oder API-Aufrufe
  ↓
Order Service / Reporting

8. Zielbild als Diagramm

mermaid
flowchart LR
    Customer[Kunde] --> Portal[Customer Portal]
    Employee[Mitarbeiter] --> Admin[Admin Portal]
    Partner[Partnerunternehmen] --> File[File Processing]

    Portal --> Gateway[API Gateway / OpenShift Route]
    Admin --> Gateway
    Gateway --> Order[Order Service]
    Gateway --> Billing[Billing Service]

    Order --> OrderDB[(PostgreSQL Orders)]
    Billing --> BillingDB[(PostgreSQL Billing)]

    Order --> Outbox[Outbox Tabelle]
    Outbox --> Broker[Kafka oder RabbitMQ]
    Broker --> Billing

    File --> Broker
    Reporting[Reporting Service] --> OrderDB
    Reporting --> BillingDB

    Keycloak[Keycloak] --> Portal
    Keycloak --> Admin
    LDAP[LDAP / Active Directory] --> Keycloak

    Prometheus[Prometheus] --> Order
    Prometheus --> Billing
    Loki[Loki] --> Order
    Loki --> Billing
    Grafana[Grafana] --> Prometheus
    Grafana --> Loki

9. Infrastruktur-Zuordnung

Die Zielarchitektur verwendet bewusst unterschiedliche Infrastrukturtypen.

OpenShift wird für moderne Services genutzt. Dazu gehören Order Service, Billing Service, Customer Portal, API Gateway, kleinere Worker und GitOps-gesteuerte Anwendungen. Diese Workloads profitieren von standardisierten Deployments, automatischem Neustart, Skalierung und Observability.

Virtuelle Maschinen bleiben wichtig für Systeme, die noch nicht containerisiert sind oder die besondere Betriebsvorgaben haben. Dazu gehören interne Tools, ältere Reporting-Komponenten, Migrationshelfer oder Dienste mit speziellen Betriebssystemabhängigkeiten.

Bare Metal wird dort genutzt, wo Performance, Storage, Latenz oder Hardwarebindung entscheidend sind. Beispiele sind große Datenbanken, Backup-Systeme, Storage Gateways oder besonders datenintensive File-Verarbeitung.

Cloud wird dort eingesetzt, wo Flexibilität, schnelle Bereitstellung oder externe Verfügbarkeit wichtig ist. Beispiele sind Object Storage, Testumgebungen, externe Schnittstellen, CDN-nahe Webauslieferung oder temporäre Analyseumgebungen.

10. Erste Architekturentscheidung

ADR 001: Hybrid statt Single Platform

Vorlage / Textauszug

ADR 001: Hybrid Platform statt Single Platform

Status

Akzeptiert

Kontext

Die Alpine Retail & Services Group betreibt Anwendungen auf virtuellen Maschinen, Bare-Metal-Servern, Cloud-Diensten und einer OpenShift-Plattform. Eine vollständige Migration aller Workloads auf eine einzige Plattform wäre teuer, riskant und fachlich nicht sinnvoll.

Entscheidung

Das Zielsystem wird als hybride Enterprise-Plattform aufgebaut.

OpenShift wird als Standardplattform für moderne containerisierte Anwendungen verwendet. Virtuelle Maschinen bleiben für bestehende interne Anwendungen und Übergangssysteme erlaubt. Bare Metal bleibt für datenintensive Systeme und Storage-nahe Verarbeitung erlaubt. Cloud-Dienste werden gezielt für Object Storage, Testumgebungen und externe Integrationen eingesetzt.

Konsequenzen

Die Plattform benötigt klare Regeln für Netzwerk, Identität, Monitoring, Deployment und Dokumentation über Infrastrukturgrenzen hinweg.

11. Erste Repository-Struktur

Skizze / Textauszug
modern-hybrid-enterprise-platform-runtime-lab/
├── docs/
│   ├── architecture/
│   ├── adr/
│   ├── runbooks/
│   ├── security/
│   └── operations/
├── apps/
│   ├── customer-portal/
│   ├── admin-portal/
│   ├── order-service/
│   ├── billing-service/
│   ├── reporting-service/
│   └── file-processing-service/
├── platform/
│   ├── openshift/
│   ├── helm/
│   ├── kustomize/
│   ├── argocd/
│   └── network-policies/
├── infrastructure/
│   ├── cloud/
│   ├── vm/
│   ├── bare-metal/
│   └── ansible/
├── security/
│   ├── keycloak/
│   ├── rbac/
│   ├── secrets/
│   └── policies/
├── observability/
│   ├── prometheus/
│   ├── grafana/
│   ├── loki/
│   └── tempo/
├── ci-cd/
│   ├── github-actions/
│   ├── gitlab-ci/
│   └── jenkins/
└── README.md

12. Dokumentation als Architekturartefakt

In vielen Projekten wird Dokumentation als Zusatz betrachtet. Man schreibt zuerst Code, danach vielleicht eine README. In einem Enterprise-Projekt ist das zu spät.

Dokumentation ist ein Architekturartefakt. Eine README erklärt den Zweck eines Systems. Ein ADR erklärt, warum eine Entscheidung getroffen wurde. Ein Runbook erklärt, wie der Betrieb reagiert, wenn etwas schiefgeht. Ein Manifest zeigt die technische Umsetzung. Ein Test zeigt, ob eine Annahme überprüfbar ist.

13. Beispiel: README für den Order Service

Vorlage / Textauszug

Order Service

Zweck

Der Order Service verwaltet Kundenbestellungen. Er nimmt Bestellungen über eine REST API entgegen, validiert fachliche Regeln, speichert Bestellungen in PostgreSQL und veröffentlicht fachliche Events über das Outbox Pattern.

Grenzen

Der Service erstellt keine Rechnungen. Rechnungen werden vom Billing Service erzeugt.

Der Service versendet keine E-Mails. Benachrichtigungen werden über einen separaten Notification Service verarbeitet.

Der Service führt keine Kundenauthentifizierung selbst durch. Authentifizierung erfolgt über Keycloak und OIDC.

14. Erste technische Leitplanken

Für Java-Services gilt Java 21, Spring Boot oder Quarkus, REST APIs mit OpenAPI, strukturierte Logs, Health Checks, Metriken, Testcontainers, Flyway oder Liquibase und eine klare Paketstruktur.

Für Frontends gilt React oder Angular, OIDC Login, Trennung von API Client und UI-Komponenten, sichtbare Fehlerzustände, keine Secrets im Frontend und Build als Container Image.

Für OpenShift gilt Deployment, Service, Route, ConfigMap, Secret oder ExternalSecret, Resource Requests und Limits, Probes, NetworkPolicy, ServiceMonitor und Argo CD Application.

15. Erstes Beispiel: Service-Skelett

Skizze / Textauszug
order-service/
├── source-material/main/java/com/seb4u/demo/spring/order/
│   ├── OrderServiceApplication.java
│   ├── api/
│   │   ├── OrderController.java
│   │   └── CreateOrderRequest.java
│   ├── application/
│   │   ├── CreateOrderUseCase.java
│   │   └── OrderApplicationService.java
│   ├── domain/
│   │   ├── Order.java
│   │   ├── OrderId.java
│   │   ├── OrderStatus.java
│   │   └── OrderRepository.java
│   ├── infrastructure/
│   │   ├── persistence/
│   │   ├── messaging/
│   │   └── security/
│   └── observability/
│       └── OrderMetrics.java
├── source-material/main/resources/
│   ├── application.yaml
│   └── db/migration/
└── pom.xml

16. Beispielcode: erster Use Case

java
package com.seb4u.demo.spring.order.application;

import com.seb4u.demo.spring.order.domain.Order;
import com.seb4u.demo.spring.order.domain.OrderRepository;
import com.seb4u.demo.spring.order.domain.OrderStatus;
import com.seb4u.demo.spring.order.domain.events.OrderCreatedEvent;
import com.seb4u.demo.spring.order.infrastructure.messaging.OutboxPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.util.Objects;

@Service
public class OrderApplicationService implements CreateOrderUseCase {

    private final OrderRepository orderRepository;
    private final OutboxPublisher outboxPublisher;
    private final Clock clock;

    public OrderApplicationService(OrderRepository orderRepository, OutboxPublisher outboxPublisher, Clock clock) {
        this.orderRepository = orderRepository;
        this.outboxPublisher = outboxPublisher;
        this.clock = clock;
    }

    @Override
    @Transactional
    public CreateOrderResult createOrder(CreateOrderCommand command) {
        validate(command);

        Order order = Order.create(command.customerId(), command.items(), OrderStatus.CREATED, Instant.now(clock));
        Order savedOrder = orderRepository.save(order);

        outboxPublisher.store(new OrderCreatedEvent(
                savedOrder.id().value(),
                savedOrder.customerId().value(),
                savedOrder.totalAmount(),
                savedOrder.createdAt()
        ));

        return new CreateOrderResult(savedOrder.id().value(), savedOrder.status().name());
    }

    private void validate(CreateOrderCommand command) {
        Objects.requireNonNull(command, class="str">"command must not be null");
        if (command.customerId() == null || command.customerId().isBlank()) {
            throw new IllegalArgumentException(class="str">"customerId must not be blank");
        }
        if (command.items() == null || command.items().isEmpty()) {
            throw new IllegalArgumentException(class="str">"order must contain at least one item");
        }
    }
}

17. Warum Outbox wichtig ist

Ohne Outbox könnte der Order Service eine Bestellung speichern und danach ein Event an Kafka senden. Wenn Kafka in diesem Moment nicht erreichbar ist, wurde die Bestellung gespeichert, aber kein Event veröffentlicht. Der Billing Service erfährt nichts davon.

Das Outbox Pattern löst dieses Problem, indem das Event zunächst in derselben Datenbanktransaktion gespeichert wird wie die Bestellung.

18. Erste Datenbankmigration

sql
CREATE TABLE orders (
    id UUID PRIMARY KEY,
    customer_id VARCHAR(80) NOT NULL,
    status VARCHAR(40) NOT NULL,
    total_amount NUMERIC(12, 2) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL,
    updated_at TIMESTAMP WITH TIME ZONE
);

CREATE TABLE outbox_events (
    id UUID PRIMARY KEY,
    aggregate_type VARCHAR(120) NOT NULL,
    aggregate_id VARCHAR(120) NOT NULL,
    event_type VARCHAR(160) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL,
    published_at TIMESTAMP WITH TIME ZONE,
    publish_attempts INTEGER NOT NULL DEFAULT 0,
    last_error TEXT
);

19. Erste OpenShift-Sicht

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080

20. Typische Fehler

Ein häufiger Fehler ist, sofort Code zu schreiben, ohne Zielbild und Betriebsmodell zu klären. Ein zweiter Fehler ist, Kubernetes als Lösung für alles zu betrachten. Ein dritter Fehler ist, Security zu spät einzubauen. Ein vierter Fehler ist fehlende Dokumentation. Ein fünfter Fehler ist fehlende Observability.

21. Übung und Musterlösung

Erstelle eine eigene Zielbeschreibung mit Anwendungen, Plattformzuordnung, Authentifizierung, Event-Verarbeitung und Monitoring. Eine mögliche Lösung ordnet Customer Portal, Admin Portal, Order Service und Billing Service OpenShift zu, lässt Reporting vorerst auf VM, hält große Datenbanken auf Bare Metal und nutzt Keycloak, Kafka, Prometheus, Loki und Grafana als gemeinsame Plattformdienste.

22. Merksätze

Eine Enterprise-Plattform ist kein einzelnes Tool. Eine Hybrid-Architektur ist oft die realistische Antwort auf unterschiedliche Anforderungen. OpenShift eignet sich sehr gut für moderne Services, aber nicht jeder Workload muss dort laufen. Dokumentation ist Teil der Architektur, nicht Nacharbeit.

Teil B: Hybrid-Infrastruktur: Netzwerk, Cloud, VM, Bare Metal und OpenShift

Teil B: Hybrid-Infrastruktur: Netzwerk, Cloud, VM, Bare Metal und OpenShift

PlanZielbildBuildUmsetzungRunBetriebTeil B: Hybrid-Infrastruktur: Netzwerk, Cloud, VM, Bare Metal und OpenShift: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 2, Teil 3, Teil 4, Teil 5.

Infrastruktur-Fundament

Netzwerkzonen kompakt
Kompakte Themenübersicht
DMZIngressWAFApp-ZoneOpenShiftVMsDaten-ZoneDBQueuesMgmt/BackupNTP DNSBackupFWLeast PrivilegeOps

1. Warum Infrastruktur zuerst verstanden werden muss

Moderne Anwendungen hängen nicht in der Luft. Jede Anwendung braucht Netzwerk, DNS, Zertifikate, Routing, Storage, Identität, Monitoring und Zugriff auf andere Systeme. Wenn diese Grundlagen unklar sind, wird selbst ein sauber geschriebener Service im Betrieb instabil.

Viele Entwickler sehen Infrastruktur erst, wenn ein Problem entsteht: Die Anwendung startet lokal, aber im Cluster nicht. Die API funktioniert intern, aber von außen ist sie nicht erreichbar. Der Login funktioniert in der Testumgebung, aber in Produktion scheitert OIDC wegen falscher Redirect-URI. Die Datenbank ist erreichbar, aber nur aus einem Namespace, nicht aus dem anderen.

Deshalb beginnt ein Enterprise-Projekt nicht nur mit Code. Es beginnt mit einer klaren Infrastruktur-Landkarte.

2. Netzwerkzonen im Unternehmen

Netzwerkzonen
Kompakte Lernhilfe
InternetDMZWAFIngressApp-ZoneAPIsOpenShiftDaten-ZoneDBBrokerMgmtDNS NTPBackup

Ein Unternehmen trennt sein Netzwerk in Zonen. Diese Trennung schützt Systeme, begrenzt Schäden und macht Zugriffe kontrollierbar.

Eine einfache Enterprise-Landschaft kann folgende Zonen haben:

Vorlage / Textauszug

Internet

DMZ

Application Zone

Data Zone

Management Zone

Backup Zone

Development Zone

Die DMZ ist eine Übergangszone zwischen Internet und internem Netzwerk. Hier stehen Systeme, die von außen erreichbar sein müssen, aber nicht direkt Zugriff auf alle internen Systeme bekommen sollen.

Die Application Zone enthält interne Anwendungen, APIs, OpenShift Worker Nodes oder virtuelle Maschinen mit Applikationsdiensten. Die Data Zone enthält Datenbanken, Message Broker, Storage-Systeme und andere datenführende Komponenten. Die Management Zone enthält Administrationszugänge, Monitoring-Systeme, Jump Hosts, Bastion Hosts, Deployment-Server oder zentrale Management-Dienste.

3. Beispielhafte Netzwerkarchitektur

mermaid
flowchart TB
    Internet[Internet] --> WAF[Web Application Firewall]
    WAF --> LB[External Load Balancer / Router]

    subgraph DMZ[DMZ]
        LB
        APIGW[API Gateway / Reverse Proxy]
    end

    subgraph APP[Application Zone]
        OCP[OpenShift Cluster]
        VMAPP[VM Application Servers]
        WORKER[File Processing Worker]
    end

    subgraph DATA[Data Zone]
        PG[(PostgreSQL)]
        MQ[(Kafka / RabbitMQ)]
        S3[(MinIO / Object Storage)]
    end

    subgraph MGMT[Management Zone]
        GIT[Git Server]
        ARGO[Argo CD]
        MON[Monitoring]
        BASTION[Bastion Host]
    end

    subgraph BACKUP[Backup Zone]
        BACKUPSTORE[(Backup Repository)]
    end

    APIGW --> OCP
    OCP --> PG
    OCP --> MQ
    VMAPP --> PG
    WORKER --> S3
    MON --> OCP
    MON --> VMAPP
    BACKUPSTORE --> PG

4. DMZ

Die DMZ ist eine Sicherheitsgrenze. Ein Customer Portal oder API Gateway muss von außen erreichbar sein. Trotzdem soll ein Angreifer, der diese Komponente erreicht, nicht automatisch Zugriff auf interne Datenbanken bekommen.

In einer vereinfachten Architektur sieht der Zugriff so aus:

Skizze / Textauszug
Internet
↓
Firewall
↓
DMZ Load Balancer
↓
API Gateway
↓
interner Service
↓
Datenbank

Jede Stufe hat eigene Regeln.

5. VLANs und Subnetze

Subnetz-Aufteilung
Kompakte Lernhilfe
10.50.0.0/16Cloud VPC10.50.10.0/24public-a10.50.20.0/24private-a10.60.10.0/24dmz10.60.20.0/24dataRegelSubnetz = Rolle + Zone

Ein VLAN trennt Netzwerkverkehr logisch. Ein Subnetz beschreibt den IP-Adressbereich. In Unternehmen werden VLANs oft genutzt, um Zonen technisch zu trennen.

Skizze / Textauszug
VLAN 10: Management  -> 10.10.10.0/24
VLAN 20: DMZ         -> 10.10.20.0/24
VLAN 30: Application -> 10.10.30.0/24
VLAN 40: Data        -> 10.10.40.0/24
VLAN 50: Backup      -> 10.10.50.0/24
VLAN 60: Development -> 10.10.60.0/24

Eine IP-Adresse ist nicht nur eine Adresse. Sie verrät oft, in welcher Zone ein System steht.

6. Firewall-Regeln als Architekturartefakt

Firewall-Regeln sollten nicht nur in einer Firewall-Oberfläche existieren. Sie sollten dokumentiert werden. Eine gute Regel beschreibt Quelle, Ziel, Port, Protokoll, Zweck, verantwortlichen Service und Review-Datum.

Beispiel:

Vorlage / Textauszug

Quelle: OpenShift Namespace order-prod

Ziel: PostgreSQL Cluster orders-db

Port: 5432

Protokoll: TCP

Zweck: Order Service schreibt und liest Bestellungen

Verantwortlich: Team Order

Review: alle 6 Monate

Das ist besser als eine breite Regel wie Allow 10.10.30.0/24 to 10.10.40.0/24 any.

7. DNS

DNS wird oft unterschätzt. Wenn DNS falsch ist, sehen Fehler häufig wie Anwendungsfehler aus. Typische Symptome sind fehlerhafte OIDC-Redirects, TLS-Probleme, falsche Umgebung oder nicht erreichbare Datenbanken.

Beispielhafte Namen:

Vorlage / Textauszug

customer.alpine.example.com

admin.alpine.example.com

api.alpine.example.com

keycloak.alpine.example.com

grafana.alpine.example.com

orders-db.internal.alpine.example.com

kafka.internal.alpine.example.com

8. Load Balancer

Ein Load Balancer verteilt Anfragen auf mehrere Ziele. In Enterprise-Architekturen gibt es oft mehrere Load-Balancer-Ebenen:

Skizze / Textauszug
Externer Load Balancer
↓
OpenShift Router
↓
Service
↓
Pod

Wenn eine Anwendung nicht erreichbar ist, kann der Fehler an DNS, Firewall, Load Balancer, OpenShift Route, Service Selector, Pod Readiness oder Anwendungsport liegen.

9. Beispiel: Fehlersuche bei nicht erreichbarer API

bash
# 1. DNS prüfen
nslookup api.alpine.example.com

# 2. Verbindung prüfen
curl -vk https://api.alpine.example.com/order

# 3. OpenShift Route prüfen
oc get route -n order-prod

# 4. Service prüfen
oc get svc -n order-prod

# 5. Endpoints prüfen
oc get endpoints -n order-prod order-service

# 6. Pods prüfen
oc get pods -n order-prod -l app=order-service

# 7. Logs prüfen
oc logs -n order-prod deployment/order-service --tail=100

10. Virtuelle Maschinen im Lab

VMs eignen sich für Anwendungen mit Betriebssystemabhängigkeiten, alte Reporting-Systeme, interne Admin-Tools, Migrationshelfer, Tools mit grafischer Oberfläche, Systeme, die noch nicht containerisiert wurden, und Übergangsarchitekturen.

Eine VM braucht Patchmanagement, SSH-Kontrolle, lokale Logs, Systemdienste, Dateisystemüberwachung, Backup und Konfigurationsmanagement.

11. Beispiel: Ansible Baseline für Linux-VM

yaml
---
- name: Linux baseline for application servers
  hosts: app_servers
  become: true

  tasks:
    - name: Ensure required packages are installed
      ansible.builtin.package:
        name:
          - curl
          - vim
          - unzip
          - chrony
          - firewalld
        state: present

    - name: Ensure chrony is enabled
      ansible.builtin.service:
        name: chronyd
        enabled: true
        state: started

    - name: Disable password authentication for SSH
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: class="str">'^PasswordAuthentication'
        line: class="str">'PasswordAuthentication no'
      notify: Restart sshd

  handlers:
    - name: Restart sshd
      ansible.builtin.service:
        name: sshd
        state: restarted

12. Bare Metal im Lab

Bare Metal bedeutet, dass ein System direkt auf physischer Hardware läuft. Das kann sinnvoll sein, wenn Performance, Storage oder Hardwarezugriff wichtig sind. Beispiele sind große relationale Datenbanken, Backup-Repositories, Storage-Systeme, große File Processing Jobs und Systeme mit spezieller Hardware.

13. Storage-Grundlagen

Es gibt Block Storage, File Storage, Object Storage, Database Storage, Backup Storage und Ephemeral Storage. Ein typischer Anfängerfehler ist, Dateien im Container-Dateisystem zu speichern und zu erwarten, dass sie nach einem Neustart noch da sind. Container sind vergänglich.

14. File Processing mit Object Storage

Skizze / Textauszug
Partner
↓
SFTP Gateway oder Upload API
↓
Object Storage Bucket incoming
↓
File Processing Worker
↓
Validierung
↓
Object Storage Bucket processed oder rejected
↓
Event an Broker
↓
Order Service oder Reporting

15. OpenShift im Infrastrukturkontext

OpenShift ist Teil der Infrastruktur, aber nicht die gesamte Infrastruktur. Ein OpenShift Cluster braucht Nodes, Netzwerk, DNS, Zertifikate, Storage, Registry, Authentifizierung, Monitoring, Logging und Backup.

Ein Service im Cluster braucht Namespace, ServiceAccount, Deployment, Service, Route oder Ingress, ConfigMap, Secret, Resource Limits, Probes und NetworkPolicy.

16. Namespaces

Namespaces helfen bei Zugriffskontrolle, Resource Quotas, NetworkPolicies, Deployment-Trennung, Monitoring-Zuordnung, Kosten- und Verantwortungszuordnung.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: order-prod
  labels:
    owner: team-order
    environment: prod
    data-classification: internal
---
apiVersion: v1
kind: ResourceQuota
metadata:
  name: order-prod-quota
  namespace: order-prod
spec:
  hard:
    requests.cpu: class="str">"4"
    requests.memory: 8Gi
    limits.cpu: class="str">"8"
    limits.memory: 16Gi
    pods: class="str">"20"

17. NetworkPolicy

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-order-to-postgres-and-kafka
  namespace: order-prod
spec:
  podSelector:
    matchLabels:
      app: order-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              name: data-services
          podSelector:
            matchLabels:
              app: postgres-orders
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector:
            matchLabels:
              name: messaging
          podSelector:
            matchLabels:
              app: kafka
      ports:
        - protocol: TCP
          port: 9092

18. Zeit, NTP und Zertifikate

Wenn Server unterschiedliche Uhrzeiten haben, entstehen Probleme mit OIDC Tokens, TLS-Zertifikaten, Log-Korrelation, Audit-Nachweisen und Datenbankreplikation. Deshalb braucht jedes System sauberes NTP oder Chrony.

19. Infrastructure as Code

Infrastruktur sollte nicht nur in Oberflächen geklickt werden. Klicks sind schwer wiederholbar.

hcl
resource class="str">"aws_s3_bucket" class="str">"partner_files" {
  bucket = class="str">"alpine-partner-files-prod"

  tags = {
    owner       = class="str">"team-integration"
    environment = class="str">"prod"
    purpose     = class="str">"partner-file-processing"
  }
}

resource class="str">"aws_s3_bucket_versioning" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  versioning_configuration {
    status = class="str">"Enabled"
  }
}

20. Runbook: Datenbank nicht erreichbar

Vorlage / Textauszug

Runbook: Order Service erreicht PostgreSQL nicht

Symptom

Der Order Service liefert HTTP 503 oder Readiness Probe schlägt fehl.

Erste Prüfung

oc get pods -n order-prod -l app=order-service

oc logs -n order-prod deployment/order-service --tail=100

oc get secret -n order-prod order-db-secret -o yaml

Vorlage / Textauszug
Häufige Ursachen
  • falscher DNS-Eintrag
  • Firewall-Regel fehlt
  • Datenbankdienst gestoppt
  • Secret enthält falschen Host
  • NetworkPolicy blockiert Egress
  • Zertifikat oder TLS-Einstellung falsch

21. Typische Fehler

Typische Fehler sind fehlende Zonierung, fehlende Namenskonvention, manuelle Serverkonfiguration, fehlende Dokumentation von Firewall-Regeln, Storage-Missverständnisse und fehlende Zeit-Synchronisation.

22. Übung und Musterlösung

Erstelle eine Infrastruktur-Landkarte mit mindestens fünf Netzwerkzonen, drei Anwendungen, zwei Datenkomponenten, einem Broker, Monitoring, Backup-Pfad und administrativem Zugriffspfad. Eine mögliche Lösung trennt DMZ, Application Zone, Data Zone, Management Zone und Backup Zone und erlaubt aus dem Internet nur HTTPS auf den externen Load Balancer.

23. Merksätze

Infrastruktur ist Teil der Anwendungsarchitektur. DNS, Firewall, Zertifikate und Routing sind keine Nebensachen. Eine DMZ schützt interne Systeme vor direkter Exposition. Nicht jede Anwendung gehört in Kubernetes. Infrastructure as Code macht Infrastruktur nachvollziehbar.

Cloud-Architektur

Landing Zone und Hybrid-Anbindung
Kompakte Themenübersicht
Landing ZoneAccountsTagsBudgetsVPC/VNetSubnetsRoutingPublicLB / IngressPrivateApps / JobsManaged DBPostgresObject StorageArtefakteOn-PremVPN / DXGovernanceNord-SüdOst-WestDataFilesHybrid Link

1. Warum Cloud im Hybrid-Lab wichtig ist

Cloud ist in diesem Lernprojekt kein Ersatz für alles andere. Cloud ist ein zusätzlicher Baustein in einer hybriden Enterprise-Landschaft. Sie wird dort eingesetzt, wo sie echte Vorteile bringt: schnelle Bereitstellung, flexible Testumgebungen, Object Storage, externe Schnittstellen, skalierbare Frontend-Auslieferung, temporäre Analyseumgebungen oder Managed Services.

Der wichtigste Punkt lautet: Cloud ist nicht automatisch modern, sicher oder günstig. Eine schlecht geplante Cloud-Umgebung kann teurer, unübersichtlicher und unsicherer werden als eine klassische VM-Landschaft. Deshalb muss Cloud-Architektur genauso sorgfältig geplant werden wie Netzwerk, OpenShift, Bare Metal oder virtuelle Maschinen.

Für die Alpine Retail & Services Group bedeutet Cloud nicht: Alle Systeme werden in AWS, Azure oder Google Cloud verschoben. Die Cloud übernimmt gezielte Aufgaben. Das Customer Portal kann Cloud-nahe ausgeliefert werden. Partnerdateien können in Object Storage landen. Testumgebungen können temporär erstellt und wieder gelöscht werden. Externe APIs können über Cloud-nahe Gateways angebunden werden. Kritische Hauptdatenbanken können trotzdem auf Bare Metal oder in einer kontrollierten privaten Umgebung bleiben.

Cloud wird also nicht ideologisch verwendet, sondern fachlich begründet.

2. Grundprinzip einer Cloud-Landing-Zone

Eine Cloud-Landing-Zone ist die vorbereitete Grundfläche für Cloud-Workloads. Sie ist vergleichbar mit dem Fundament eines Gebäudes. Bevor Anwendungen einziehen, müssen Netzwerk, Identität, Logging, Sicherheit, Kostenkontrolle und Governance definiert sein.

Eine Landing-Zone beantwortet Fragen wie:

Vorlage / Textauszug

Welche Accounts oder Subscriptions gibt es?

Welche Netzwerke gibt es?

Wie werden Umgebungen getrennt?

Wer darf Ressourcen erstellen?

Wie werden Logs gesammelt?

Wie werden Kosten zugeordnet?

Welche Regionen sind erlaubt?

Welche Daten dürfen in die Cloud?

Wie wird die Cloud mit dem Rechenzentrum verbunden?

Ohne Landing-Zone entstehen schnell einzelne Cloud-Ressourcen ohne Zusammenhang. Ein Entwickler erstellt einen Storage Bucket, ein anderer eine Datenbank, ein dritter ein öffentliches Netzwerk. Nach einigen Monaten weiß niemand mehr, welche Ressource produktiv ist, welche Daten sie enthält und wer verantwortlich ist.

Eine gute Landing-Zone verhindert dieses Chaos nicht vollständig, aber sie setzt klare Leitplanken.

3. Accounts, Subscriptions und Umgebungen

In großen Umgebungen werden Cloud-Ressourcen nicht alle in einem einzigen Konto betrieben. Es gibt eine Trennung nach Umgebung, Verantwortung und Risiko.

Ein mögliches Modell:

Skizze / Textauszug
cloud-root
├── platform-shared
├── security-logging
├── network-hub
├── dev-workloads
├── test-workloads
├── prod-workloads
└── sandbox

platform-shared enthält gemeinsame Plattformdienste. security-logging sammelt Logs, Audit-Ereignisse und Sicherheitsinformationen. network-hub enthält zentrale Netzwerkkomponenten wie VPN, Transit Gateway oder Firewall. dev-workloads, test-workloads und prod-workloads trennen Anwendungen nach Umgebung. sandbox erlaubt Experimente, aber nur mit begrenzten Rechten und Budgetgrenzen.

Diese Trennung ist wichtig. Eine fehlerhafte Testressource darf nicht Produktionsdaten erreichen. Eine Sandbox darf keine unkontrollierten öffentlichen Datenbanken erzeugen. Produktionsressourcen müssen strenger geschützt und stärker überwacht werden als Entwicklungsressourcen.

4. VPC und VNet als Cloud-Netzwerk

Cloud-Netzwerk
Kompakte Lernhilfe
Landing ZoneAccountsBudgetsVPC / VNetRoutingSubnetsPublicLBPrivateAppsDBVPN

In AWS heißt das virtuelle Netzwerk VPC. In Azure heißt es VNet. Andere Clouds haben ähnliche Konzepte. Die Idee ist dieselbe: Man erstellt ein isoliertes Netzwerk in der Cloud und unterteilt es in Subnetze.

Für das Lab könnte die Cloud-Netzwerkstruktur so aussehen:

Skizze / Textauszug
Cloud Network 10.50.0.0/16
├── public-subnet-a      10.50.10.0/24
├── public-subnet-b      10.50.11.0/24
├── private-app-subnet-a 10.50.20.0/24
├── private-app-subnet-b 10.50.21.0/24
├── private-data-subnet-a 10.50.30.0/24
├── private-data-subnet-b 10.50.31.0/24
└── management-subnet    10.50.40.0/24

Öffentliche Subnetze sind nicht automatisch gefährlich, aber sie müssen streng kontrolliert werden. Dort stehen typischerweise Load Balancer, NAT Gateways oder Reverse Proxies. Anwendungen und Datenbanken gehören normalerweise in private Subnetze.

Ein typischer Fehler ist, eine Datenbank in ein öffentliches Subnetz zu stellen, weil es beim ersten Test einfacher ist. Das ist im Enterprise-Kontext fast immer falsch.

5. Cloud-Netzwerkdiagramm

mermaid
flowchart TB
    Internet[Internet] --> PublicLB[Public Load Balancer]

    subgraph Cloud[Cloud VPC / VNet]
        subgraph Public[Public Subnets]
            PublicLB
            NAT[NAT Gateway]
        end

        subgraph PrivateApp[Private App Subnets]
            UploadAPI[Partner Upload API]
            TestPortal[Test Customer Portal]
            Worker[Cloud Worker]
        end

        subgraph PrivateData[Private Data Subnets]
            ObjectStore[(Object Storage Endpoint)]
            ManagedDB[(Managed Database optional)]
        end

        subgraph Mgmt[Management Subnet]
            Runner[CI Runner]
            Bastion[Cloud Bastion]
        end
    end

    PublicLB --> UploadAPI
    UploadAPI --> ObjectStore
    Worker --> ObjectStore
    Worker --> ManagedDB
    Runner --> PrivateApp
    Bastion --> PrivateApp

    subgraph Datacenter[On-Prem Rechenzentrum]
        OCP[OpenShift]
        PG[(Bare Metal PostgreSQL)]
        Kafka[(Kafka / RabbitMQ)]
    end

    PrivateApp <-->|VPN / Direct Connect| OCP
    PrivateData <-->|VPN / Direct Connect| PG
    Worker --> Kafka

Dieses Diagramm zeigt, dass Cloud und Rechenzentrum miteinander verbunden sind. Die Cloud ist nicht isoliert. Sie muss in DNS, Identität, Monitoring, Sicherheit und Betriebsprozesse integriert werden.

6. Public und Private Subnets

Public Subnets haben typischerweise eine Route ins Internet. Private Subnets haben keine direkte öffentliche Erreichbarkeit. Sie erreichen externe Dienste über NAT, Proxy oder definierte Ausgänge.

Für Enterprise-Workloads gilt oft:

Vorlage / Textauszug

Load Balancer: public möglich

Anwendungen: private bevorzugt

Datenbanken: private

Message Broker: private

Management: private

CI Runner: private oder streng kontrolliert

Das Customer Portal kann öffentlich erreichbar sein, aber seine Backend-Services müssen nicht öffentlich sein. Eine Upload API kann extern erreichbar sein, aber sie sollte nicht direkt Datenbanken erreichen. Stattdessen schreibt sie Dateien in Object Storage und veröffentlicht kontrolliert Events oder Metadaten.

Ein gutes Netzwerkdesign reduziert die Angriffsfläche.

7. Security Groups und Network Security Groups

Freigaben in der Cloud
Kompakte Lernhilfe
InternetLB / IngressSG-App443 von LBSG-DB5432 nur von AppPrinzipnur explizit erlauben443allowallow

Cloud-Firewalls werden je nach Anbieter unterschiedlich genannt. In AWS heißen sie Security Groups, in Azure Network Security Groups. Sie definieren, welche Verbindungen erlaubt sind.

Eine gute Security Group ist eng begrenzt:

Vorlage / Textauszug

Erlaube HTTPS von Internet zum Public Load Balancer.

Erlaube HTTPS vom Load Balancer zur Upload API.

Erlaube Zugriff von Worker zu Object Storage Endpoint.

Erlaube keinen direkten Datenbankzugriff aus dem Internet.

Eine schlechte Regel sieht so aus:

Skizze / Textauszug
0.0.0.0/0 darf auf Port 5432 zugreifen.

Das würde bedeuten: Jede Adresse im Internet darf versuchen, PostgreSQL zu erreichen. Auch wenn Benutzername und Passwort stark sind, ist das eine unnötige und gefährliche Exposition.

Security Groups sind Teil der Architektur. Sie gehören in Code, Review und Dokumentation.

8. Beispiel: Terraform/OpenTofu für Netzwerk

hcl
variable class="str">"environment" {
  type    = string
  default = class="str">"prod"
}

resource class="str">"aws_vpc" class="str">"main" {
  cidr_block           = class="str">"10.50.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name        = class="str">"alpine-${var.environment}-vpc"
    Environment = var.environment
    Owner       = class="str">"platform-team"
  }
}

resource class="str">"aws_subnet" class="str">"private_app_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = class="str">"10.50.20.0/24"
  availability_zone = class="str">"eu-central-1a"

  tags = {
    Name = class="str">"alpine-${var.environment}-private-app-a"
    Tier = class="str">"application"
  }
}

resource class="str">"aws_subnet" class="str">"private_data_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = class="str">"10.50.30.0/24"
  availability_zone = class="str">"eu-central-1a"

  tags = {
    Name = class="str">"alpine-${var.environment}-private-data-a"
    Tier = class="str">"data"
  }
}

Dieses Beispiel ist noch kein vollständiges Netzwerk. Es zeigt aber das Prinzip: Netzwerk wird als Code beschrieben, bekommt Tags und ist reproduzierbar.

9. Object Storage als Cloud-Baustein

Object Storage ist einer der nützlichsten Cloud-Bausteine für hybride Enterprise-Systeme. Er eignet sich für Dateien, Exporte, Partnerdaten, Reports, Backups, statische Assets und Archivdaten.

Im Lab nutzen wir Object Storage für Partnerdateien:

Skizze / Textauszug
incoming/     neue Dateien von Partnern
processing/   Dateien in Verarbeitung
processed/    erfolgreich verarbeitete Dateien
rejected/     fachlich oder technisch abgelehnte Dateien
archive/      langfristige Ablage

Diese Struktur ist einfach, aber sehr hilfreich. Sie macht Zustände sichtbar und erlaubt Wiederholungen.

Eine Datei wird nicht einfach überschrieben. Sie bekommt eine eindeutige ID, Metadaten und einen Verarbeitungsstatus. Dadurch kann man später nachvollziehen, wann sie angekommen ist, wer sie geliefert hat, welche Prüfung fehlgeschlagen ist und ob ein erneuter Lauf möglich ist.

10. Beispiel: Bucket mit Versionierung und Verschlüsselung

hcl
resource class="str">"aws_s3_bucket" class="str">"partner_files" {
  bucket = class="str">"alpine-partner-files-prod"

  tags = {
    owner          = class="str">"team-integration"
    environment    = class="str">"prod"
    classification = class="str">"internal"
    purpose        = class="str">"partner-file-processing"
  }
}

resource class="str">"aws_s3_bucket_versioning" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  versioning_configuration {
    status = class="str">"Enabled"
  }
}

resource class="str">"aws_s3_bucket_server_side_encryption_configuration" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = class="str">"AES256"
    }
  }
}

resource class="str">"aws_s3_bucket_public_access_block" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Wichtig ist der letzte Block. Ein Bucket mit Partnerdaten darf nicht versehentlich öffentlich werden.

11. IAM: Identität in der Cloud

IAM regelt, wer oder was Cloud-Ressourcen verwenden darf. Dabei geht es nicht nur um menschliche Benutzer. Auch Anwendungen, CI/CD-Pipelines, Worker und externe Integrationen brauchen Identitäten.

Ein gutes IAM-Modell verwendet möglichst kurze, konkrete Rechte.

Nicht gut:

Vorlage / Textauszug

Service bekommt AdministratorAccess.

Besser:

Vorlage / Textauszug

Upload API darf nur in incoming/ schreiben.

File Processor darf aus incoming/ lesen, nach processed/ schreiben und nach rejected/ schreiben.

Reporting darf nur aus archive/ lesen.

Rechte sollten sich an Aufgaben orientieren, nicht an Bequemlichkeit.

12. Beispiel: IAM Policy für File Processor

json
{
  class="str">"Version": class="str">"2012-10-17",
  class="str">"Statement": [
    {
      class="str">"Sid": class="str">"ReadIncomingFiles",
      class="str">"Effect": class="str">"Allow",
      class="str">"Action": [
        class="str">"s3:GetObject",
        class="str">"s3:ListBucket"
      ],
      class="str">"Resource": [
        class="str">"arn:aws:s3:::alpine-partner-files-prod",
        class="str">"arn:aws:s3:::alpine-partner-files-prod/incoming/*"
      ]
    },
    {
      class="str">"Sid": class="str">"WriteProcessingResults",
      class="str">"Effect": class="str">"Allow",
      class="str">"Action": [
        class="str">"s3:PutObject"
      ],
      class="str">"Resource": [
        class="str">"arn:aws:s3:::alpine-partner-files-prod/processed/*",
        class="str">"arn:aws:s3:::alpine-partner-files-prod/rejected/*"
      ]
    }
  ]
}

Diese Policy ist fachlich verständlich. Sie beschreibt nicht nur technische Rechte, sondern spiegelt den Ablauf des File Processing wider.

13. Managed Databases: Nutzen und Grenzen

Cloud-Anbieter bieten Managed Databases an. Das kann sehr hilfreich sein, weil viele Betriebsaufgaben übernommen werden: Patching, Backups, Replikation, Monitoring-Integration und Hochverfügbarkeit.

Trotzdem ist eine Managed Database nicht automatisch die richtige Wahl.

Sie ist gut, wenn:

Vorlage / Textauszug
  • das Team weniger Datenbankbetrieb selbst machen möchte
  • die Daten in der Cloud bleiben dürfen
  • Latenz zu Cloud-Anwendungen niedrig sein soll
  • Standardfunktionen ausreichen
  • Backup und HA durch den Anbieter genutzt werden können

Sie ist problematisch, wenn:

Vorlage / Textauszug
  • Daten das Rechenzentrum nicht verlassen dürfen
  • spezielle Erweiterungen benötigt werden
  • bestehende Lizenzmodelle dagegen sprechen
  • sehr niedrige Latenz zu On-Prem-Systemen nötig ist
  • Kosten schwer planbar werden

Im Lab bleibt die Hauptdatenbank zunächst auf Bare Metal. Eine optionale Cloud-Datenbank kann für Testumgebungen oder weniger kritische Dienste genutzt werden.

14. Hybrid-Anbindung: VPN und Direct Connect

Eine hybride Plattform braucht eine Verbindung zwischen Cloud und Rechenzentrum. Dafür gibt es typischerweise VPN oder dedizierte Verbindungen wie Direct Connect, ExpressRoute oder Interconnect.

VPN ist oft schneller einzurichten und günstiger. Es eignet sich für Testumgebungen, kleinere Datenmengen und erste Integrationen.

Dedizierte Verbindungen sind stabiler, planbarer und leistungsfähiger. Sie eignen sich für produktive Lasten, große Datenmengen oder niedrige Latenzanforderungen.

Eine wichtige Regel lautet:

Vorlage / Textauszug

Die Cloud-Verbindung ist ein produktionskritischer Pfad, wenn produktive Anwendungen darüber miteinander kommunizieren.

Dann braucht sie Monitoring, Redundanz, Runbooks und klare Eskalationswege.

15. DNS und Hybrid Cloud

DNS wird in hybriden Architekturen anspruchsvoller. Interne Systeme müssen Cloud-Ressourcen auflösen können. Cloud-Systeme müssen interne Dienste finden können. Gleichzeitig dürfen interne Namen nicht unkontrolliert öffentlich sichtbar sein.

Ein mögliches Modell:

Vorlage / Textauszug

Öffentlich:

api.alpine.example.com

customer.alpine.example.com

Intern:

orders-db.internal.alpine.example.com

kafka.internal.alpine.example.com

object-storage.cloud.internal.alpine.example.com

Wichtig ist eine klare Trennung zwischen öffentlichen und internen Namen. DNS-Forwarding, Private Hosted Zones oder Private DNS-Zonen müssen sauber dokumentiert werden.

16. Cloud-Kostenkontrolle

Cloud-Kosten entstehen schnell. Nicht nur große Server kosten Geld, sondern auch Datenübertragung, Storage, Snapshots, Logs, NAT Gateways, Load Balancer, Managed Databases und dauerhaft laufende Testumgebungen.

Ein Enterprise-Projekt braucht daher Kostenregeln:

Vorlage / Textauszug

Jede Ressource bekommt Tags.

Jede Umgebung bekommt Budgetgrenzen.

Sandbox-Ressourcen haben Ablaufdaten.

Logs haben Retention-Regeln.

Snapshots werden aufgeräumt.

Nicht genutzte IPs und Volumes werden erkannt.

Teure Ressourcen brauchen Review.

Kostenkontrolle ist kein rein kaufmännisches Thema. Architekturentscheidungen beeinflussen Kosten direkt. Ein NAT Gateway, das große Datenmengen verarbeitet, kann teuer werden. Eine falsch konfigurierte Log-Retention kann hohe Speicherkosten erzeugen. Eine Testdatenbank, die monatelang läuft, obwohl sie niemand nutzt, verschwendet Budget.

17. Beispiel: Tagging-Standard

Vorlage / Textauszug

owner: team-integration

environment: prod

application: file-processing

cost-center: retail-platform

data-classification: internal

lifecycle: managed

review-date: 2026-12-31

Tags helfen bei Kosten, Sicherheit, Betrieb und Verantwortlichkeit. Sie ersetzen keine Dokumentation, aber sie machen Cloud-Ressourcen auswertbar.

18. Cloud Security Baseline

Eine Cloud Security Baseline definiert Mindestregeln. Für das Lab verwenden wir diese Baseline:

Vorlage / Textauszug

Keine öffentlichen Datenbanken.

Keine öffentlichen Buckets mit internen Daten.

MFA für menschliche Administratoren.

Least Privilege für Service-Rollen.

Zentrale Logs für Cloud-Audit-Ereignisse.

Verschlüsselung für Storage.

Security Groups ohne breite Any-Regeln.

Keine langfristigen Access Keys in Repositories.

Secrets über Secret Manager oder Vault.

Regelmäßige Prüfung ungenutzter Ressourcen.

Diese Regeln sind nicht spektakulär, aber sie verhindern viele typische Fehler.

19. Beispiel: Cloud-Runbook für öffentlichen Bucket-Fund

Vorlage / Textauszug

Runbook: Öffentlicher Bucket entdeckt

Symptom

Ein Security Scan meldet, dass ein Storage Bucket öffentlich lesbar oder schreibbar ist.

Sofortmaßnahmen
  1. Public Access sofort blockieren.
  2. Bucket Policy sichern und im Incident-Ticket dokumentieren.
  3. Prüfen, welche Objekte betroffen sind.
  4. Zugrifflogs auswerten.
  5. Datenklassifizierung prüfen.
  6. Security-Team informieren.
Technische Prüfung

aws s3api get-public-access-block --bucket alpine-partner-files-prod

aws s3api get-bucket-policy --bucket alpine-partner-files-prod

aws s3api get-bucket-acl --bucket alpine-partner-files-prod

Vorlage / Textauszug
Nacharbeit
  • Ursache analysieren
  • Terraform/OpenTofu Code korrigieren
  • Policy Review durchführen
  • Tests gegen öffentliche Buckets ergänzen
  • ADR oder Security Guideline aktualisieren

Dieses Runbook verbindet Betrieb, Security und Infrastructure as Code.

20. Cloud und OpenShift

Cloud und OpenShift können auf verschiedene Weise zusammenarbeiten.

Variante 1: OpenShift läuft im eigenen Rechenzentrum, Cloud liefert Zusatzdienste wie Object Storage oder externe APIs.

Variante 2: OpenShift läuft als Managed Service in der Cloud, zum Beispiel als OpenShift Dedicated oder vergleichbare Plattform.

Variante 3: Es gibt OpenShift on-prem und zusätzlich Cloud-native Workloads.

Für das Lab betrachten wir vor allem Variante 1 und Variante 3. Das ist realistisch für Unternehmen, die bereits ein Rechenzentrum betreiben und Cloud schrittweise integrieren möchten.

21. Beispiel: Partner Upload API in Cloud, Verarbeitung hybrid

Ein Partner lädt eine Datei in der Cloud hoch. Die Upload API prüft Authentifizierung, Dateigröße und Metadaten. Danach legt sie die Datei im Object Storage ab. Ein File Processing Worker verarbeitet die Datei. Dieser Worker kann in OpenShift, auf einer VM oder auf Bare Metal laufen.

Der Ablauf:

Skizze / Textauszug
Partner sendet Datei
↓
Cloud Load Balancer
↓
Upload API
↓
Object Storage incoming
↓
Event oder Polling
↓
File Processing Worker
↓
Validierung
↓
Object Storage processed/rejected
↓
Event an Kafka/RabbitMQ
↓
Order oder Reporting

Diese Architektur nutzt Cloud dort, wo sie stark ist: externe Erreichbarkeit und Object Storage. Die Verarbeitung bleibt flexibel.

22. Beispielcode: einfache Upload-Validierung

java
package com.seb4u.demo.spring.integration.upload;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.Set;

@Service
public class PartnerUploadValidator {

    private static final long MAX_SIZE_BYTES = 100 * 1024 * 1024;
    private static final Set<String> ALLOWED_CONTENT_TYPES = Set.of(
            class="str">"text/csv",
            class="str">"application/json",
            class="str">"application/xml"
    );

    public void validate(MultipartFile file, String partnerId) {
        if (partnerId == null || partnerId.isBlank()) {
            throw new IllegalArgumentException(class="str">"partnerId must not be blank");
        }

        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException(class="str">"file must not be empty");
        }

        if (file.getSize() > MAX_SIZE_BYTES) {
            throw new IllegalArgumentException(class="str">"file exceeds maximum upload size");
        }

        if (!ALLOWED_CONTENT_TYPES.contains(file.getContentType())) {
            throw new IllegalArgumentException(class="str">"unsupported content type: " + file.getContentType());
        }
    }
}

Dieser Code ist nur ein Einstieg. In einer echten Umgebung kommen Virenscan, Prüfsummen, Partnerberechtigungen, Audit Logging, Rate Limiting und Quarantäne hinzu.

23. Cloud Observability

Cloud-Ressourcen müssen genauso beobachtet werden wie Anwendungen im Rechenzentrum. Man braucht Metriken, Logs, Events und Kosteninformationen.

Wichtige Signale:

Vorlage / Textauszug

Bucket-Zugriffe

Fehlgeschlagene API-Aufrufe

IAM-Änderungen

öffentliche Resource Policies

Datenübertragung

NAT Gateway Traffic

Load Balancer Fehler

Datenbankverbindungen

Backup-Status

Cloud-Audit-Logs sind besonders wichtig. Sie zeigen, wer wann welche Ressource verändert hat. Ohne Audit-Logs kann ein öffentlicher Bucket oder eine gelöschte Datenbank schwer aufgeklärt werden.

24. Typische Cloud-Fehler

Ein häufiger Fehler ist ein öffentlicher Storage Bucket. Er entsteht oft durch Tests, unklare Policies oder manuelle Änderungen.

Ein zweiter Fehler ist zu breites IAM. Service-Rollen bekommen Administratorrechte, weil eine konkrete Policy am Anfang aufwendiger erscheint. Später weiß niemand, welche Rechte wirklich gebraucht werden.

Ein dritter Fehler ist fehlende Kostenkontrolle. Temporäre Ressourcen bleiben dauerhaft aktiv. Logs werden ewig gespeichert. Snapshots werden nicht aufgeräumt.

Ein vierter Fehler ist unklare Netzwerkführung. Cloud-Systeme sprechen direkt mit internen Systemen, ohne dass DNS, Firewall, Routing und Monitoring sauber dokumentiert sind.

Ein fünfter Fehler ist fehlende Datenklassifizierung. Teams speichern Daten in der Cloud, ohne zu wissen, ob diese Daten dort liegen dürfen.

25. Übung: Cloud-Zielbild für Partnerdateien

Entwirf ein Cloud-Zielbild für Partnerdateien.

Beantworte:

Vorlage / Textauszug

Wo laden Partner Dateien hoch?

Welche Komponente prüft die Datei?

Wo liegt die Datei vor der Verarbeitung?

Wo liegen abgelehnte Dateien?

Welche Rolle darf lesen?

Welche Rolle darf schreiben?

Wie wird öffentlicher Zugriff verhindert?

Wie wird ein Fehler im Upload-Prozess erkannt?

Wie wird die Ressource kostenmäßig zugeordnet?

26. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

Partner laden Dateien über eine Upload API hoch, die hinter einem Cloud Load Balancer liegt. Die API prüft Partner-ID, Dateigröße, Content-Type und Authentifizierung. Danach schreibt sie die Datei in den Bucket-Pfad incoming/.

Der File Processing Worker liest nur aus incoming/ und schreibt Ergebnisse nach processed/ oder rejected/. Abgelehnte Dateien werden mit Fehlerbericht abgelegt.

Öffentlicher Zugriff auf den Bucket wird durch Public Access Block, Bucket Policy und Security Baseline verhindert. IAM-Rollen sind getrennt: Upload API darf nur schreiben, Worker darf lesen und Ergebnisbereiche schreiben, Reporting darf nur archivierte Daten lesen.

Fehler werden über API-Metriken, Bucket-Events, Worker-Logs und Alerts erkannt. Alle Ressourcen tragen Tags für owner, environment, application, cost-center und data-classification.

27. ADR 002: Cloud gezielt nutzen

Vorlage / Textauszug

ADR 002: Cloud gezielt für Object Storage und externe Integration nutzen

Status

Akzeptiert

Kontext

Das Unternehmen betreibt eine hybride Landschaft. Nicht alle Systeme sollen in die Cloud migriert werden. Gleichzeitig gibt es Anforderungen an externe Partnerintegration, skalierbare Uploads, flexible Testumgebungen und Object Storage.

Entscheidung

Die Cloud wird gezielt für Object Storage, Partner Upload API, temporäre Testumgebungen und externe Integrationen genutzt. Kritische Hauptdatenbanken bleiben zunächst im Rechenzentrum oder auf dedizierter Infrastruktur.

Konsequenzen

Cloud-Ressourcen müssen über Infrastructure as Code erzeugt werden. Alle Ressourcen brauchen Tags, Verschlüsselung, Zugriffsbeschränkungen, Logging und Kostenkontrolle. Hybrid-Verbindungen werden als produktionskritische Infrastruktur betrachtet, wenn produktive Workloads sie nutzen.

28. Merksätze

Vorlage / Textauszug

Cloud ist kein Ersatz für Architektur.

Eine Landing-Zone verhindert Cloud-Wildwuchs.

Public Subnet bedeutet nicht: alles darf öffentlich sein.

Object Storage ist sehr nützlich, aber öffentliche Buckets sind ein schwerer Fehler.

IAM muss Aufgaben abbilden, nicht Bequemlichkeit.

Cloud-Kosten sind Architekturfolgen.

Hybrid-Verbindungen sind produktionskritisch, wenn produktive Workloads davon abhängen.

Cloud-Ressourcen ohne Tags, Logs und IaC sind schwer beherrschbar.

VM- und Bare-Metal-Betrieb

Workload-Zuordnung kompakt
Kompakte Themenübersicht
VMBatchWindowsToolsBare MetalDB-nahFile GatewayOpenShiftstatelessAPIsRegelnach I/Ound Betriebmehr I/Omehr ElastizitätZuordnung

1. Warum VM und Bare Metal weiterhin wichtig sind

In modernen Architekturgesprächen klingt es manchmal so, als müssten alle Systeme sofort in Container, Kubernetes oder Cloud-Dienste verschoben werden. Das ist in echten Unternehmen selten realistisch. Eine Enterprise-Landschaft besteht fast immer aus mehreren Laufzeitmodellen. Neue Services laufen vielleicht auf OpenShift, aber zentrale Datenbanken, alte Reporting-Systeme, File-Verarbeitung, Backup-Repositories oder Spezialsoftware laufen weiterhin auf virtuellen Maschinen oder physischer Hardware.

Das ist kein Fehler. Es ist eine Realität, die sauber gestaltet werden muss.

Eine virtuelle Maschine ist sinnvoll, wenn ein System ein eigenes Betriebssystem braucht, wenn vorhandene Software nicht containerfähig ist oder wenn ein kontrollierter Übergang von alter zu neuer Architektur geplant wird. Bare Metal ist sinnvoll, wenn Hardwareleistung, Storage-Zugriff, Latenz, spezielle Karten, große Datenmengen oder sehr planbare Performance wichtig sind.

Das Ziel dieses Kapitels ist nicht, Container gegen VMs auszuspielen. Das Ziel ist, Workloads fachlich richtig zuzuordnen und für VM- sowie Bare-Metal-Systeme dieselben Qualitätsprinzipien anzuwenden wie für moderne Plattformdienste: Automatisierung, Sicherheit, Monitoring, Backup, Dokumentation und nachvollziehbare Änderungen.

Merksatz:

Vorlage / Textauszug

VM und Bare Metal sind nicht automatisch Legacy.

Legacy entsteht erst, wenn Systeme undokumentiert, ungepatcht, unüberwacht und manuell betrieben werden.

---

2. Die Rolle von virtuellen Maschinen im Lab

Im Enterprise Platform Lab werden virtuelle Maschinen bewusst eingesetzt. Sie bilden Systeme ab, die in Unternehmen häufig vorhanden sind und nicht sofort modernisiert werden können.

Typische VM-Kandidaten im Lab sind:

Vorlage / Textauszug
  • Reporting Service mit alten Treibern
  • Legacy Adapter für alte interne Schnittstellen
  • Migrationshelfer für Datenübernahmen
  • interne Admin-Tools
  • SFTP Gateway
  • Batch Scheduler
  • Testsysteme für Übergangsarchitekturen

Eine VM bietet eine stabile Umgebung mit eigenem Betriebssystem. Das kann für alte Software wichtig sein. Gleichzeitig entstehen Pflichten: Patchmanagement, Benutzerverwaltung, SSH-Härtung, Firewall, Logrotation, Monitoring-Agent, Backup und Dokumentation.

Ein häufiger Fehler ist, VMs als Ausnahmebereich zu behandeln. Dann entsteht ein Schattenbetrieb: einige Systeme laufen modern mit GitOps, andere werden per SSH von Hand geändert. Das ist gefährlich, weil niemand zuverlässig rekonstruieren kann, wie ein Server wiederhergestellt werden soll.

Deshalb gilt im Lab:

Vorlage / Textauszug

Auch VM-Systeme werden beschrieben, versioniert und überwacht.

---

3. Die Rolle von Bare Metal im Lab

Bare Metal bezeichnet physische Server ohne Hypervisor-Schicht zwischen Betriebssystem und Hardware. In Enterprise-Umgebungen wird Bare Metal oft für besonders datenintensive oder storage-nahe Systeme verwendet.

Im Lab sind typische Bare-Metal-Kandidaten:

Vorlage / Textauszug
  • große PostgreSQL-Datenbank
  • Backup Repository
  • Storage Gateway
  • große File Processing Workloads
  • optionaler MinIO-Cluster
  • Monitoring- oder Logging-Speicher mit hohem Datenvolumen

Bare Metal kann sehr performant sein, aber es nimmt dem Betrieb auch weniger ab. Eine VM kann leichter verschoben, geklont oder gesichert werden. Ein physischer Server braucht klare Hardwaredokumentation, Ersatzteilstrategie, RAID-/Storage-Konzept, Firmwarepflege und definierte Restore-Prozesse.

Ein Bare-Metal-System ohne Runbook ist riskant. Wenn die Hardware ausfällt, reicht es nicht zu wissen, dass dort „die Datenbank“ läuft. Man muss wissen, welche Daten, welche Version, welche Konfiguration, welche Backup-Quelle und welche Wiederherstellungsreihenfolge relevant sind.

---

4. Workload-Zuordnung: VM, Bare Metal oder OpenShift?

Workload-Zuordnung
Kompakte Lernhilfe
VMstabilwenig dynamischBare Metalhoher I/Onahe HardwareOpenShiftskalierbarstandardisiertFrageBetrieb + I/O

Eine gute Architekturentscheidung entsteht nicht aus Geschmack, sondern aus Anforderungen.

Ein moderner REST-Service mit klarer Konfiguration, Health Checks und horizontaler Skalierung passt gut zu OpenShift. Ein altes Reporting-Tool mit lokalen Fonts, nativen Bibliotheken und speziellen ODBC-Treibern passt zunächst besser auf eine VM. Eine große Datenbank mit sehr hohem I/O-Bedarf kann auf Bare Metal besser aufgehoben sein.

Die Leitfrage lautet:

Vorlage / Textauszug

Welche Laufzeitumgebung reduziert Risiko und Betriebsaufwand für genau diesen Workload?

Nicht:

Vorlage / Textauszug

Welche Plattform ist gerade modern?

Beispielentscheidungen im Lab:

Vorlage / Textauszug

Order Service:

Läuft auf OpenShift, weil er containerisierbar ist, über Health Checks verfügt und regelmäßig deployt wird.

Reporting Service:

Läuft zunächst auf VM, weil alte Treiber und bestehende Reporting-Tools genutzt werden.

PostgreSQL Main Database:

Läuft im ersten Schritt auf Bare Metal, weil I/O, Backup und bestehende Betriebsprozesse kritisch sind.

File Processing Worker:

Kann auf VM oder Bare Metal laufen, wenn sehr große Dateien direkt aus internem Storage verarbeitet werden.

Billing Event Consumer:

Läuft auf OpenShift, weil er gut skalierbar und eventgetrieben ist.

---

5. Standard-Baseline für Linux-Server

Jeder Linux-Server im Lab bekommt eine Baseline. Diese Baseline legt Mindeststandards fest. Sie ist unabhängig davon, ob der Server eine VM oder Bare Metal ist.

Die Baseline umfasst:

Vorlage / Textauszug
  • eindeutiger Hostname
  • zentrale Zeitquelle
  • SSH nur mit Schlüssel
  • keine direkte Root-Anmeldung
  • Firewall aktiv
  • Monitoring-Agent installiert
  • Logrotation konfiguriert
  • automatische Sicherheitsupdates nach Policy
  • definierte Benutzergruppen
  • dokumentierter Zweck
  • Backup- oder Reinstallationsstrategie

Der Server soll nicht erst nach einem Audit gehärtet werden. Die Härtung beginnt beim Aufbau.

---

6. Beispiel: Ansible-Baseline für VM und Bare Metal

yaml
---
- name: Enterprise Linux baseline
  hosts: linux_servers
  become: true

  vars:
    management_network: class="str">"10.10.10.0/24"
    timezone_name: class="str">"Europe/Vienna"

  tasks:
    - name: Set timezone
      ansible.builtin.timezone:
        name: class="str">"{{ timezone_name }}"

    - name: Install baseline packages
      ansible.builtin.package:
        name:
          - chrony
          - firewalld
          - curl
          - vim
          - unzip
          - jq
          - rsync
          - logrotate
        state: present

    - name: Enable time synchronization
      ansible.builtin.service:
        name: chronyd
        enabled: true
        state: started

    - name: Disable direct root login
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: class="str">'^PermitRootLogin'
        line: class="str">'PermitRootLogin no'
      notify: restart sshd

    - name: Disable SSH password login
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: class="str">'^PasswordAuthentication'
        line: class="str">'PasswordAuthentication no'
      notify: restart sshd

    - name: Start and enable firewall
      ansible.builtin.service:
        name: firewalld
        enabled: true
        state: started

    - name: Allow SSH only from management network
      ansible.posix.firewalld:
        source: class="str">"{{ management_network }}"
        service: ssh
        permanent: true
        immediate: true
        state: enabled

    - name: Create application group
      ansible.builtin.group:
        name: appops
        state: present

  handlers:
    - name: restart sshd
      ansible.builtin.service:
        name: sshd
        state: restarted

Dieses Playbook ist kein vollständiges Hardening, aber es zeigt den richtigen Ansatz: Server werden nicht per Hand geklickt, sondern reproduzierbar beschrieben.

---

7. Systemd für interne Services

Nicht jede Anwendung läuft in Kubernetes. Ein interner Worker auf einer VM kann als systemd-Service betrieben werden.

Beispiel: File Processing Worker.

ini
[Unit]
Description=Alpine File Processing Worker
After=network-online.target
Wants=network-online.target

[Service]
User=fileworker
Group=fileworker
WorkingDirectory=/opt/alpine/file-worker
EnvironmentFile=/etc/alpine/file-worker.env
ExecStart=/usr/bin/java -jar /opt/alpine/file-worker/file-worker.jar
Restart=on-failure
RestartSec=10
TimeoutStopSec=45
SuccessExitStatus=143

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/alpine/file-worker /var/log/alpine/file-worker

[Install]
WantedBy=multi-user.target

Dieser Service zeigt mehrere wichtige Punkte. Die Anwendung läuft nicht als root. Konfiguration liegt außerhalb des JARs. Schreibrechte sind begrenzt. systemd startet den Dienst bei Fehlern neu. Das ist einfacher als Kubernetes, aber trotzdem professionell.

---

8. Konfiguration eines VM-basierten Workers

Eine VM-Anwendung braucht eine klare Konfigurationsdatei. Secrets gehören nicht in Git. Nicht geheime Konfiguration darf versioniert werden.

Beispiel für /etc/alpine/file-worker.env:

bash
APP_ENV=prod
APP_PORT=8085
INCOMING_DIR=/data/partner/incoming
PROCESSED_DIR=/data/partner/processed
REJECTED_DIR=/data/partner/rejected
BROKER_URL=kafka.internal.alpine.example.com:9092
METRICS_ENABLED=true
LOG_FORMAT=json

Sensitive Werte wie Passwörter, Tokens oder private Schlüssel würden über ein Secret-Verfahren bereitgestellt, zum Beispiel über Vault, eine geschützte Datei mit engen Rechten oder ein Betriebssystem-Secret-Management.

Ein häufiger Fehler ist, VM-Konfiguration nicht zu versionieren. Dann unterscheidet sich Produktion von Test, ohne dass es jemand bemerkt.

---

9. Logging auf VMs

Containerisierte Anwendungen schreiben Logs meist nach stdout. Auf VMs landen Logs oft in Dateien oder im systemd Journal. Beide Varianten sind möglich, aber sie müssen zentral gesammelt werden.

Für das Lab gilt:

Vorlage / Textauszug
  • Anwendungen schreiben strukturierte JSON-Logs
  • Logs enthalten Service-Name, Umgebung und Trace-ID
  • lokale Logs werden rotiert
  • zentrale Sammlung erfolgt über Agent
  • sensible Daten werden nicht geloggt

Beispiel für einen JSON-Logeintrag:

json
{
  class="str">"timestamp": class="str">"2026-07-05T20:15:00Z",
  class="str">"level": class="str">"ERROR",
  class="str">"service": class="str">"file-processing-worker",
  class="str">"environment": class="str">"prod",
  class="str">"traceId": class="str">"8e21c1b7f0a44e2d",
  class="str">"partnerId": class="str">"partner-42",
  class="str">"message": class="str">"File validation failed",
  class="str">"reason": class="str">"missing mandatory column customer_id"
}

Dieser Logeintrag ist nützlich, weil er einen Fehler konkret beschreibt, aber keine vertraulichen Inhalte der Datei ausgibt.

---

10. Monitoring für VM- und Bare-Metal-Systeme

Ein Server ist nicht gesund, nur weil er pingbar ist. Monitoring muss fachliche und technische Signale erfassen.

Für eine VM sind typische Metriken:

Vorlage / Textauszug
  • CPU-Auslastung
  • Arbeitsspeicher
  • Festplattenfüllstand
  • I/O Wait
  • Netzwerkfehler
  • Prozess läuft
  • Service-Port erreichbar
  • Log-Fehlerrate

Für Bare-Metal-Datenbanken kommen hinzu:

Vorlage / Textauszug
  • Datenbankverbindungen
  • langsame Queries
  • Replication Lag
  • WAL-Wachstum
  • Backup-Erfolg
  • Tabellen- und Indexgröße
  • Cache Hit Ratio

Für File Processing sind fachliche Metriken wichtig:

Vorlage / Textauszug
  • Anzahl eingegangener Dateien
  • Anzahl erfolgreich verarbeiteter Dateien
  • Anzahl abgelehnter Dateien
  • durchschnittliche Verarbeitungsdauer
  • älteste unverarbeitete Datei

Gerade die letzte Metrik ist wichtig. Wenn eine Datei seit Stunden im Eingang liegt, ist das oft kritischer als eine hohe CPU-Auslastung.

---

11. Beispiel: Prometheus Node Exporter Ziel

VMs und Bare-Metal-Server können über den Node Exporter überwacht werden.

yaml
scrape_configs:
  - job_name: class="str">"linux-nodes"
    static_configs:
      - targets:
          - class="str">"reporting-vm-01.internal.alpine.example.com:9100"
          - class="str">"file-worker-01.internal.alpine.example.com:9100"
          - class="str">"postgres-bm-01.internal.alpine.example.com:9100"
        labels:
          environment: class="str">"prod"
          platform: class="str">"vm-bare-metal"

In echten Umgebungen würde man Ziele nicht immer statisch pflegen. Man kann Service Discovery, Inventory oder Konfigurationsmanagement verwenden. Für das Lab ist die statische Variante gut verständlich.

---

12. Patchmanagement

Patchmanagement ist ein zentraler Unterschied zwischen Managed Services und selbst betriebenen Systemen. Bei VMs und Bare Metal trägt das Unternehmen mehr Verantwortung.

Ein Patchprozess braucht:

Vorlage / Textauszug
  • Klassifizierung der Systeme
  • Wartungsfenster
  • Testumgebung
  • Backup vor kritischen Updates
  • Rollback-Plan
  • Verantwortliche Personen
  • Dokumentation der Änderung
  • Nachkontrolle

Ein Produktivserver darf nicht spontan aktualisiert werden, nur weil ein Paketmanager Updates anbietet. Gleichzeitig dürfen Sicherheitsupdates nicht monatelang liegen bleiben. Enterprise-Betrieb bedeutet, zwischen Stabilität und Sicherheit kontrolliert abzuwägen.

Beispielregel:

Vorlage / Textauszug

Kritische Security-Patches: innerhalb von 72 Stunden bewerten und nach Freigabe einspielen.

Normale Betriebssystemupdates: monatliches Wartungsfenster.

Kernel-Updates: mit Neustartplanung und Service-Prüfung.

Datenbank-Major-Upgrades: eigenes Migrationsprojekt.

---

13. Backup für VMs

VM-Backups können auf mehreren Ebenen erfolgen: Image-Backup, Dateisicherung, Anwendungssicherung oder Datenbankdump. Nicht jede Ebene ersetzt die andere.

Ein VM-Image-Backup kann helfen, ein System schnell zurückzubringen. Es garantiert aber nicht immer konsistente Anwendungsdaten, besonders bei Datenbanken. Für Datenbanken braucht man transaktionskonsistente Backups oder native Datenbankmechanismen.

Für eine Reporting-VM könnte die Backup-Strategie so aussehen:

Vorlage / Textauszug
  • VM-Image einmal täglich
  • Konfiguration zusätzlich in Git
  • Reports als exportierbare Artefakte sichern
  • keine primären Geschäftsdaten dauerhaft nur auf der VM speichern
  • Restore-Test quartalsweise

Für einen File Worker:

Vorlage / Textauszug
  • Anwendungspaket reproduzierbar aus Build
  • Konfiguration in Ansible/Git
  • Eingangs- und Ergebnisdateien auf Object/File Storage
  • lokale temporäre Daten nicht als führende Datenquelle betrachten

Der wichtigste Punkt: Ein Backup ohne Restore-Test ist nur eine Hoffnung.

---

14. Backup für Bare-Metal-Datenbanken

Bei Datenbanken ist Backup besonders kritisch. Es reicht nicht, den Server zu sichern. Man muss wissen, bis zu welchem Zeitpunkt Daten wiederhergestellt werden können.

Für PostgreSQL kann ein produktionsnahes Konzept enthalten:

Vorlage / Textauszug
  • tägliches Full Backup
  • kontinuierliche WAL-Archivierung
  • Point-in-Time Recovery
  • verschlüsselte Backup-Ablage
  • getrennte Backup-Zone
  • regelmäßiger Restore-Test
  • Monitoring des Backup-Erfolgs

Beispiel für eine einfache Backup-Idee:

bash
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR=class="str">"/backup/postgres/orders"
TIMESTAMP=class="str">"$(date +%Y%m%d-%H%M%S)"
DB_NAME=class="str">"orders"

mkdir -p class="str">"${BACKUP_DIR}/${TIMESTAMP}"

pg_dump \
  --format=custom \
  --file=class="str">"${BACKUP_DIR}/${TIMESTAMP}/${DB_NAME}.dump" \
  class="str">"${DB_NAME}"

sha256sum class="str">"${BACKUP_DIR}/${TIMESTAMP}/${DB_NAME}.dump" \
  > class="str">"${BACKUP_DIR}/${TIMESTAMP}/${DB_NAME}.dump.sha256"

Dieses Skript ist bewusst einfach. Für produktive PostgreSQL-Backups wären Werkzeuge wie pgBackRest, Barman oder ein professionelles Backup-System besser geeignet. Das Beispiel zeigt aber, welche Nachweise wichtig sind: Backup-Datei, Zeitstempel und Prüfsumme.

---

15. Restore ist wichtiger als Backup

Viele Teams dokumentieren Backups, aber nicht den Restore. Im Ernstfall ist jedoch der Restore entscheidend.

Ein Restore-Runbook muss beantworten:

Vorlage / Textauszug
  • Wer darf Restore auslösen?
  • Welche Datenquelle wird wiederhergestellt?
  • Welcher Zeitpunkt ist gewünscht?
  • Welche Systeme müssen gestoppt werden?
  • Welche Reihenfolge ist einzuhalten?
  • Wie wird geprüft, ob Restore erfolgreich war?
  • Wie wird Kommunikation an Fachbereich und Betrieb durchgeführt?

Beispiel für eine Prüffrage:

Vorlage / Textauszug

Kann das Team eine Orders-Datenbank auf eine neue Umgebung wiederherstellen, ohne die alte Umgebung zu zerstören?

Wenn die Antwort nein ist, ist der Backup-Prozess nicht reif genug.

---

16. Security-Härtung für VM und Bare Metal

Security für Server beginnt mit einfachen, aber konsequenten Regeln:

Vorlage / Textauszug
  • keine Root-Logins per SSH
  • persönliche Admin-Konten statt gemeinsamem Benutzer
  • sudo mit Nachvollziehbarkeit
  • Firewall aktiv
  • nur benötigte Ports offen
  • regelmäßige Updates
  • keine Secrets in Shell-History
  • Logging sicherheitsrelevanter Ereignisse
  • Integritätsprüfungen für kritische Dateien

Besonders wichtig ist die Trennung von Anwendung und Administration. Ein Service-User sollte keine Administrationsrechte haben. Ein Admin-User sollte nicht für den regulären Anwendungsprozess verwendet werden.

Beispiel:

Vorlage / Textauszug

fileworker: führt Anwendung aus, keine Shell für normale Admins nötig

appops: Gruppe für Betrieb

root: nur über sudo und nachvollziehbar

---

17. Beispiel: sudo-Regel für eingeschränkten Betrieb

Vorlage / Textauszug

%appops ALL=(root) /bin/systemctl restart alpine-file-worker

%appops ALL=(root) /bin/systemctl status alpine-file-worker

%appops ALL=(root) /usr/bin/journalctl -u alpine-file-worker

Diese Regel erlaubt dem Betriebsteam, den Service zu prüfen und neu zu starten, aber nicht beliebige Root-Kommandos auszuführen. In echten Umgebungen muss man solche Regeln sorgfältig prüfen, weil manche erlaubten Kommandos indirekt Rechte ausweiten können.

---

18. Migration von VM zu OpenShift

Nicht jedes VM-System bleibt für immer auf einer VM. Manche Systeme werden später modernisiert. Dafür braucht man eine Migrationsstrategie.

Ein sinnvoller Weg:

Vorlage / Textauszug
  1. Ist-Zustand dokumentieren
  2. Konfiguration aus Anwendung entfernen
  3. Logs strukturieren
  4. Health Endpoint ergänzen
  5. externe Abhängigkeiten klären
  6. Anwendung containerisieren
  7. Datenhaltung trennen
  8. Testumgebung in OpenShift aufbauen
  9. Parallelbetrieb testen
  10. Umschaltung planen

Der wichtigste Schritt ist oft nicht das Dockerfile. Der wichtigste Schritt ist, die Anwendung so zu verändern, dass sie in einer dynamischen Umgebung laufen kann.

Typische Hindernisse:

Vorlage / Textauszug
  • lokale Dateipfade
  • feste IP-Adressen
  • Konfiguration im Code
  • Sessions im lokalen Speicher
  • fehlende Health Checks
  • fehlende Metriken
  • schwer automatisierbarer Build

---

19. Beispiel: Containerisierung eines alten Workers

Ein einfacher erster Dockerfile-Ansatz:

dockerfile
FROM eclipse-temurin:21-jre

RUN useradd --system --uid 10001 appuser

WORKDIR /app
COPY target/file-worker.jar /app/file-worker.jar

USER 10001

EXPOSE 8085

ENTRYPOINT [class="str">"java", class="str">"-jar", class="str">"/app/file-worker.jar"]

Das ist nur ein Start. Für OpenShift müssen zusätzlich Schreibpfade, SecurityContext, ConfigMaps, Secrets, Probes und Ressourcenlimits betrachtet werden. Wenn die Anwendung weiterhin lokale Dateien erwartet, braucht sie ein PersistentVolume oder muss auf Object Storage umgebaut werden.

---

20. Betriebliches Szenario: Reporting-VM fällt aus

Ausgangslage: Die Reporting-VM erzeugt jeden Morgen um 06:00 Uhr Management-Berichte. Um 06:30 Uhr meldet der Fachbereich, dass keine Reports vorhanden sind.

Ein unreifer Betrieb würde per SSH auf die VM gehen und zufällig Logs suchen.

Ein reifer Betrieb arbeitet nach Runbook:

Vorlage / Textauszug
  1. Monitoring prüfen: VM erreichbar?
  2. systemd Service prüfen: Reporting Scheduler aktiv?
  3. Logs des Jobs prüfen
  4. Datenbankverbindung prüfen
  5. Speicherplatz prüfen
  6. letzte erfolgreiche Ausführung prüfen
  7. manuelle Wiederholung nur nach Freigabe starten
  8. Fachbereich informieren
  9. Ursache dokumentieren
  10. dauerhafte Korrektur als Ticket erfassen

Das Ziel ist nicht nur, den Report einmal nachzuliefern. Das Ziel ist, die Ursache zu verstehen und Wiederholung zu verhindern.

---

21. Runbook: Reporting-Job fehlgeschlagen

Vorlage / Textauszug

Runbook: Reporting-Job fehlgeschlagen

Symptom

Der tägliche Management-Report wurde nicht erzeugt oder liegt nicht im Zielverzeichnis.

Betroffene Systeme
  • reporting-vm-01
  • PostgreSQL Orders
  • PostgreSQL Billing
  • Object Storage Bucket reports
Prüfung

ssh reporting-vm-01

systemctl status alpine-reporting-scheduler

journalctl -u alpine-reporting-scheduler --since "today 05:30"

df -h

Vorlage / Textauszug
Datenbankverbindung prüfen

nc -vz orders-db.internal.alpine.example.com 5432

nc -vz billing-db.internal.alpine.example.com 5432

Vorlage / Textauszug
Häufige Ursachen
  • Datenbank nicht erreichbar
  • Passwort abgelaufen
  • Zielverzeichnis voll
  • Object Storage nicht erreichbar
  • fehlerhafte Daten im Quellsystem
  • vorheriger Job hängt noch
Sofortmaßnahme

Wenn Datenbank und Storage erreichbar sind, kann der Job einmalig manuell wiederholt werden.

Nacharbeit
  • Ticket mit Ursache erstellen
  • Alert-Regel prüfen
  • falls Datenproblem: Fachbereich informieren
  • falls Infrastrukturproblem: zuständiges Team einbinden

---

22. Qualitätskriterien für VM- und Bare-Metal-Systeme

Ein VM- oder Bare-Metal-System gilt im Lab als produktionsnah, wenn folgende Kriterien erfüllt sind:

Vorlage / Textauszug
  • Zweck dokumentiert
  • Owner bekannt
  • Konfiguration versioniert
  • Installation automatisiert oder dokumentiert
  • Monitoring aktiv
  • Logs zentral sichtbar
  • Backup oder Wiederaufbau beschrieben
  • Restore getestet
  • Firewall-Regeln dokumentiert
  • Zugriff kontrolliert
  • Patchprozess definiert
  • Runbook vorhanden

Diese Kriterien verhindern, dass klassische Systeme zu unsichtbaren Risiken werden.

---

23. Typische Fehler bei VM und Bare Metal

Ein häufiger Fehler ist manuelle Konfiguration ohne Dokumentation. Dadurch entsteht ein Server, den niemand reproduzieren kann.

Ein zweiter Fehler ist fehlendes Monitoring. Solange niemand bemerkt, dass ein Dienst ausgefallen ist, ist der Betrieb blind.

Ein dritter Fehler ist falsches Backup-Verständnis. Ein VM-Snapshot ersetzt nicht automatisch ein konsistentes Datenbankbackup.

Ein vierter Fehler ist zu breiter Netzwerkzugriff. Wenn eine VM aus historischen Gründen viele Ports offen hat, wird sie zum Risiko.

Ein fünfter Fehler ist fehlende Migrationsstrategie. Systeme bleiben dann nicht aus fachlichen Gründen auf VMs, sondern weil niemand den ersten Schritt zur Modernisierung definiert.

---

24. Übung: VM-Betriebsmodell entwerfen

Entwirf ein Betriebsmodell für eine Reporting-VM im Lab.

Beantworte:

Vorlage / Textauszug
  1. Welchen Zweck hat die VM?
  2. Welche Datenquellen nutzt sie?
  3. Welche Ports müssen offen sein?
  4. Welche Benutzer dürfen zugreifen?
  5. Welche Logs werden gesammelt?
  6. Welche Metriken sind kritisch?
  7. Wie erfolgt Backup oder Wiederaufbau?
  8. Was steht im Runbook?
  9. Welche Teile könnten später nach OpenShift migriert werden?

---

25. Musterlösung zur Übung

Die Reporting-VM erzeugt tägliche Berichte aus Orders- und Billing-Daten. Sie liest aus PostgreSQL Orders und PostgreSQL Billing, schreibt Ergebnisdateien in Object Storage und wird über einen systemd Scheduler gestartet.

Offen sein müssen ausgehend Verbindungen zu den Datenbanken auf Port 5432 und zum Object Storage auf HTTPS. Eingehend ist nur SSH aus dem Management-Netz erlaubt. Der Betrieb greift über persönliche Konten und sudo-Regeln zu.

Logs werden im systemd Journal und zusätzlich zentral gesammelt. Kritische Metriken sind Job-Erfolg, Job-Dauer, letzter erfolgreicher Lauf, Speicherplatz, CPU, RAM und Datenbankverbindungsfehler.

Die VM selbst kann über Image-Backup gesichert werden. Wichtiger ist aber, dass Anwendungspaket, Konfiguration und Scheduler-Definition reproduzierbar sind. Reports liegen im Object Storage und sind versioniert.

Später kann die Report-Erzeugung in einen Kubernetes CronJob migriert werden, wenn Treiber, Konfiguration, Storage-Zugriff und Laufzeitverhalten containerfähig sind.

---

26. Merksätze

Vorlage / Textauszug

VMs sind weiterhin nützlich, wenn sie professionell betrieben werden.

Bare Metal ist sinnvoll für Workloads mit besonderen Performance- oder Storage-Anforderungen.

Manuelle Server sind ein Risiko, wenn sie nicht dokumentiert und automatisiert werden.

Ein Snapshot ist nicht automatisch ein gutes Backup.

Restore-Tests sind wichtiger als Backup-Versprechen.

Auch klassische Systeme brauchen Monitoring, Security, Runbooks und klare Owner.

Modernisierung beginnt oft damit, einen VM-Service beobachtbar, dokumentiert und reproduzierbar zu machen.

Kubernetes- und OpenShift-Plattform

Request-Pfad im Cluster
Kompakte Themenübersicht
ClientRouteServicePodsAppReplicasConfigSecretConfigMapPV/PVCStatefulTLSPersistenzMount

1. Warum Kubernetes/OpenShift im Lab eine zentrale Rolle spielt

Kubernetes ist in diesem Lernprojekt nicht einfach ein weiterer Deployment-Ort. Es ist die standardisierte Plattform für moderne, containerisierte Anwendungen. OpenShift erweitert Kubernetes um Enterprise-Funktionen wie integrierte Routes, Security Context Constraints, Build- und Image-Funktionen, Operatoren, Monitoring-Integration und ein stärkeres Betriebsmodell.

Für das Enterprise Platform Lab bedeutet das: Neue Services werden nicht mehr einzeln auf Server kopiert. Sie werden als Container Images gebaut, versioniert, geprüft und über deklarative Manifeste ausgerollt. Eine Änderung an einer Anwendung besteht nicht nur aus Code, sondern auch aus Deployment-Konfiguration, Ressourcenlimits, Health Checks, Security-Einstellungen, Monitoring und GitOps-Zustand.

Der wichtigste Unterschied zu einer klassischen VM ist:

Vorlage / Textauszug

Auf einer VM verwaltest du meistens einen langlebigen Server.

In Kubernetes verwaltest du gewünschte Zustände.

Ein Pod darf verschwinden. Ein Node darf ausfallen. Ein neues Image darf ausgerollt werden. Die Plattform versucht, den gewünschten Zustand wiederherzustellen. Genau deshalb müssen Anwendungen für diese Welt gebaut werden: stateless wo möglich, externes Storage wo nötig, Health Checks, robuste Konfiguration, kontrollierter Shutdown und gute Beobachtbarkeit.

---

2. Kubernetes-Grundidee

Kubernetes beschreibt Anwendungen nicht als einzelne Prozesse, sondern als Ressourcen. Eine Anwendung wird in Objekte zerlegt: Namespace, Deployment, Pod, Service, ConfigMap, Secret, Route oder Ingress, PersistentVolumeClaim, NetworkPolicy und weitere Bausteine.

Ein Deployment beschreibt, wie viele Instanzen einer Anwendung laufen sollen und welches Container Image verwendet wird. Ein Pod ist die kleinste ausführbare Einheit. Ein Service gibt Pods eine stabile interne Adresse. Eine Route oder ein Ingress macht eine Anwendung erreichbar. ConfigMaps und Secrets liefern Konfiguration. Probes sagen der Plattform, ob die Anwendung bereit oder gesund ist.

Für Entwickler ist wichtig: Kubernetes startet nicht einfach nur einen Container. Kubernetes betreibt einen gewünschten Zustand.

Beispiel:

Vorlage / Textauszug

Gewünschter Zustand:

  • order-service soll mit 2 Replicas laufen
  • Image: registry.example.com/alpine/order-service:1.0.0
  • Port: 8080
  • Readiness Probe aktiv
  • CPU und RAM begrenzt

Tatsächlicher Zustand:

  • nur 1 Pod läuft, weil ein Node ausgefallen ist

Kubernetes-Reaktion:

  • neuer Pod wird auf einem geeigneten Node gestartet

Diese Selbstheilung funktioniert aber nur, wenn die Manifeste sauber beschrieben sind.

---

3. OpenShift-spezifische Sicht

OpenShift ist Kubernetes, aber mit zusätzlichen Enterprise-Bausteinen. Dazu gehören unter anderem:

Vorlage / Textauszug
  • OpenShift Routes für externen HTTP(S)-Zugriff
  • Security Context Constraints für Container-Rechte
  • integrierte OAuth-Anbindung
  • Operator Lifecycle Manager
  • integriertes Monitoring
  • Projekt-/Namespace-Modell
  • Image Streams und BuildConfig in klassischen Setups
  • Web Console für Betrieb und Entwicklung

Für das Lab nutzen wir OpenShift als Zielplattform für moderne Anwendungen. Dabei achten wir darauf, keine OpenShift-Funktion blind zu verwenden. Manche Ressourcen sind Kubernetes-Standard, andere sind OpenShift-spezifisch. Das ist wichtig, wenn Anwendungen später auch auf anderen Kubernetes-Distributionen laufen sollen.

Beispiel:

Vorlage / Textauszug

Kubernetes-Standard:

  • Deployment
  • Service
  • ConfigMap
  • Secret
  • Ingress
  • NetworkPolicy
  • PersistentVolumeClaim

OpenShift-spezifisch:

  • Route
  • SecurityContextConstraints
  • BuildConfig
  • ImageStream

Im Lab verwenden wir für externe Zugriffe bevorzugt OpenShift Routes, erklären aber den Unterschied zu Ingress.

---

4. Namespace-Strategie

Namespaces nach Umgebung
Kompakte Lernhilfe
platform-shareddevtestprodsandboxLabels / Quotas / PoliciesUmgebung + Team + Kritikalität

Namespaces sind eine der wichtigsten Ordnungsstrukturen im Cluster. Sie trennen Ressourcen, Berechtigungen, Quotas, Netzwerkregeln und Verantwortlichkeiten.

Für das Lab verwenden wir keine einzige große Sammelzone. Stattdessen werden Fachbereiche und Umgebungen getrennt.

Vorlage / Textauszug

customer-dev

customer-test

customer-prod

order-dev

order-test

order-prod

billing-dev

billing-test

billing-prod

integration-dev

integration-test

integration-prod

platform-gitops

platform-monitoring

platform-logging

security-keycloak

messaging-prod

data-services

Diese Struktur verhindert nicht automatisch alle Fehler, aber sie macht Verantwortlichkeiten sichtbar. Ein Team erkennt, welche Ressourcen zu seinem Bereich gehören. Security kann Rechte gezielter vergeben. Betrieb kann Ressourcenverbrauch pro Bereich beobachten.

Ein häufiger Fehler ist, Entwicklung, Test und Produktion in denselben Namespace zu legen. Dadurch verschwimmen Grenzen. Ein Test-Deployment kann versehentlich produktive Secrets verwenden oder produktive Ressourcen blockieren.

---

5. Namespace-Definition mit Labels

Labels sind wichtig, weil viele andere Mechanismen darauf aufbauen. Monitoring, NetworkPolicies, Kostenberichte und GitOps können Labels auswerten.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: order-prod
  labels:
    app.kubernetes.io/part-of: order-domain
    environment: prod
    owner: team-order
    data-classification: internal
    network-zone: application

Diese Labels wirken einfach, sind aber wertvoll. Eine NetworkPolicy kann auf network-zone reagieren. Ein Kostenreport kann nach owner gruppieren. Ein Audit kann zeigen, welche Namespaces produktiv sind.

---

6. Deployment für den Order Service

Ein Deployment beschreibt, wie ein Service ausgeführt wird. Es sollte nicht nur Image und Port enthalten. Für einen produktionsnahen Service gehören Ressourcenlimits, Probes, Labels und Konfigurationsquellen dazu.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
  labels:
    app.kubernetes.io/name: order-service
    app.kubernetes.io/part-of: order-domain
spec:
  replicas: 2
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app.kubernetes.io/name: order-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: order-service
        app.kubernetes.io/part-of: order-domain
        environment: prod
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: order-service-config
            - secretRef:
                name: order-service-secret
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 60
            periodSeconds: 20
            timeoutSeconds: 3
            failureThreshold: 3
          resources:
            requests:
              cpu: class="str">"250m"
              memory: class="str">"512Mi"
            limits:
              cpu: class="str">"1000m"
              memory: class="str">"1024Mi"
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            capabilities:
              drop:
                - ALL

Dieses Manifest zeigt mehrere wichtige Entscheidungen. Der Service läuft nicht als privilegierter Container. Er hat klare Ressourcen. Die Health Checks unterscheiden zwischen readiness und liveness. Konfiguration wird nicht fest ins Image geschrieben, sondern über ConfigMap und Secret geliefert.

---

7. Readiness und Liveness richtig verstehen

Readiness und Liveness werden häufig verwechselt.

Readiness bedeutet: Kann dieser Pod gerade Traffic annehmen?

Liveness bedeutet: Ist dieser Pod grundsätzlich noch lebendig, oder muss er neu gestartet werden?

Ein Pod kann lebendig sein, aber nicht ready. Zum Beispiel wenn die Anwendung startet, aber die Datenbankverbindung noch nicht aufgebaut ist. In diesem Fall soll Kubernetes den Pod nicht töten, sondern nur noch keinen Traffic schicken.

Ein häufiger Fehler ist, die Liveness Probe zu streng zu machen. Wenn eine Datenbank kurz langsam ist und die Liveness Probe deshalb fehlschlägt, startet Kubernetes die Anwendung ständig neu. Dadurch wird die Störung schlimmer.

Besser ist:

Vorlage / Textauszug

Readiness prüft externe Abhängigkeiten vorsichtig.

Liveness prüft nur, ob die Anwendung intern blockiert oder tot ist.

Für Spring Boot kann man getrennte Actuator-Endpunkte nutzen:

yaml
management:
  endpoint:
    health:
      probes:
        enabled: true
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

---

8. Service und Route

Ein Deployment allein macht eine Anwendung noch nicht stabil erreichbar. Pods haben wechselnde IPs. Ein Kubernetes Service gibt ihnen eine stabile interne Adresse.

yaml
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: order-prod
  labels:
    app.kubernetes.io/name: order-service
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: order-service
  ports:
    - name: http
      port: 8080
      targetPort: http

In OpenShift kann eine Route diesen Service nach außen verfügbar machen.

yaml
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: order-service
  namespace: order-prod
spec:
  host: api.alpine.example.com
  path: /orders
  to:
    kind: Service
    name: order-service
  port:
    targetPort: http
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect

Diese Route nutzt TLS am Edge. In manchen Umgebungen ist Re-Encrypt besser, wenn die Verbindung vom Router zum Pod ebenfalls verschlüsselt bleiben soll. Diese Entscheidung hängt von Sicherheitsanforderungen und Zertifikatsmodell ab.

---

9. ConfigMap und Secret

Konfiguration und Secrets müssen getrennt werden. Eine ConfigMap enthält nicht-sensitive Einstellungen. Ein Secret enthält sensible Werte wie Passwörter, Tokens oder Zertifikatsmaterial.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
  namespace: order-prod
data:
  SPRING_PROFILES_ACTIVE: class="str">"openshift,prod"
  ORDER_OUTBOX_BATCH_SIZE: class="str">"100"
  KAFKA_BOOTSTRAP_SERVERS: class="str">"kafka.messaging-prod.svc.cluster.local:9092"
yaml
apiVersion: v1
kind: Secret
metadata:
  name: order-service-secret
  namespace: order-prod
type: Opaque
stringData:
  DB_HOST: class="str">"orders-db.internal.alpine.example.com"
  DB_PORT: class="str">"5432"
  DB_NAME: class="str">"orders"
  DB_USERNAME: class="str">"order_app"
  DB_PASSWORD: class="str">"change-me-in-real-secret-management"

In einer produktionsnahen Umgebung sollte das Secret nicht dauerhaft als Klartext in Git liegen. Besser sind External Secrets, Sealed Secrets, Vault oder ein anderes Secret-Management-Verfahren. Im Lab wird später External Secrets behandelt.

---

10. Resource Requests und Limits

Kubernetes plant Pods anhand von Requests. Limits begrenzen den maximalen Verbrauch. Ohne Requests weiß der Scheduler nicht zuverlässig, wie viel Kapazität ein Pod braucht. Ohne Limits kann ein fehlerhafter Prozess zu viel Speicher oder CPU verbrauchen.

Beispiel:

yaml
resources:
  requests:
    cpu: class="str">"250m"
    memory: class="str">"512Mi"
  limits:
    cpu: class="str">"1000m"
    memory: class="str">"1024Mi"

Das bedeutet nicht, dass jeder Service genau diese Werte braucht. Werte müssen beobachtet und angepasst werden. Für Java-Anwendungen ist Speicher besonders wichtig, weil JVM Heap, Metaspace, Threads und native Speicherbereiche berücksichtigt werden müssen.

Ein häufiger Fehler ist, ein Java-Container-Limit auf 512 MiB zu setzen und der JVM gleichzeitig einen zu großen Heap zu erlauben. Dann wird der Pod vom Kernel beendet, oft als OOMKilled.

Besser ist, JVM-Parameter und Container-Limits gemeinsam zu betrachten:

Vorlage / Textauszug

Container Memory Limit: 1024 MiB

Max Heap: etwa 60-70 Prozent davon

Reserve für Metaspace, Threads, Direct Memory und Native Memory

---

11. ServiceAccount und RBAC

Jeder Service sollte mit einem eigenen ServiceAccount laufen. Dadurch kann man Berechtigungen gezielt vergeben.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-service
  namespace: order-prod

Der Order Service braucht normalerweise keine Rechte, um Kubernetes-Ressourcen zu lesen oder zu ändern. Deshalb bekommt er keine breiten Cluster-Rollen.

Ein häufiger Fehler ist, Anwendungen mit zu vielen Rechten laufen zu lassen. Zum Beispiel mit Admin-Rechten im Namespace oder sogar Cluster-Rechten. Das ist gefährlich, wenn die Anwendung kompromittiert wird.

RBAC-Regel:

Vorlage / Textauszug

Ein Service bekommt nur die Rechte, die er wirklich benötigt.

Wenn er keine Kubernetes API nutzen muss, bekommt er keine zusätzlichen Kubernetes-Rechte.

---

12. SecurityContext und OpenShift SCC

Container sollten nicht als root laufen. Privilege Escalation sollte deaktiviert werden. Linux Capabilities sollten entfernt werden, wenn sie nicht gebraucht werden.

yaml
securityContext:
  allowPrivilegeEscalation: false
  runAsNonRoot: true
  capabilities:
    drop:
      - ALL

OpenShift hat zusätzlich Security Context Constraints. Diese bestimmen, welche Sicherheitskontexte Pods verwenden dürfen. OpenShift ist hier oft strenger als ein Standard-Kubernetes-Cluster. Das ist gut, weil unsichere Container schneller auffallen.

Ein typischer Fehler ist ein Container Image, das erwartet, als root zu laufen oder in feste Verzeichnisse schreiben zu dürfen. In OpenShift kann das scheitern.

Besser ist:

Vorlage / Textauszug
  • Image läuft ohne root
  • Anwendung schreibt nur in erlaubte Verzeichnisse
  • temporäre Dateien liegen in /tmp oder gemountetem Volume
  • keine festen UID-Annahmen im Image

---

13. NetworkPolicy für den Order Service

Eine produktionsnahe Plattform sollte nicht erlauben, dass jeder Pod mit jedem anderen Pod spricht. NetworkPolicies begrenzen Kommunikation.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-egress
  namespace: order-prod
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: order-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: messaging-prod
      ports:
        - protocol: TCP
          port: 9092
    - to:
        - ipBlock:
            cidr: 10.10.40.25/32
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: security-keycloak
      ports:
        - protocol: TCP
          port: 8443

Diese Policy erlaubt nur Kafka, die externe Datenbank-IP und Keycloak. In echten Umgebungen muss man außerdem DNS-Zugriffe berücksichtigen, da Services Namen auflösen müssen.

---

14. Persistent Volumes und wann man sie vermeidet

Nicht jede Anwendung braucht persistentes Volume. Moderne Backend-Services sollten nach Möglichkeit zustandslos sein. Daten liegen in Datenbanken, Object Storage oder spezialisierten Systemen.

Persistent Volumes sind sinnvoll für:

Vorlage / Textauszug
  • Datenbanken im Cluster
  • Broker im Cluster
  • Stateful Anwendungen
  • technische Komponenten wie Registry oder Monitoring-Speicher

Für den Order Service ist ein PersistentVolume meistens nicht nötig. Der Service speichert fachliche Daten in PostgreSQL und Events in der Outbox-Tabelle. Lokale Dateien wären riskant, weil Pods verschoben oder neu gestartet werden können.

Ein typischer Fehler ist:

Vorlage / Textauszug

Upload-Datei im Pod-Dateisystem speichern

später erwarten, dass sie noch vorhanden ist

Besser:

Vorlage / Textauszug

Upload-Datei in Object Storage speichern

Metadaten in Datenbank speichern

Verarbeitung über Event oder Job auslösen

---

15. Jobs und CronJobs

Nicht jede Aufgabe ist ein dauerhaft laufender Service. Manche Aufgaben laufen einmalig oder regelmäßig. Dafür gibt es Jobs und CronJobs.

Beispiel: nächtlicher Reporting-Export.

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-order-report
  namespace: reporting-prod
spec:
  schedule: class="str">"0 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: report-generator
              image: registry.example.com/alpine/reporting-service:1.0.0
              args:
                - class="str">"generate-nightly-order-report"
              envFrom:
                - configMapRef:
                    name: reporting-config
                - secretRef:
                    name: reporting-secret
              resources:
                requests:
                  cpu: class="str">"500m"
                  memory: class="str">"1Gi"
                limits:
                  cpu: class="str">"2000m"
                  memory: class="str">"4Gi"

concurrencyPolicy: Forbid verhindert, dass ein neuer Lauf startet, während der vorherige noch läuft. Das ist bei Reporting wichtig, damit keine doppelten Exporte entstehen.

---

16. GitOps mit Argo CD

GitOps bedeutet: Der gewünschte Zustand der Plattform liegt in Git. Argo CD vergleicht diesen Zustand mit dem Cluster und synchronisiert Änderungen.

Der Vorteil ist Nachvollziehbarkeit. Niemand muss per Hand oc apply ausführen, ohne dass Git den Zielzustand kennt. Jede Änderung wird als Pull Request sichtbar, geprüft und versioniert.

Beispiel für eine Argo CD Application:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: platform-gitops
spec:
  project: order-domain
  source:
    repoURL: https://git.example.com/alpine/platform-config.git
    targetRevision: main
    path: apps/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=false

In Produktion ist prune manchmal bewusst deaktiviert oder stark kontrolliert, damit Ressourcen nicht versehentlich gelöscht werden. Für Testumgebungen kann eine aggressivere Automatisierung sinnvoll sein.

---

17. Kustomize-Struktur

Für das Lab eignet sich Kustomize, um gemeinsame Basis-Manifeste und umgebungsspezifische Overlays zu trennen.

Skizze / Textauszug
platform-config/
└── apps/
    └── order-service/
        ├── base/
        │   ├── deployment.yaml
        │   ├── service.yaml
        │   ├── route.yaml
        │   ├── configmap.yaml
        │   ├── serviceaccount.yaml
        │   └── kustomization.yaml
        └── overlays/
            ├── dev/
            │   ├── kustomization.yaml
            │   └── patch-replicas.yaml
            ├── test/
            │   ├── kustomization.yaml
            │   └── patch-resources.yaml
            └── prod/
                ├── kustomization.yaml
                ├── patch-replicas.yaml
                ├── patch-resources.yaml
                └── patch-route-host.yaml

Der Vorteil: Die Grundstruktur bleibt gleich, aber Produktionswerte unterscheiden sich kontrolliert von Dev-Werten.

---

18. Helm oder Kustomize?

Helm und Kustomize lösen ähnliche, aber nicht identische Probleme.

Helm ist stark, wenn man parametrisierte Pakete veröffentlichen will. Viele Plattformkomponenten werden als Helm Charts bereitgestellt. Kustomize ist stark, wenn man vorhandene YAML-Manifeste über Overlays anpassen will.

Für Anwendungen im Lab nutzen wir bevorzugt Kustomize, weil es einfach nachvollziehbar ist. Für Plattformkomponenten wie Prometheus, Grafana, Loki oder externe Operatoren kann Helm sinnvoll sein.

Die Regel lautet:

Vorlage / Textauszug

Kustomize für transparente App-Manifeste.

Helm für paketierte Plattformkomponenten oder wiederverwendbare Charts.

Wichtig ist, nicht beides chaotisch zu vermischen. Ein Team sollte klar dokumentieren, welche Methode es verwendet.

---

19. Build, Image und Registry

Ein Container Image ist ein Lieferartefakt. Es muss versioniert, gescannt und nachvollziehbar sein.

Ein gutes Image hat:

Vorlage / Textauszug
  • eindeutigen Tag
  • Build-Metadaten
  • möglichst kleine Basis
  • keine unnötigen Tools
  • keine Secrets
  • nicht-root Ausführung
  • SBOM oder Scan-Ergebnis

Beispiel für ein Spring Boot Dockerfile:

dockerfile
FROM eclipse-temurin:21-jre-alpine

WORKDIR /app

RUN addgroup -S app && adduser -S app -G app

COPY target/order-service.jar /app/order-service.jar

USER app

EXPOSE 8080

ENTRYPOINT [class="str">"java", class="str">"-XX:MaxRAMPercentage=70", class="str">"-jar", class="str">"/app/order-service.jar"]

Dieses Image ist bewusst einfach. In einer erweiterten Version könnte man Distroless Images, Buildpacks oder mehrstufige Builds verwenden.

---

20. Beispiel: GitHub Actions Pipeline

yaml
name: order-service-ci

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-test-scan:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">'21'
          cache: maven

      - name: Build and test
        run: mvn -B clean verify

      - name: Build container image
        run: |
          docker build -t registry.example.com/alpine/order-service:${{ github.sha }} .

      - name: Scan image
        run: |
          trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/alpine/order-service:${{ github.sha }}

      - name: Push image
        if: github.ref == class="str">'refs/heads/main'
        run: |
          docker push registry.example.com/alpine/order-service:${{ github.sha }}

Diese Pipeline baut nicht nur Code. Sie testet, baut ein Image und scannt es. In einem vollständigen GitOps-Prozess würde danach ein Pull Request im Plattform-Repository den Image-Tag aktualisieren.

---

21. Observability im Cluster

Ein Service muss im Cluster beobachtbar sein. Dazu gehören Logs, Metriken und Traces.

Für Prometheus braucht man oft einen ServiceMonitor:

yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: order-service
  namespace: order-prod
  labels:
    release: platform-monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: order-service
  endpoints:
    - port: http
      path: /actuator/prometheus
      interval: 30s

Wenn dieser ServiceMonitor fehlt, läuft der Service vielleicht korrekt, aber niemand sammelt seine Metriken. Dadurch fehlen Dashboards und Alerts.

Wichtige Metriken für den Order Service:

Vorlage / Textauszug
  • HTTP Request Rate
  • HTTP Error Rate
  • Request Duration
  • JVM Memory
  • Database Connection Pool Usage
  • Outbox Pending Events
  • Kafka Publish Failures

---

22. Alerts für den Order Service

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: order-service-alerts
  namespace: order-prod
spec:
  groups:
    - name: order-service.rules
      rules:
        - alert: OrderServiceHighErrorRate
          expr: |
            sum(rate(http_server_requests_seconds_count{namespace=class="str">"order-prod",status=~class="str">"5.."}[5m]))
            /
            sum(rate(http_server_requests_seconds_count{namespace=class="str">"order-prod"}[5m])) > 0.05
          for: 10m
          labels:
            severity: warning
            service: order-service
          annotations:
            summary: class="str">"Order Service has high HTTP 5xx error rate"
            description: class="str">"More than 5 percent of requests failed for 10 minutes."

        - alert: OrderOutboxBacklogGrowing
          expr: order_outbox_pending_events > 1000
          for: 15m
          labels:
            severity: critical
            service: order-service
          annotations:
            summary: class="str">"Order outbox backlog is growing"
            description: class="str">"Pending outbox events exceed threshold. Billing may not receive new orders."

Diese Alerts verbinden technische Metriken mit fachlichen Risiken. Ein wachsender Outbox-Rückstand bedeutet nicht nur ein technisches Problem. Es bedeutet, dass Folgeprozesse wie Billing verzögert werden.

---

23. Rollout-Strategien

Kubernetes Deployments unterstützen Rolling Updates. Dabei werden alte Pods schrittweise durch neue ersetzt. Das funktioniert gut, wenn die Anwendung rückwärtskompatibel ist.

Ein Problem entsteht, wenn neue Anwendungsversion und Datenbankschema nicht zusammenpassen. Deshalb müssen Datenbankmigrationen vorsichtig geplant werden.

Eine robuste Regel lautet:

Vorlage / Textauszug

Datenbankänderungen müssen mindestens eine Version lang rückwärtskompatibel sein.

Beispiel:

Vorlage / Textauszug

Gut:

  • neue Spalte hinzufügen
  • Anwendung schreibt optional in neue Spalte
  • alte Version funktioniert weiterhin

Riskant:

  • Spalte löschen
  • bestehende Anwendung erwartet diese Spalte noch

Für kritische Systeme können Blue/Green oder Canary Deployments sinnvoll sein. Im Lab beginnen wir mit Rolling Updates und erklären später erweiterte Strategien.

---

24. PodDisruptionBudget

Ein PodDisruptionBudget verhindert, dass bei geplanten Wartungen zu viele Pods gleichzeitig verschwinden.

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service-pdb
  namespace: order-prod
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: order-service

Bei zwei Replicas stellt minAvailable: 1 sicher, dass mindestens ein Pod verfügbar bleiben soll. Das hilft bei Node-Wartungen, ersetzt aber keine echte Hochverfügbarkeitsplanung.

---

25. Typische Fehler in Kubernetes/OpenShift

Ein häufiger Fehler ist, nur ein Deployment zu erstellen und alles andere zu vergessen. Ohne Service, Route, Probes, Limits, SecurityContext und Monitoring ist der Service nicht produktionsnah.

Ein zweiter Fehler ist, Secrets direkt ins Image oder in Git zu schreiben. Das macht Rotation schwierig und erhöht das Risiko bei Leaks.

Ein dritter Fehler ist, alle Workloads mit Admin-Rechten laufen zu lassen. Das widerspricht Least Privilege.

Ein vierter Fehler ist fehlende NetworkPolicy. Dann kann eine kompromittierte Anwendung möglicherweise zu vielen internen Zielen sprechen.

Ein fünfter Fehler ist falsche Liveness Probe. Zu aggressive Liveness Checks erzeugen Neustart-Schleifen und verschlimmern Störungen.

Ein sechster Fehler ist fehlende Ressourcenplanung. Ohne Requests und Limits kann der Cluster Pods schlecht planen und bei Last instabil werden.

Ein siebter Fehler ist blinde Helm-Nutzung. Wenn niemand versteht, welche Ressourcen ein Chart erzeugt, wird Betrieb schwierig.

---

26. Runbook: Pod startet nicht

Vorlage / Textauszug

Runbook: Order Service Pod startet nicht

Symptom

Der Pod im Namespace `order-prod` ist im Status `CrashLoopBackOff`, `ImagePullBackOff`, `Pending` oder `CreateContainerConfigError`.

Erste Prüfung

oc get pods -n order-prod -l app.kubernetes.io/name=order-service

oc describe pod -n order-prod -l app.kubernetes.io/name=order-service

Vorlage / Textauszug
Logs prüfen

oc logs -n order-prod deployment/order-service --previous

oc logs -n order-prod deployment/order-service --tail=200

Vorlage / Textauszug
Häufige Ursachen
  • Image kann nicht aus Registry gezogen werden
  • Secret oder ConfigMap fehlt
  • Anwendung beendet sich wegen fehlerhafter Konfiguration
  • SecurityContext passt nicht zu OpenShift SCC
  • ResourceQuota verhindert Scheduling
  • Node-Kapazität reicht nicht
  • Readiness schlägt fehl wegen Datenbank oder Broker
Prüfung ImagePullBackOff

oc get events -n order-prod --sort-by=.metadata.creationTimestamp

oc get secret -n order-prod

Vorlage / Textauszug
Prüfung fehlende Konfiguration

oc get configmap order-service-config -n order-prod -o yaml

oc get secret order-service-secret -n order-prod -o yaml

Vorlage / Textauszug
Eskalation
  • Registry-Problem: Plattformteam
  • fehlende App-Konfiguration: Team Order
  • Cluster-Kapazität: Plattformbetrieb
  • SecurityContext/SCC: Security/Plattformteam

Dieses Runbook ist ein Beispiel dafür, wie Kubernetes-Betrieb konkret beschrieben wird. Es verbindet Status, Events, Logs, Konfiguration und Verantwortlichkeiten.

---

27. Übung: Order Service produktionsnah machen

Erstelle eine produktionsnahe Ressourcenliste für den Order Service.

Sie soll enthalten:

Vorlage / Textauszug
  1. Namespace
  2. ServiceAccount
  3. Deployment
  4. Service
  5. Route
  6. ConfigMap
  7. Secret-Konzept
  8. NetworkPolicy
  9. ServiceMonitor
  10. PrometheusRule
  11. PodDisruptionBudget
  12. Argo CD Application

Beschreibe für jede Ressource, warum sie gebraucht wird.

---

28. Musterlösung zur Übung

Eine mögliche Antwort:

Vorlage / Textauszug

Der Namespace `order-prod` trennt die produktive Order-Domäne von anderen Umgebungen.

Der ServiceAccount `order-service` verhindert, dass der Pod mit Default-Rechten läuft.

Das Deployment beschreibt Image, Replicas, Probes, Ressourcen und SecurityContext.

Der Service gibt den Pods eine stabile interne Adresse.

Die Route macht die API über den OpenShift Router erreichbar.

Die ConfigMap enthält nicht-sensitive Einstellungen wie Profil, Batch-Größe und Broker-Adresse.

Secrets werden nicht direkt im Git gespeichert, sondern später über External Secrets aus Vault oder einem Cloud Secret Manager synchronisiert.

Die NetworkPolicy erlaubt nur notwendige Ziele: PostgreSQL, Kafka, Keycloak und DNS.

Der ServiceMonitor sorgt dafür, dass Prometheus Metriken sammelt.

Die PrometheusRule definiert fachlich relevante Alerts, zum Beispiel hohe Fehlerrate und wachsenden Outbox-Rückstand.

Das PodDisruptionBudget schützt vor zu vielen gleichzeitigen Pod-Unterbrechungen.

Die Argo CD Application sorgt dafür, dass der gewünschte Zustand aus Git in den Cluster synchronisiert wird.

---

29. Merksätze

Vorlage / Textauszug

Kubernetes betreibt gewünschten Zustand, keine einzelnen Server.

OpenShift ergänzt Kubernetes um wichtige Enterprise-Funktionen.

Ein Deployment allein ist keine produktionsnahe Anwendung.

Readiness und Liveness dürfen nicht verwechselt werden.

Secrets gehören nicht ins Image und nicht ungeschützt ins Git.

NetworkPolicies sind ein konkreter Schritt zu Zero Trust.

GitOps macht Cluster-Zustand nachvollziehbar.

Monitoring ist Teil des Deployments, nicht ein späterer Zusatz.

Teil C: Identity, Security, Governance und Plattformregeln

Teil C: Identity, Security, Governance und Plattformregeln

PlanZielbildBuildUmsetzungRunBetriebTeil C: Identity, Security, Governance und Plattformregeln: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 6.

Identity, Security und Governance

OIDC- und Rollenfluss
Kompakte Themenübersicht
NutzerKeycloakOIDC / RollenTokenJWTAPIPrüfungRBAC/PoliciesBerechtigungLoginOIDCBearerClaims → Rechte

1. Warum Identity und Security nicht nachträglich angeklebt werden dürfen

In einer modernen Enterprise-Plattform ist Security kein späteres Zusatzkapitel. Sie bestimmt, wie Benutzer auf Anwendungen zugreifen, wie Services miteinander sprechen, wie Secrets geschützt werden, wie Änderungen genehmigt werden und wie ein Audit später nachvollziehen kann, was passiert ist.

Wenn Security erst am Ende eingebaut wird, entstehen typische Probleme:

Vorlage / Textauszug

Der Service funktioniert, aber niemand weiß, welche Rollen erlaubt sind.

Das Frontend ruft APIs direkt auf, aber Token werden unsauber gespeichert.

Ein interner Admin-Endpunkt ist versehentlich öffentlich erreichbar.

Secrets liegen in Klartext in Git.

Ein ServiceAccount hat Cluster-Admin-Rechte, obwohl er nur eine ConfigMap lesen müsste.

Logs enthalten sensible Daten.

Audit-Nachweise fehlen, obwohl die Anwendung produktiv läuft.

Identity und Security müssen deshalb von Anfang an mitgedacht werden. In diesem Lab bedeutet das: Jede Anwendung bekommt eine klare Authentifizierungsstrategie, ein Rollenmodell, technische Zugriffskontrollen, Secret-Management, Audit-Logging und dokumentierte Governance-Regeln.

Der wichtigste Grundsatz lautet:

Vorlage / Textauszug

Security ist nicht nur ein Firewall-Thema.

Security entsteht aus Identität, Rollen, Netzwerken, Secrets, Code, Deployment, Betrieb und Nachweisbarkeit.

---

2. Identity als zentrales Plattformthema

Identity beantwortet die Frage: Wer oder was greift auf ein System zu?

Dabei gibt es zwei Arten von Identitäten:

Vorlage / Textauszug

Menschliche Identitäten:

  • Kunden
  • interne Mitarbeiter
  • Administratoren
  • Entwickler
  • externe Partner

Technische Identitäten:

  • ServiceAccounts
  • CI/CD Runner
  • GitOps Controller
  • Batchjobs
  • API Clients
  • Monitoring-Komponenten

Ein häufiger Fehler ist, nur menschliche Benutzer zu betrachten. In einer Enterprise-Plattform sind technische Identitäten mindestens genauso wichtig. Ein Billing Service, der Events konsumiert, braucht eine technische Identität. Ein Argo CD Controller, der Deployments ausführt, braucht Rechte. Ein CI Runner, der Container Images baut, braucht Zugriff auf Registry und Quellcode.

Das Ziel ist nicht, allen Identitäten möglichst viele Rechte zu geben. Das Ziel ist, jeder Identität genau die Rechte zu geben, die sie für ihre Aufgabe braucht.

Vorlage / Textauszug

Least Privilege:

Eine Identität bekommt nur die Rechte, die sie wirklich benötigt.

---

3. Keycloak im Zielbild

Im Lab verwenden wir Keycloak als Identity Provider. Keycloak übernimmt Login, Token-Ausstellung, OIDC-Flows, Rollen und die Anbindung an ein bestehendes LDAP oder Active Directory.

Die Zielarchitektur sieht so aus:

mermaid
flowchart LR
    User[Kunde oder Mitarbeiter] --> Portal[Customer Portal / Admin Portal]
    Portal --> Keycloak[Keycloak]
    Keycloak --> LDAP[LDAP / Active Directory]
    Portal --> API[Backend API]
    API --> TokenCheck[Token Validierung]
    TokenCheck --> Roles[Rollen und Claims]

Keycloak ist dabei nicht nur ein Login-Bildschirm. Keycloak ist ein zentrales Sicherheitssystem. Es entscheidet, welche Clients existieren, welche Redirect-URIs erlaubt sind, welche Rollen vergeben werden und welche Token-Informationen an Anwendungen weitergegeben werden.

Für das Lab gibt es mehrere Clients:

Vorlage / Textauszug

customer-portal

admin-portal

order-service

billing-service

reporting-service

integration-api

Das Customer Portal verwendet einen Browser-basierten OIDC-Flow. Das Admin Portal benötigt strengere Rollen. Service-zu-Service-Kommunikation kann über Client Credentials oder interne Plattformmechanismen abgesichert werden.

---

4. Unterschied zwischen Authentifizierung und Autorisierung

Authentifizierung beantwortet:

Vorlage / Textauszug

Wer bist du?

Autorisierung beantwortet:

Vorlage / Textauszug

Was darfst du tun?

Ein Benutzer kann korrekt angemeldet sein und trotzdem nicht berechtigt sein, eine bestimmte Aktion auszuführen.

Beispiel:

Vorlage / Textauszug

Mitarbeiter Max ist angemeldet.

Max darf Bestellungen lesen.

Max darf keine Rechnung stornieren.

Max darf keine Benutzerrollen ändern.

Im Code muss dieser Unterschied sichtbar sein. Ein API-Endpunkt darf nicht nur prüfen, ob ein Token vorhanden ist. Er muss prüfen, ob die passende Rolle oder Berechtigung vorhanden ist.

---

5. Rollenmodell für das Lab

Für das Lab definieren wir fachliche Rollen:

Vorlage / Textauszug

CUSTOMER

SUPPORT_AGENT

ORDER_MANAGER

BILLING_SPECIALIST

REPORTING_VIEWER

SECURITY_AUDITOR

PLATFORM_OPERATOR

DEPLOYMENT_MANAGER

Diese Rollen dürfen nicht zufällig wachsen. Jede Rolle braucht eine Bedeutung.

Beispiele:

Vorlage / Textauszug

CUSTOMER:

Darf eigene Bestellungen sehen und neue Bestellungen anlegen.

SUPPORT_AGENT:

Darf Kundenbestellungen lesen, aber keine Rechnungen stornieren.

ORDER_MANAGER:

Darf Bestellungen prüfen, Status ändern und Eskalationen auslösen.

BILLING_SPECIALIST:

Darf Rechnungen prüfen, Zahlungsstatus korrigieren und Rechnungsläufe starten.

SECURITY_AUDITOR:

Darf Audit-Informationen lesen, aber keine fachlichen Daten ändern.

PLATFORM_OPERATOR:

Darf Betriebsinformationen sehen und Deployments beobachten.

DEPLOYMENT_MANAGER:

Darf Releases in definierte Umgebungen freigeben.

Wichtig ist: Rollen sind fachliche Begriffe. Kubernetes RBAC ist ein technischer Mechanismus. Beides darf nicht verwechselt werden.

---

6. Keycloak Realm-Konzept

Ein Realm in Keycloak ist ein abgegrenzter Sicherheitsbereich. Für das Lab verwenden wir einen Realm:

Vorlage / Textauszug

alpine-enterprise

Darin liegen Clients, Rollen, Gruppen und Benutzerzuordnungen.

Beispielhafte Struktur:

Skizze / Textauszug
Realm: alpine-enterprise
├── Clients
│   ├── customer-portal
│   ├── admin-portal
│   ├── order-service
│   └── billing-service
├── Realm Roles
│   ├── CUSTOMER
│   ├── SUPPORT_AGENT
│   ├── ORDER_MANAGER
│   ├── BILLING_SPECIALIST
│   └── SECURITY_AUDITOR
├── Groups
│   ├── customers
│   ├── support
│   ├── billing
│   └── platform-ops
└── Identity Provider
    └── LDAP / Active Directory

In produktionsnahen Umgebungen muss man entscheiden, ob Entwicklung, Test und Produktion getrennte Realms oder getrennte Keycloak-Instanzen verwenden. Für ein Lab reicht oft ein Realm mit klaren Umgebungsclients. Für echte Produktion ist stärkere Trennung sinnvoll.

---

7. Beispiel: Keycloak Client-Konfiguration als JSON

json
{
  class="str">"clientId": class="str">"admin-portal",
  class="str">"name": class="str">"Admin Portal",
  class="str">"protocol": class="str">"openid-connect",
  class="str">"publicClient": true,
  class="str">"standardFlowEnabled": true,
  class="str">"directAccessGrantsEnabled": false,
  class="str">"redirectUris": [
    class="str">"https://admin.alpine.example.com/*"
  ],
  class="str">"webOrigins": [
    class="str">"https://admin.alpine.example.com"
  ],
  class="str">"attributes": {
    class="str">"pkce.code.challenge.method": class="str">"S256"
  }
}

Diese Konfiguration zeigt mehrere wichtige Punkte. Das Admin Portal ist ein Browser-Client. Es nutzt den Standard Flow und PKCE. Direct Access Grants sind deaktiviert, weil Passwörter nicht direkt an die Anwendung gegeben werden sollen. Redirect-URIs sind explizit eingeschränkt.

Ein häufiger Fehler wäre:

Vorlage / Textauszug

redirectUris: [class="str">"*"]

Das ist bequem, aber gefährlich. Es öffnet die Tür für Redirect-Missbrauch.

---

8. OIDC-Token und Claims

OIDC-Tokenfluss
Kompakte Lernhilfe
UserKeycloakID TokenLoginAccess TokenAPI ZugriffServiceClaims prüftRollen / RechteauthoritiesOIDCBearer

Ein OIDC-Token enthält Claims. Claims sind Aussagen über Benutzer oder technische Clients.

Beispiele:

json
{
  class="str">"sub": class="str">"9f7a2d4c-1234-44f0-91a1-abcdef123456",
  class="str">"preferred_username": class="str">"maria.schmidt",
  class="str">"email": class="str">"maria.schmidt@example.com",
  class="str">"realm_access": {
    class="str">"roles": [
      class="str">"SUPPORT_AGENT",
      class="str">"REPORTING_VIEWER"
    ]
  },
  class="str">"aud": class="str">"admin-portal",
  class="str">"iss": class="str">"https://keycloak.alpine.example.com/realms/alpine-enterprise"
}

Eine Anwendung darf Claims nicht blind vertrauen, wenn Token-Signatur, Issuer und Audience nicht geprüft wurden. Ein Token ist nur gültig, wenn es von der richtigen Quelle kommt, korrekt signiert ist, nicht abgelaufen ist und für den erwarteten Client oder Service bestimmt ist.

---

9. Spring Security im Order Service

Der Order Service soll Token validieren und Rollen auswerten.

java
package com.seb4u.demo.spring.order.infrastructure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(class="str">"/actuator/health/**").permitAll()
                        .requestMatchers(class="str">"/actuator/prometheus").hasRole(class="str">"PLATFORM_OPERATOR")
                        .requestMatchers(class="str">"/api/orders/**").authenticated()
                        .anyRequest().denyAll()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
                )
                .build();
    }

    @Bean
    Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
        return converter;
    }
}

Diese Konfiguration macht mehrere Dinge bewusst:

Vorlage / Textauszug

Health ist öffentlich, damit Kubernetes Probes funktionieren.

Prometheus-Metriken sind nicht öffentlich für alle.

Order APIs brauchen Authentifizierung.

Unbekannte Pfade werden abgelehnt.

Rollen werden aus Keycloak Claims übersetzt.

---

10. Keycloak-Rollen in Spring Authorities übersetzen

java
package com.seb4u.demo.spring.order.infrastructure.security;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class KeycloakRealmRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

    @Override
    public Collection<GrantedAuthority> convert(Jwt jwt) {
        Map<String, Object> realmAccess = jwt.getClaimAsMap(class="str">"realm_access");

        if (realmAccess == null || !realmAccess.containsKey(class="str">"roles")) {
            return List.of();
        }

        Object rolesObject = realmAccess.get(class="str">"roles");

        if (!(rolesObject instanceof Collection<?> roles)) {
            return List.of();
        }

        return roles.stream()
                .filter(String.class::isInstance)
                .map(String.class::cast)
                .map(role -> new SimpleGrantedAuthority(class="str">"ROLE_" + role))
                .collect(Collectors.toUnmodifiableSet());
    }
}

Dieser Code ist klein, aber wichtig. Ohne solche Übersetzung sieht Spring die Keycloak-Rollen oft nicht in der erwarteten Form.

Ein typischer Fehler ist, Rollen im Token zu haben, aber im Code hasRole("ORDER_MANAGER") zu verwenden, ohne die Rollen vorher als ROLE_ORDER_MANAGER bereitzustellen.

---

11. Method Security im Use Case

Security sollte nicht nur im Controller stehen. Kritische Aktionen sollten auch an der Anwendungsschicht abgesichert werden.

java
package com.seb4u.demo.spring.order.application;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderApprovalService {

    private final OrderRepository orderRepository;
    private final AuditLog auditLog;

    public OrderApprovalService(OrderRepository orderRepository, AuditLog auditLog) {
        this.orderRepository = orderRepository;
        this.auditLog = auditLog;
    }

    @Transactional
    @PreAuthorize(class="str">"hasRole('ORDER_MANAGER')")
    public void approveOrder(String orderId, String reason) {
        Order order = orderRepository.findRequired(orderId);
        order.approve(reason);
        orderRepository.save(order);

        auditLog.record(
                class="str">"ORDER_APPROVED",
                orderId,
                class="str">"Order approved with reason: " + reason
        );
    }
}

Der Vorteil: Selbst wenn später ein zweiter Controller oder ein interner API-Pfad diese Methode aufruft, bleibt die Sicherheitsregel erhalten.

---

12. Kubernetes RBAC ist etwas anderes als Fachrollen

Kubernetes RBAC steuert, was ein Benutzer oder ServiceAccount im Cluster darf.

Beispiel:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-service
  namespace: order-prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: order-service-config-reader
  namespace: order-prod
rules:
  - apiGroups: [class="str">""]
    resources: [class="str">"configmaps"]
    verbs: [class="str">"get", class="str">"list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: order-service-config-reader-binding
  namespace: order-prod
subjects:
  - kind: ServiceAccount
    name: order-service
    namespace: order-prod
roleRef:
  kind: Role
  name: order-service-config-reader
  apiGroup: rbac.authorization.k8s.io

Diese YAML-Datei hat nichts mit der fachlichen Rolle ORDER_MANAGER zu tun. Sie beschreibt, was der Pod im Cluster darf. Eine fachliche Rolle beschreibt, was ein Benutzer in der Anwendung darf.

Beide Ebenen sind nötig.

---

13. ServiceAccount-Fehler, die man vermeiden muss

Ein sehr gefährlicher Fehler ist, Anwendungen mit zu mächtigen ServiceAccounts laufen zu lassen.

Schlecht:

Vorlage / Textauszug

ServiceAccount order-service bekommt cluster-admin.

Warum schlecht?

Vorlage / Textauszug

Wenn die Anwendung kompromittiert wird, kann der Angreifer den Cluster kontrollieren.

Besser:

Vorlage / Textauszug

order-service bekommt nur Rechte, die er wirklich braucht.

Im Idealfall braucht er gar keine Kubernetes API Rechte.

Viele Anwendungen brauchen überhaupt keinen Zugriff auf die Kubernetes API. Dann sollte ihnen auch kein besonderer Zugriff gegeben werden.

---

14. Secrets: Warum sie besonders behandelt werden müssen

Secrets sind Zugangsdaten oder vertrauliche Informationen.

Beispiele:

Vorlage / Textauszug

Datenbankpasswörter

API Tokens

OAuth Client Secrets

TLS Private Keys

Broker Credentials

SFTP Schlüssel

Ein Secret darf nicht in Klartext in Git liegen. Auch nicht in einem privaten Repository, wenn es vermeidbar ist.

Es gibt mehrere Möglichkeiten:

Vorlage / Textauszug

Kubernetes Secret direkt manuell pflegen

Sealed Secrets verwenden

External Secrets Operator verwenden

Vault verwenden

Cloud Secret Manager verwenden

Für ein Enterprise-Lab ist External Secrets oder Vault sinnvoll, weil GitOps dann keine Klartext-Secrets enthält.

---

15. ExternalSecret Beispiel

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: order-db-secret
  namespace: order-prod
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: platform-vault
    kind: ClusterSecretStore
  target:
    name: order-db-secret
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: prod/order/postgres
        property: username
    - secretKey: password
      remoteRef:
        key: prod/order/postgres
        property: password
    - secretKey: host
      remoteRef:
        key: prod/order/postgres
        property: host

Diese Definition enthält nicht das Passwort. Sie beschreibt nur, woher das Passwort geladen wird. Das ist ein wichtiger Unterschied.

---

16. Secret Rotation

Secret Rotation bedeutet, Zugangsdaten regelmäßig oder anlassbezogen zu erneuern.

Anlässe:

Vorlage / Textauszug

geplanter Sicherheitszyklus

Mitarbeiterwechsel

Verdacht auf Leak

Provider-Vorgabe

Audit-Feststellung

Ein Secret-Rotation-Prozess muss beantworten:

Vorlage / Textauszug

Wer darf Rotation auslösen?

Wie wird neues Secret erzeugt?

Wie wird es verteilt?

Welche Anwendungen müssen neu gestartet werden?

Wie wird geprüft, ob alles funktioniert?

Wie wird zurückgerollt, wenn Rotation fehlschlägt?

Ohne Prozess kann Secret Rotation gefährlich werden, weil produktive Systeme plötzlich keine Datenbankverbindung mehr bekommen.

---

17. SecurityContext für Pods

Container sollten nicht unnötig privilegiert laufen.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
spec:
  template:
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.0.0
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            capabilities:
              drop:
                - ALL

Diese Einstellungen reduzieren Angriffsmöglichkeiten. Nicht jede Anwendung funktioniert sofort mit read-only Root Filesystem. Dann muss man bewusst temporäre Verzeichnisse konfigurieren, statt die Sicherheitsregel einfach zu entfernen.

---

18. OpenShift SCC

OpenShift verwendet Security Context Constraints. SCCs steuern, welche Sicherheitskontexte Pods verwenden dürfen.

Ein häufiger Anfängerfehler ist, bei Problemen sofort eine privilegierte SCC zu vergeben.

Schlecht:

bash
oc adm policy add-scc-to-user privileged -z order-service -n order-prod

Das löst kurzfristig Startprobleme, öffnet aber ein großes Sicherheitsloch.

Besser ist, die Ursache zu verstehen:

Vorlage / Textauszug

Braucht der Container wirklich root?

Schreibt er in verbotene Pfade?

Braucht er Linux Capabilities?

Sind Dateirechte im Image falsch?

Muss ein EmptyDir für temporäre Dateien definiert werden?

Security-Härtung bedeutet oft, das Image und die Anwendung sauberer zu bauen, nicht die Plattformregeln abzuschalten.

---

19. Network Security und Zero Trust

Zero Trust bedeutet nicht, dass man niemandem vertraut und alles blockiert. Es bedeutet: Vertrauen wird nicht aus Netzwerkposition abgeleitet.

Früher war ein typisches Denken:

Vorlage / Textauszug

Wenn ein System im internen Netz steht, ist es vertrauenswürdig.

Modernes Denken:

Vorlage / Textauszug

Auch interne Systeme müssen authentifiziert, autorisiert, beobachtet und begrenzt werden.

Für das Lab heißt das:

Vorlage / Textauszug
  • Services kommunizieren nur über definierte Pfade.
  • NetworkPolicies begrenzen Pod-Kommunikation.
  • APIs prüfen Tokens.
  • Secrets werden zentral verwaltet.
  • Admin-Zugriffe laufen über kontrollierte Wege.
  • Audit Logs dokumentieren kritische Aktionen.

---

20. Audit Logging

Audit Logging beantwortet nicht nur die Frage, was technisch passiert ist. Es beantwortet fachliche Nachweisfragen.

Beispiele:

Vorlage / Textauszug

Wer hat eine Bestellung freigegeben?

Wer hat eine Rechnung storniert?

Wer hat eine Rolle geändert?

Welcher Service hat einen Export gestartet?

Wann wurde ein Secret rotiert?

Welcher Release wurde produktiv geschaltet?

Ein Audit-Log muss vorsichtig gestaltet werden. Es darf genug Informationen enthalten, um Vorgänge nachzuvollziehen, aber keine unnötigen sensiblen Daten speichern.

Guter Audit-Eintrag:

json
{
  class="str">"eventType": class="str">"ORDER_APPROVED",
  class="str">"actor": class="str">"maria.schmidt",
  class="str">"actorType": class="str">"USER",
  class="str">"resourceType": class="str">"ORDER",
  class="str">"resourceId": class="str">"ORD-2026-00091",
  class="str">"timestamp": class="str">"2026-07-05T18:45:12Z",
  class="str">"traceId": class="str">"5f2a9a3c8e4b",
  class="str">"outcome": class="str">"SUCCESS"
}

Schlechter Audit-Eintrag:

json
{
  class="str">"message": class="str">"Maria approved order with customer email, address, full payment data and internal notes"
}

Audit Logging ist kein Ort für Datenablage. Es ist ein Nachweisprotokoll.

---

21. Audit Service Beispiel

java
package com.seb4u.demo.spring.order.infrastructure.audit;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.time.Clock;
import java.time.Instant;
import java.util.UUID;

@Component
public class JdbcAuditLog implements AuditLog {

    private final JdbcTemplate jdbcTemplate;
    private final CurrentActorProvider currentActorProvider;
    private final TraceIdProvider traceIdProvider;
    private final Clock clock;

    public JdbcAuditLog(
            JdbcTemplate jdbcTemplate,
            CurrentActorProvider currentActorProvider,
            TraceIdProvider traceIdProvider,
            Clock clock
    ) {
        this.jdbcTemplate = jdbcTemplate;
        this.currentActorProvider = currentActorProvider;
        this.traceIdProvider = traceIdProvider;
        this.clock = clock;
    }

    @Override
    public void record(String eventType, String resourceId, String summary) {
        jdbcTemplate.update(class="str">"""
                INSERT INTO audit_events
                (id, event_type, actor, resource_id, summary, trace_id, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                class="str">""",
                UUID.randomUUID(),
                eventType,
                currentActorProvider.currentActor(),
                resourceId,
                summary,
                traceIdProvider.currentTraceId(),
                Instant.now(clock)
        );
    }
}

Der Code zeigt bewusst mehrere Abhängigkeiten: aktueller Benutzer, Trace-ID und Zeit. Dadurch können Audit-Einträge später mit Logs und Traces verbunden werden.

---

22. Governance: Regeln für kontrollierte Veränderung

Governance bedeutet nicht Bürokratie um der Bürokratie willen. Governance bedeutet, dass wichtige Änderungen nachvollziehbar, überprüfbar und verantwortet sind.

Im Lab betrifft Governance vor allem:

Vorlage / Textauszug

Architekturentscheidungen

Security-Regeln

Deployment-Freigaben

Produktionszugriffe

Secret Rotation

Netzwerkfreigaben

Rollenänderungen

Backup- und Restore-Tests

Eine gute Governance-Regel ist konkret. Eine schlechte Regel ist allgemein.

Schlecht:

Vorlage / Textauszug

Security muss beachtet werden.

Besser:

Vorlage / Textauszug

Jede produktionsnahe Anwendung muss eine NetworkPolicy, Resource Limits, Readiness Probe, dokumentierte Rollen, ein Runbook und einen ServiceMonitor besitzen.

---

23. Pull Request Regeln

Jede Änderung an produktionsnahen Artefakten läuft über Pull Request.

Ein PR muss beantworten:

Vorlage / Textauszug

Was wird geändert?

Warum wird es geändert?

Welche Umgebung ist betroffen?

Welche Risiken gibt es?

Wie wird getestet?

Wie wird zurückgerollt?

Gibt es Security-Auswirkungen?

Gibt es Betriebs-Auswirkungen?

Beispiel-Template:

Vorlage / Textauszug

Pull Request

Änderung

Beschreibe die Änderung kurz.

Grund

Warum ist die Änderung notwendig?

Betroffene Systeme
  • [ ] Anwendung
  • [ ] Infrastruktur
  • [ ] Security
  • [ ] Datenbank
  • [ ] Monitoring
Tests
  • [ ] Unit Tests
  • [ ] Integration Tests
  • [ ] Deployment in Testumgebung
  • [ ] Smoke Test
Rollback

Beschreibe, wie die Änderung zurückgenommen wird.

Security Review
  • [ ] keine neuen Secrets in Git
  • [ ] Rollen geprüft
  • [ ] Netzwerkzugriffe geprüft
  • [ ] Logs enthalten keine sensiblen Daten

Dieses Template verhindert nicht alle Fehler, aber es zwingt zu besseren Fragen.

---

24. Policy as Code

Policy as Code bedeutet, dass Regeln maschinenlesbar beschrieben und automatisch geprüft werden.

Beispiele für Regeln:

Vorlage / Textauszug

Kein Container darf privileged laufen.

Jeder produktive Pod braucht Resource Limits.

Images dürfen nicht latest verwenden.

Secrets dürfen nicht als Klartext im Repository liegen.

Jede Route muss TLS verwenden.

Jede Anwendung braucht Labels für Owner und Environment.

Solche Regeln können mit Tools wie Kyverno, OPA Gatekeeper, Conftest oder CI-Skripten geprüft werden.

Beispiel einer Kyverno-Regel:

yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: validate-resources
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: class="str">"CPU and memory requests and limits are required."
        pattern:
          spec:
            containers:
              - resources:
                  requests:
                    memory: class="str">"?*"
                    cpu: class="str">"?*"
                  limits:
                    memory: class="str">"?*"
                    cpu: class="str">"?*"

Eine solche Regel macht Plattformstandards verbindlicher.

---

25. Container Image Security

Ein Container Image ist Teil der Lieferkette. Es enthält Betriebssystembestandteile, Laufzeit, Bibliotheken und Anwendungscode.

Risiken:

Vorlage / Textauszug

veraltete Base Images

bekannte CVEs

unnötige Tools im Image

Root User im Container

Secrets im Image Layer

unsignierte Images

unklare Herkunft

Gute Praxis:

Vorlage / Textauszug

kleine Base Images

regelmäßige Updates

Security Scans

SBOM erzeugen

Images signieren

keine Secrets im Image

nicht als root laufen

Beispiel Dockerfile:

dockerfile
FROM eclipse-temurin:21-jre-alpine

RUN addgroup -S app && adduser -S app -G app

WORKDIR /app
COPY target/order-service.jar /app/order-service.jar

USER app
EXPOSE 8080

ENTRYPOINT [class="str">"java", class="str">"-XX:MaxRAMPercentage=75", class="str">"-jar", class="str">"/app/order-service.jar"]

Dieses Dockerfile ist nicht perfekt für alle Fälle, aber besser als ein großes Image mit Root-User und unnötigen Tools.

---

26. Supply Chain Security

Supply Chain Security betrachtet den Weg von Code bis Produktion.

Fragen:

Vorlage / Textauszug

Wer hat den Code geändert?

Welche Dependencies wurden verwendet?

Welche Pipeline hat gebaut?

Welches Image wurde erzeugt?

Wurde es gescannt?

Wurde es signiert?

Welche Umgebung hat es deployt?

Ein moderner Release sollte nachvollziehbar sein:

Skizze / Textauszug
Git Commit
↓
Pull Request Review
↓
CI Build
↓
Tests
↓
Security Scan
↓
Image Build
↓
Image Signatur
↓
GitOps Manifest Update
↓
Argo CD Sync
↓
Deployment

Wenn eine Sicherheitslücke gefunden wird, muss man wissen, welche Images und Umgebungen betroffen sind.

---

27. Beispiel: GitHub Actions Security Pipeline

yaml
name: build-and-scan-order-service

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write
      security-events: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">'21'

      - name: Build
        run: ./mvnw -B verify

      - name: Build container image
        run: docker build -t registry.example.com/alpine/order-service:${{ github.sha }} .

      - name: Scan image
        run: trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/alpine/order-service:${{ github.sha }}

Diese Pipeline bricht bei kritischen Funden ab. In echten Unternehmen müssen Ausnahmen geregelt werden. Eine Ausnahme darf aber nicht einfach mündlich entstehen. Sie braucht Begründung, Ablaufdatum und Verantwortlichen.

---

28. Datenschutz und sensible Daten

Security schützt Systeme. Datenschutz schützt personenbezogene Daten.

Im Lab können folgende Daten sensibel sein:

Vorlage / Textauszug

Kundennamen

Adressen

E-Mail-Adressen

Bestellhistorie

Zahlungsreferenzen

Support-Kommentare

Audit-Einträge

IP-Adressen

Regeln:

Vorlage / Textauszug

Nur notwendige Daten speichern.

Keine sensiblen Daten in Logs schreiben.

Exports begrenzen und auditieren.

Zugriffe rollenbasiert steuern.

Testdaten anonymisieren oder synthetisch erzeugen.

Lösch- und Aufbewahrungsfristen definieren.

Ein typischer Fehler ist, komplette Request- oder Response-Bodies zu loggen. Das hilft beim Debugging, kann aber personenbezogene oder vertrauliche Daten offenlegen.

---

29. Sichere Logging-Regeln

Gute Logs enthalten:

Vorlage / Textauszug

Trace-ID

Service-Name

Event-Typ

technischer Status

fachliche Referenz-ID

Fehlerklasse

Schlechte Logs enthalten:

Vorlage / Textauszug

Passwörter

Tokens

Kreditkartendaten

vollständige Adressen

komplette personenbezogene Datensätze

private Schlüssel

Beispiel guter Log:

json
{
  class="str">"level": class="str">"WARN",
  class="str">"service": class="str">"order-service",
  class="str">"event": class="str">"ORDER_VALIDATION_FAILED",
  class="str">"orderReference": class="str">"ORD-2026-00091",
  class="str">"traceId": class="str">"8af7e2c9",
  class="str">"reasonCode": class="str">"ITEM_NOT_AVAILABLE"
}

Dieser Log hilft im Betrieb, ohne unnötige Daten offenzulegen.

---

30. Admin Portal als Security-Beispiel

Das Admin Portal ist besonders kritisch, weil interne Mitarbeiter dort Daten sehen oder Aktionen auslösen können.

Risiken:

Vorlage / Textauszug

zu breite Rollen

fehlendes Audit Logging

direkter Zugriff auf interne APIs

fehlende Session-Kontrolle

CSRF-Fehler

unsichere Redirects

zu viele Daten in Listenansichten

Gute Praxis:

Vorlage / Textauszug

starke OIDC-Konfiguration

rollenbasierte UI und API-Prüfung

Audit für kritische Aktionen

kurze Session-Laufzeiten für sensible Funktionen

keine Secrets im Frontend

Backend prüft jede Berechtigung erneut

Wichtig: Die UI darf Buttons ausblenden, aber die API muss trotzdem Berechtigungen prüfen. Frontend-Security allein ist keine Security.

---

31. Incident Response im Security-Kontext

Ein Security Incident ist ein Ereignis, das Vertraulichkeit, Integrität oder Verfügbarkeit gefährdet.

Beispiele:

Vorlage / Textauszug

Token-Leak

kompromittierter ServiceAccount

verdächtige Admin-Aktion

unerwartete Netzwerkverbindung

kritische CVE im produktiven Image

öffentlich erreichbarer interner Endpunkt

Ein Incident-Prozess braucht Phasen:

Vorlage / Textauszug

Erkennen

Bewerten

Eindämmen

Untersuchen

Beheben

Wiederherstellen

Nachbereiten

Security ist erst ernsthaft, wenn auch der Umgang mit Fehlern beschrieben ist.

---

32. Runbook: Verdacht auf Secret Leak

Vorlage / Textauszug

Runbook: Verdacht auf Secret Leak

Symptom

Ein Secret wurde versehentlich in Logs, Git, Ticket, Chat oder ein Artefakt geschrieben.

Sofortmaßnahmen
  1. Secret als kompromittiert betrachten.
  2. Keine Diskussion, ob es class="str">"wirklich schlimm" ist.
  3. Zugriff auf das betroffene Artefakt einschränken.
  4. Security-Team informieren.
  5. Neues Secret erzeugen.
  6. Betroffene Anwendungen kontrolliert umstellen.
  7. Altes Secret deaktivieren.
  8. Logs und Zugriffe prüfen.
Prüfung
  • Welche Systeme haben das Secret genutzt?
  • Seit wann war es sichtbar?
  • Wer konnte es sehen?
  • Wurde es verwendet?
  • Gibt es verdächtige Zugriffe?
Nachbereitung
  • Ursache dokumentieren
  • Pipeline-Regel ergänzen
  • Secret-Scanner aktivieren
  • Team informieren
  • ADR oder Runbook aktualisieren

Dieses Runbook ist bewusst klar. Bei Secret Leaks darf man nicht zuerst lange diskutieren. Man rotiert.

---

33. Übung: Rollen- und Rechtekonzept erstellen

Erstelle ein Rollen- und Rechtekonzept für das Admin Portal.

Es soll enthalten:

Vorlage / Textauszug
  1. mindestens fünf Rollen
  2. mindestens zehn Aktionen
  3. klare Zuordnung, wer was darf
  4. Audit-Pflicht für kritische Aktionen
  5. technische Umsetzungsidee mit Keycloak und Spring Security

Denke dabei an:

Vorlage / Textauszug

Bestellungen lesen

Bestellungen freigeben

Bestellungen stornieren

Rechnungen lesen

Zahlungsstatus ändern

Benutzerrollen sehen

Benutzerrollen ändern

Reports exportieren

Systemstatus sehen

Konfiguration ändern

---

34. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

SUPPORT_AGENT:

  • Bestellungen lesen
  • Kundenstatus sehen
  • keine Stornierung
  • keine Zahlungsänderung

ORDER_MANAGER:

  • Bestellungen lesen
  • Bestellungen freigeben
  • Bestellungen stornieren
  • Audit für Freigabe und Storno

BILLING_SPECIALIST:

  • Rechnungen lesen
  • Zahlungsstatus ändern
  • Rechnungslauf starten
  • Audit für Zahlungsänderung

REPORTING_VIEWER:

  • Reports lesen
  • Exporte anfordern
  • keine fachlichen Änderungen

SECURITY_AUDITOR:

  • Audit Logs lesen
  • Rollenzuordnung sehen
  • keine Änderungen durchführen

PLATFORM_OPERATOR:

  • Systemstatus sehen
  • Health und Metriken sehen
  • keine fachlichen Daten ändern

Technisch werden diese Rollen in Keycloak gepflegt. Das Admin Portal zeigt UI-Funktionen rollenabhängig an. Die Backend-APIs prüfen Rollen mit Spring Security. Kritische Aktionen schreiben Audit Events.

---

35. Merksätze

Vorlage / Textauszug

Authentifizierung sagt, wer du bist.

Autorisierung sagt, was du darfst.

Fachrollen und Kubernetes RBAC sind unterschiedliche Ebenen.

Secrets gehören nicht in Klartext nach Git.

Ein ServiceAccount sollte fast nie Cluster-Admin sein.

Audit Logging ist ein Nachweis, kein Debug-Dump.

Zero Trust bedeutet: interne Systeme werden nicht automatisch vertraut.

Governance ist gut, wenn sie konkrete Regeln überprüfbar macht.

Security muss im Code, im Manifest, in der Pipeline und im Betrieb sichtbar sein.

Teil D: Moderne Anwendungen, Datenflüsse, Messaging und Integration

Teil D: Moderne Anwendungen, Datenflüsse, Messaging und Integration

PlanZielbildBuildUmsetzungRunBetriebTeil D: Moderne Anwendungen, Datenflüsse, Messaging und Integration: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 7, Teil 8.

Moderne Anwendungen

Anwendungslandkarte kompakt
Kompakte Themenübersicht
Customer PortalReactAdmin PortalReact/VMOrder ServiceSpring BootBillingQuarkusReportingBatchFile Proc.I/OPlattformdiensteKeycloakKafkaPGRESTinternEvents / DBEventsReportsFiles

1. Warum moderne Anwendungen anders geplant werden

Eine moderne Enterprise-Anwendung ist nicht nur eine Anwendung mit neuem Framework. Sie ist ein Baustein in einer Plattform. Sie muss fachliche Verantwortung klar schneiden, sicher deploybar sein, mit anderen Systemen über stabile Schnittstellen sprechen, beobachtbar sein und im Fehlerfall kontrolliert reagieren. Genau hier unterscheiden sich produktionsnahe Systeme von kleinen Demo-Projekten.

Im Enterprise Platform Lab betrachten wir moderne Anwendungen deshalb nicht isoliert. Ein Order Service ist nicht nur ein REST Controller mit Datenbankzugriff. Er hat Authentifizierung, fachliche Regeln, Transaktionen, Outbox Events, Metriken, Logs, Datenbankmigrationen, Kubernetes-Manifeste, CI/CD, Runbooks und Betriebsgrenzen. Ein Customer Portal ist nicht nur ein React-Frontend. Es braucht OIDC, Token Handling, Fehlerzustände, API-Verträge, Routing, Build-Prozess und sichere Konfiguration.

Die wichtigste Regel lautet:

Vorlage / Textauszug

Eine moderne Anwendung ist erst dann vollständig, wenn Code, Schnittstelle, Deployment, Security, Observability und Betrieb zusammen beschrieben sind.

In diesem Teil bauen wir das Anwendungsbild des Labs. Die Anwendungen werden nicht als künstliche Beispiele nebeneinander gestellt, sondern als zusammenhängendes System: Customer Portal, Admin Portal, Order Service, Billing Service, Reporting Service, File Processing Service und API Gateway.

---

2. Die Anwendungslandkarte

Die moderne Anwendungslandschaft des Labs besteht aus mehreren Services mit klarer Verantwortung.

Vorlage / Textauszug

Customer Portal

  • Kundenoberfläche
  • Login über Keycloak
  • Anzeige von Bestellungen und Rechnungen
  • Aufruf von Backend-APIs

Admin Portal

  • interne Oberfläche für Mitarbeiter
  • stärkere Rollenprüfung
  • manuelle Korrekturen
  • Auditpflichtige Aktionen

Order Service

  • Bestellungen anlegen und verwalten
  • fachliche Validierung
  • PostgreSQL für Bestelldaten
  • Outbox Events für nachgelagerte Prozesse

Billing Service

  • Rechnungsprozesse
  • Konsum von Order Events
  • eigene Datenbank für Abrechnung
  • idempotente Verarbeitung

Reporting Service

  • Auswertungen und Exporte
  • liest aus freigegebenen Datenquellen
  • läuft teilweise als CronJob, teilweise auf VM

File Processing Service

  • Verarbeitung von Partnerdateien
  • Validierung, Fehlerablage und Events
  • kann auf VM, Bare Metal oder OpenShift laufen

API Gateway

  • zentraler Eingangspunkt
  • Routing zu Backend-Services
  • TLS, Rate Limits und technische Schutzregeln

Diese Landkarte macht sichtbar, dass jede Anwendung einen eigenen Grund hat. Es gibt keine generischen Platzhalterdienste. Jeder Service erfüllt eine Aufgabe im Gesamtprozess.

---

3. Architekturprinzipien für Anwendungen

Für alle modernen Anwendungen gelten gemeinsame Architekturprinzipien.

Erstens: Jeder Service besitzt eine klare fachliche Verantwortung. Der Order Service erstellt keine Rechnungen. Der Billing Service verändert keine Bestellung. Das Customer Portal enthält keine Geschäftslogik, die eigentlich in einen Backend-Service gehört.

Zweitens: Jeder Service besitzt seine eigenen Betriebsartefakte. Dazu gehören README, Konfigurationsbeschreibung, Health Checks, Metriken, Logs und Runbook. Ein Service ohne Betriebsbeschreibung wird im Lab nicht als fertig betrachtet.

Drittens: Jeder Service kommuniziert über definierte Schnittstellen. REST APIs werden mit OpenAPI dokumentiert. Events werden mit klaren Eventnamen und Payload-Strukturen beschrieben. Datenbanktabellen anderer Services werden nicht direkt manipuliert.

Viertens: Jeder Service ist sicher konfigurierbar. Secrets liegen nicht im Code. Rollen werden nicht hart im Frontend entschieden. Sicherheitsentscheidungen müssen serverseitig überprüfbar sein.

Fünftens: Jeder Service ist ersetzbar und deploybar. Ein Deployment muss über Pipeline und GitOps reproduzierbar sein. Manuelle Änderungen in Produktion sind kein Zielzustand.

---

4. Customer Portal: Aufgabe und Grenzen

Das Customer Portal ist die Oberfläche für Kunden. Es soll einfach wirken, ist aber sicherheitstechnisch wichtig. Kunden sehen Bestellungen, Rechnungen, Profilinformationen und Statusmeldungen. Sie können neue Bestellungen auslösen oder bestehende Vorgänge einsehen.

Das Portal darf keine geheimen Zugangsdaten enthalten. Es darf keine Datenbank direkt erreichen. Es darf keine kritischen Entscheidungen allein treffen. Wenn das Frontend einen Button versteckt, ist das keine Sicherheit. Die Backend-API muss trotzdem prüfen, ob der Benutzer die Aktion ausführen darf.

Die typische Laufzeit ist OpenShift oder Cloud Hosting. Das Portal wird als statisches oder serverseitig ausgeliefertes Frontend gebaut. Der Build erzeugt ein Container Image oder statische Assets, die über einen Webserver ausgeliefert werden.

Ein typischer Ablauf:

Skizze / Textauszug
Kunde öffnet customer.alpine.example.com
↓
Portal leitet zu Keycloak weiter
↓
Kunde meldet sich an
↓
Portal erhält OIDC Token
↓
Portal ruft API Gateway auf
↓
Order Service liefert Bestellungen des Kunden

Wichtig ist: Das Portal muss Token sauber behandeln. Es darf Access Tokens nicht unnötig lange speichern. Es muss Fehler wie abgelaufene Sitzung, fehlende Berechtigung und API-Ausfall sichtbar behandeln.

---

5. Beispiel: Frontend API Client

Der API Client trennt technische Kommunikation von UI-Komponenten. Dadurch wird die Oberfläche einfacher testbar.

typescript
export type OrderSummary = {
  id: string;
  status: class="str">'CREATED' | class="str">'CONFIRMED' | class="str">'CANCELLED' | class="str">'BILLED';
  totalAmount: number;
  createdAt: string;
};

export class ApiError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly correlationId?: string
  ) {
    super(message);
  }
}

export async function loadOrders(accessToken: string): Promise<OrderSummary[]> {
  const response = await fetch(class="str">'/api/orders', {
    method: class="str">'GET',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      Accept: class="str">'application/json'
    }
  });

  if (!response.ok) {
    const correlationId = response.headers.get(class="str">'x-correlation-id') ?? undefined;
    throw new ApiError(class="str">'Bestellungen konnten nicht geladen werden.', response.status, correlationId);
  }

  return response.json();
}

Dieser Code zeigt zwei wichtige Punkte. Erstens wird das Token nur für den API-Aufruf verwendet. Zweitens wird eine Correlation-ID aus der Antwort übernommen. Dadurch kann ein Fehler im Frontend später mit Backend-Logs verbunden werden.

---

6. Admin Portal: interne Oberfläche mit höherem Risiko

Das Admin Portal ist deutlich sensibler als das Customer Portal. Mitarbeiter können Bestellungen prüfen, Rechnungsstatus einsehen, Kundendaten bearbeiten oder Fehlerfälle korrigieren. Solche Aktionen müssen protokolliert werden.

Ein Admin Portal braucht daher:

Vorlage / Textauszug
  • starke Authentifizierung
  • rollenbasierte Autorisierung
  • serverseitige Rechteprüfung
  • Audit Logging für kritische Aktionen
  • klare Trennung zwischen Lesen und Ändern
  • nachvollziehbare Fehlerbehandlung

Ein typischer Fehler ist, interne Oberflächen als weniger kritisch zu betrachten, weil sie „nur intern“ erreichbar sind. Das ist gefährlich. Interne Systeme haben oft mächtige Funktionen. Deshalb brauchen sie mindestens genauso klare Sicherheitsregeln wie externe Anwendungen.

Beispiel für Rollen:

Vorlage / Textauszug

ROLE_SUPPORT_READ

  • darf Bestellungen suchen und lesen

ROLE_ORDER_CORRECTION

  • darf bestimmte Bestelldaten korrigieren

ROLE_BILLING_REVIEW

  • darf Rechnungsstatus prüfen

ROLE_ADMIN_AUDIT

  • darf Audit-Protokolle einsehen

Nicht jeder interne Benutzer ist automatisch Administrator. Das Rollenmodell muss fein genug sein, damit Aufgaben sauber getrennt werden können.

---

7. Order Service: fachlicher Kern

Der Order Service ist einer der zentralen Backend-Services. Er nimmt Bestellungen entgegen, validiert sie, speichert sie und veröffentlicht Events. Er ist bewusst nicht für Abrechnung, E-Mail-Versand oder Reporting zuständig.

Die wichtigste fachliche Verantwortung lautet:

Vorlage / Textauszug

Der Order Service ist die Quelle der Wahrheit für Bestellungen.

Das bedeutet: Andere Systeme dürfen Bestellstatus nicht heimlich in ihrer eigenen Datenhaltung ändern. Sie können Ereignisse verarbeiten, Informationen ergänzen oder eigene Sichten aufbauen, aber die fachliche Bestellentscheidung liegt beim Order Service.

Der Service nutzt eine klassische Schichtung:

Vorlage / Textauszug

api

  • REST Controller
  • Request/Response DTOs

application

  • Use Cases
  • Transaktionsgrenzen
  • Koordination von Domäne und Ports

domain

  • fachliche Objekte
  • Regeln
  • Zustände

infrastructure

  • Datenbank
  • Messaging
  • Security Adapter
  • externe Clients

Diese Struktur verhindert, dass Infrastrukturdetails die Fachlogik dominieren.

---

8. Beispiel: REST Controller für Bestellungen

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    @PreAuthorize(class="str">"hasAuthority('SCOPE_order:write')")
    public ResponseEntity<CreateOrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request
    ) {
        var result = createOrderUseCase.createOrder(
                new CreateOrderCommand(request.customerId(), request.items())
        );

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(new CreateOrderResponse(result.orderId(), result.status()));
    }
}

Der Controller bleibt dünn. Er kennt HTTP, Validierung und Security Annotation. Er enthält aber keine Geschäftslogik. Die eigentliche Bestellung wird im Use Case verarbeitet.

---

9. Domain-Modell für Order

Das Domain-Modell schützt Regeln vor zufälliger Verteilung im Code.

java
package com.seb4u.demo.spring.order.domain;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class Order {

    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private OrderStatus status;
    private final Instant createdAt;

    private Order(OrderId id, CustomerId customerId, List<OrderItem> items, OrderStatus status, Instant createdAt) {
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException(class="str">"Eine Bestellung benötigt mindestens eine Position.");
        }
        this.id = id;
        this.customerId = customerId;
        this.items = new ArrayList<>(items);
        this.status = status;
        this.createdAt = createdAt;
    }

    public static Order create(CustomerId customerId, List<OrderItem> items, Instant createdAt) {
        return new Order(new OrderId(UUID.randomUUID()), customerId, items, OrderStatus.CREATED, createdAt);
    }

    public void confirm() {
        if (status != OrderStatus.CREATED) {
            throw new IllegalStateException(class="str">"Nur neu erstellte Bestellungen können bestätigt werden.");
        }
        status = OrderStatus.CONFIRMED;
    }

    public void cancel() {
        if (status == OrderStatus.BILLED) {
            throw new IllegalStateException(class="str">"Eine bereits abgerechnete Bestellung kann nicht storniert werden.");
        }
        status = OrderStatus.CANCELLED;
    }

    public BigDecimal totalAmount() {
        return items.stream()
                .map(OrderItem::totalPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public OrderId id() {
        return id;
    }

    public CustomerId customerId() {
        return customerId;
    }

    public OrderStatus status() {
        return status;
    }

    public Instant createdAt() {
        return createdAt;
    }
}

Dieses Modell verhindert, dass beliebige Codestellen den Status einer Bestellung ohne Regelprüfung verändern. Die Methoden confirm und cancel enthalten fachliche Regeln.

---

10. Billing Service: Eventgetriebene Verarbeitung

Billing als Eventfluss
Kompakte Lernhilfe
OrderOutboxPublisherKafkaorder.createdBillingConsumerInvoice DB

Der Billing Service reagiert auf Events aus dem Order Service. Er erzeugt Rechnungsentwürfe, prüft Abrechnungsregeln und speichert eigene Rechnungsdaten. Er ist bewusst nicht Teil derselben Datenbank wie der Order Service.

Das Ziel ist lose Kopplung:

Skizze / Textauszug
Order Service veröffentlicht OrderCreatedEvent
↓
Broker transportiert Event
↓
Billing Service konsumiert Event
↓
Billing Service erstellt Billing Draft

Lose Kopplung bedeutet aber nicht, dass Fehler ignoriert werden dürfen. Eventverarbeitung braucht Idempotenz. Wenn ein Event zweimal geliefert wird, darf nicht zweimal eine Rechnung erzeugt werden.

---

11. Beispiel: Idempotenter Event Consumer

java
package com.seb4u.demo.spring.billing.messaging;

import com.seb4u.demo.spring.billing.application.CreateBillingDraftCommand;
import com.seb4u.demo.spring.billing.application.CreateBillingDraftUseCase;
import com.seb4u.demo.spring.billing.persistence.ProcessedEventRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class OrderCreatedConsumer {

    private final ProcessedEventRepository processedEventRepository;
    private final CreateBillingDraftUseCase createBillingDraftUseCase;

    public OrderCreatedConsumer(
            ProcessedEventRepository processedEventRepository,
            CreateBillingDraftUseCase createBillingDraftUseCase
    ) {
        this.processedEventRepository = processedEventRepository;
        this.createBillingDraftUseCase = createBillingDraftUseCase;
    }

    @Transactional
    public void handle(OrderCreatedMessage message) {
        if (processedEventRepository.existsByEventId(message.eventId())) {
            return;
        }

        createBillingDraftUseCase.createDraft(
                new CreateBillingDraftCommand(
                        message.orderId(),
                        message.customerId(),
                        message.totalAmount(),
                        message.createdAt()
                )
        );

        processedEventRepository.markProcessed(message.eventId(), message.eventType());
    }
}

Idempotenz ist kein Luxus. Sie ist in eventgetriebenen Systemen Pflicht, weil Broker Nachrichten erneut liefern können.

---

12. Reporting Service: Lesen ohne Fachlogik zu beschädigen

Reporting ist in Enterprise-Systemen oft schwierig. Fachbereiche wollen Auswertungen, Exporte, Excel-Dateien und historische Sichten. Gleichzeitig darf Reporting nicht die produktiven Kernprozesse stören.

Der Reporting Service soll daher nicht direkt in die Transaktionslogik eingreifen. Er liest aus freigegebenen Sichten, Replikaten oder speziell vorbereiteten Reporting-Tabellen. In kleinen Umgebungen kann er direkt aus Datenbanken lesen, aber in produktionsnahen Umgebungen sollte man Belastung, Rechte und Datenqualität beachten.

Mögliche Betriebsformen:

Vorlage / Textauszug

Kubernetes CronJob

  • gut für regelmäßige Exporte
  • standardisiertes Deployment
  • Logs und Status im Cluster sichtbar

VM-basierter Job

  • gut bei alten Reporting Tools
  • gut bei speziellen Treibern
  • sinnvoll als Übergangslösung

Separater Reporting Service

  • gut für APIs und interaktive Dashboards
  • braucht klare Zugriffskontrolle

Das Lab zeigt bewusst mehrere Varianten, weil Reporting in Unternehmen selten vollständig modernisiert ist.

---

13. Beispiel: Kubernetes CronJob für Reporting

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-order-report
  namespace: reporting-prod
spec:
  schedule: class="str">"15 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        metadata:
          labels:
            app: daily-order-report
        spec:
          restartPolicy: Never
          containers:
            - name: report-generator
              image: registry.example.com/alpine/reporting-service:1.0.0
              args:
                - class="str">"generate-daily-order-report"
              envFrom:
                - secretRef:
                    name: reporting-db-secret
              resources:
                requests:
                  cpu: class="str">"500m"
                  memory: class="str">"1Gi"
                limits:
                  cpu: class="str">"2000m"
                  memory: class="str">"2Gi"

Der CronJob nutzt concurrencyPolicy: Forbid, damit nicht mehrere Berichte gleichzeitig laufen und dieselbe Datenbank belasten.

---

14. File Processing Service: große Dateien kontrolliert verarbeiten

Der File Processing Service verarbeitet Dateien von Partnern. Solche Dateien können CSV, XML, JSON oder proprietäre Formate enthalten. Fehlerhafte Dateien dürfen nicht stillschweigend verschwinden. Jede Datei braucht einen nachvollziehbaren Status.

Ein sauberer Ablauf:

Skizze / Textauszug
Datei kommt an
↓
Datei bekommt technische ID
↓
Checksumme wird berechnet
↓
Schema wird geprüft
↓
Fachliche Regeln werden geprüft
↓
valide Datensätze werden weitergegeben
↓
fehlerhafte Datensätze werden protokolliert
↓
Originaldatei wird archiviert

Dieser Service kann auf OpenShift laufen, wenn die Dateigrößen und Storage-Anforderungen passen. Er kann aber auch auf VM oder Bare Metal laufen, wenn sehr große Dateien, lokale Storage-Anbindungen oder Spezialsoftware benötigt werden.

---

15. Beispiel: File Processing Pipeline in Java

java
package com.seb4u.demo.spring.fileprocessing.application;

import com.seb4u.demo.spring.fileprocessing.domain.FileValidationResult;
import com.seb4u.demo.spring.fileprocessing.domain.PartnerFile;
import com.seb4u.demo.spring.fileprocessing.infrastructure.storage.ObjectStorageClient;
import org.springframework.stereotype.Service;

@Service
public class PartnerFileProcessingService {

    private final ObjectStorageClient objectStorageClient;
    private final PartnerFileParser parser;
    private final PartnerRecordValidator validator;
    private final ProcessingEventPublisher eventPublisher;

    public PartnerFileProcessingService(
            ObjectStorageClient objectStorageClient,
            PartnerFileParser parser,
            PartnerRecordValidator validator,
            ProcessingEventPublisher eventPublisher
    ) {
        this.objectStorageClient = objectStorageClient;
        this.parser = parser;
        this.validator = validator;
        this.eventPublisher = eventPublisher;
    }

    public void process(String bucket, String objectKey) {
        PartnerFile file = objectStorageClient.load(bucket, objectKey);
        var records = parser.parse(file);
        FileValidationResult result = validator.validate(records);

        if (result.hasErrors()) {
            objectStorageClient.storeRejected(file, result.errors());
            eventPublisher.publishRejected(file.id(), result.errors().size());
            return;
        }

        objectStorageClient.storeProcessed(file);
        eventPublisher.publishAccepted(file.id(), records.size());
    }
}

Der Service löscht Dateien nicht einfach. Er verschiebt sie in definierte Zustände: verarbeitet oder abgelehnt. Das erleichtert Betrieb und Nachvollziehbarkeit.

---

16. API Gateway: Eingangspunkt und Schutzschicht

Das API Gateway ist der zentrale technische Eingang für APIs. Es ist nicht der Ort für komplette Fachlogik. Es übernimmt technische Querschnittsaufgaben.

Typische Aufgaben:

Vorlage / Textauszug
  • TLS-Terminierung oder Weiterleitung
  • Routing zu Services
  • Rate Limits
  • Header-Normalisierung
  • Correlation-ID setzen
  • technische Authentifizierungsprüfung
  • einfache Schutzregeln

Fachliche Autorisierung bleibt trotzdem in den Services. Das Gateway kann prüfen, ob ein Token existiert. Der Order Service muss aber weiterhin prüfen, ob die konkrete Aktion erlaubt ist.

Ein häufiger Fehler ist, das Gateway zu mächtig zu machen. Dann entsteht eine zentrale Monsterkomponente. Besser ist: Gateway für technische Querschnittslogik, Fachentscheidungen in den Services.

---

17. API-Verträge und OpenAPI

REST APIs brauchen Verträge. Ohne Vertrag weiß das Frontend nicht zuverlässig, welche Felder existieren, welche Fehler auftreten können und welche Statuscodes verwendet werden.

Ein kleiner OpenAPI-Ausschnitt für den Order Service:

yaml
openapi: 3.0.3
info:
  title: Order Service API
  version: 1.0.0
paths:
  /api/orders:
    post:
      summary: Create a new order
      operationId: createOrder
      security:
        - bearerAuth: []
      responses:
        class="str">'201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: class="str">'#/components/schemas/CreateOrderResponse'
        class="str">'400':
          description: Invalid request
        class="str">'401':
          description: Missing or invalid token
        class="str">'403':
          description: Missing permission
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    CreateOrderResponse:
      type: object
      required:
        - orderId
        - status
      properties:
        orderId:
          type: string
        status:
          type: string

OpenAPI ist nicht nur Dokumentation. Es kann auch für Tests, Client-Generierung und Review genutzt werden.

---

18. Fehlerbehandlung als Architekturthema

Fehlerbehandlung darf nicht zufällig entstehen. Moderne Anwendungen brauchen ein einheitliches Fehlermodell.

Ein gutes Fehlermodell enthält:

Vorlage / Textauszug
  • HTTP Statuscode
  • fachliche Fehlermeldung
  • technischer Fehlercode
  • Correlation-ID
  • Zeitstempel
  • optional Details für Validierungsfehler

Beispielantwort:

json
{
  class="str">"code": class="str">"ORDER_VALIDATION_FAILED",
  class="str">"message": class="str">"Die Bestellung enthält ungültige Positionen.",
  class="str">"correlationId": class="str">"7ff8c1d2-0b23-4fa7-98a0-79916b827111",
  class="str">"timestamp": class="str">"2026-07-05T20:30:00Z",
  class="str">"details": [
    {
      class="str">"field": class="str">"items[0].quantity",
      class="str">"message": class="str">"Die Menge muss größer als 0 sein."
    }
  ]
}

Damit kann das Frontend dem Benutzer eine verständliche Meldung zeigen und der Betrieb kann über die Correlation-ID Logs suchen.

---

19. Observability in Anwendungen

Eine moderne Anwendung muss beobachtbar sein. Beobachtbarkeit besteht aus Logs, Metriken und Traces.

Logs beantworten:

Vorlage / Textauszug

Was ist passiert?

Metriken beantworten:

Vorlage / Textauszug

Wie oft passiert es und wie stark ist das System belastet?

Traces beantworten:

Vorlage / Textauszug

Welcher Weg wurde durch mehrere Services genommen?

Der Order Service sollte zum Beispiel messen:

Vorlage / Textauszug
  • Anzahl erstellter Bestellungen
  • Anzahl fehlgeschlagener Validierungen
  • Dauer der Create-Order-Operation
  • Anzahl offener Outbox Events
  • Anzahl fehlgeschlagener Veröffentlichungen

Ohne solche Metriken merkt man Probleme oft erst, wenn Benutzer sie melden.

---

20. Beispiel: fachliche Metrik in Spring

java
package com.seb4u.demo.spring.order.observability;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

@Component
public class OrderMetrics {

    private final Counter ordersCreated;
    private final Counter orderValidationFailures;

    public OrderMetrics(MeterRegistry registry) {
        this.ordersCreated = Counter.builder(class="str">"orders_created_total")
                .description(class="str">"Anzahl erfolgreich erstellter Bestellungen")
                .register(registry);

        this.orderValidationFailures = Counter.builder(class="str">"order_validation_failures_total")
                .description(class="str">"Anzahl fehlgeschlagener Bestellvalidierungen")
                .register(registry);
    }

    public void orderCreated() {
        ordersCreated.increment();
    }

    public void validationFailed() {
        orderValidationFailures.increment();
    }
}

Diese Metriken sind fachlich verständlich. Sie helfen nicht nur Entwicklern, sondern auch Betrieb und Fachbereich.

---

21. Konfiguration moderner Anwendungen

Konfiguration muss zwischen Umgebungen variieren können. Eine Anwendung darf nicht für jede Umgebung neu gebaut werden, nur weil sich Datenbankhost oder Keycloak-URL ändern.

Richtig ist:

Vorlage / Textauszug

Ein Image wird gebaut.

Konfiguration wird zur Laufzeit injiziert.

Beispiel application.yaml:

yaml
server:
  port: 8080

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${OIDC_ISSUER_URI}

management:
  endpoints:
    web:
      exposure:
        include: health,prometheus,info
  endpoint:
    health:
      probes:
        enabled: true

Secrets kommen aus Kubernetes Secrets, External Secrets oder Vault. Sie gehören nicht in Git.

---

22. Container Image für Spring Boot

dockerfile
FROM eclipse-temurin:21-jre

RUN addgroup --system app && adduser --system --ingroup app app

WORKDIR /app
COPY target/order-service.jar /app/order-service.jar

USER app

EXPOSE 8080

ENTRYPOINT [class="str">"java", class="str">"-XX:MaxRAMPercentage=75", class="str">"-jar", class="str">"/app/order-service.jar"]

Dieses Dockerfile nutzt einen Nicht-Root-Benutzer. Das ist wichtig für Security. Zusätzlich sollte das Image später gescannt und versioniert werden.

---

23. Deployment-Paket pro Anwendung

Jede Anwendung braucht neben Code auch Deployment-Artefakte.

Eine sinnvolle Struktur:

Skizze / Textauszug
order-service/
├── source-material/
├── pom.xml
├── Dockerfile
├── README.md
├── docs/
│   ├── adr/
│   └── runbooks/
└── deploy/
    ├── base/
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   ├── route.yaml
    │   ├── configmap.yaml
    │   ├── networkpolicy.yaml
    │   └── kustomization.yaml
    └── overlays/
        ├── dev/
        ├── test/
        └── prod/

Damit ist die Anwendung nicht nur lokal lauffähig, sondern auch plattformfähig.

---

24. Tests als Teil der Architektur

Tests sind nicht nur Qualitätskontrolle. Sie dokumentieren Annahmen. Besonders wichtig sind Integrationstests mit Datenbank, Broker und Security-Kontext.

Beispiel für einen Test mit Testcontainers:

java
package com.seb4u.demo.spring.order;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@SpringBootTest
class OrderServiceIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(class="str">"postgres:16");

    @Test
    void contextStartsWithPostgreSQL() {
        assertThat(postgres.isRunning()).isTrue();
    }
}

Dieser Test beweist nicht alle Fachregeln, aber er zeigt, dass der Service mit einer echten PostgreSQL-Instanz starten kann.

---

25. Typische Fehler bei modernen Anwendungen

Ein häufiger Fehler ist zu viel Logik im Controller. Dann wird der Controller schwer testbar und jede Änderung betrifft HTTP, Fachlogik und Datenbank gleichzeitig.

Ein zweiter Fehler ist direkte Kopplung zwischen Services und fremden Datenbanken. Wenn Billing direkt Order-Tabellen verändert, ist die Servicegrenze kaputt.

Ein dritter Fehler ist fehlende Idempotenz bei Events. Dadurch entstehen doppelte Rechnungen, doppelte Benachrichtigungen oder falsche Statuswechsel.

Ein vierter Fehler ist unklare Fehlerbehandlung. Wenn jeder Service andere Fehlerformate liefert, wird das Frontend kompliziert und Betrieb verliert Nachvollziehbarkeit.

Ein fünfter Fehler ist fehlende Observability. Ein Service ohne fachliche Metriken kann im Betrieb technisch gesund wirken, obwohl fachlich nichts mehr funktioniert.

Ein sechster Fehler ist Build-Time-Konfiguration. Wenn für jede Umgebung ein anderes Image gebaut wird, entstehen schwer nachvollziehbare Unterschiede.

---

26. Runbook: Outbox wächst an

Vorlage / Textauszug

Runbook: Outbox Events werden nicht veröffentlicht

Symptom

Die Metrik `outbox_unpublished_events` steigt kontinuierlich an.

Auswirkung

Nachgelagerte Systeme wie Billing oder Reporting erhalten keine neuen Events. Bestellungen werden gespeichert, aber nicht weiterverarbeitet.

Erste Prüfung

oc logs -n order-prod deployment/order-service --tail=200 | grep -i outbox

oc exec -n order-prod deployment/order-service -- curl -s localhost:8080/actuator/health

Vorlage / Textauszug
Datenbank prüfen

SELECT event_type, count(*)

FROM outbox_events

WHERE published_at IS NULL

GROUP BY event_type;

SELECT id, event_type, publish_attempts, last_error

FROM outbox_events

WHERE published_at IS NULL

ORDER BY created_at

LIMIT 20;

Vorlage / Textauszug
Broker prüfen

oc get pods -n messaging

oc logs -n messaging statefulset/kafka --tail=100

Vorlage / Textauszug
Häufige Ursachen
  • Broker nicht erreichbar
  • falsche Broker-URL
  • Zertifikat abgelaufen
  • Topic fehlt
  • NetworkPolicy blockiert Egress
  • Event Payload kann nicht serialisiert werden
Sofortmaßnahmen
  • Broker-Verbindung wiederherstellen
  • fehlerhafte Konfiguration korrigieren
  • betroffene Events nicht manuell löschen
  • nach Wiederherstellung kontrollieren, ob Outbox sinkt
Eskalation

Wenn Events fachlich fehlerhaft sind, Team Order informieren.

Wenn Broker nicht verfügbar ist, Plattformteam informieren.

Dieses Runbook ist direkt mit der Anwendung verbunden. Es ist kein separater Anhang, sondern Teil des Betriebswissens zum Order Service.

---

27. Übung: Servicegrenze prüfen

Prüfe folgende Aussage:

Vorlage / Textauszug

Der Billing Service darf direkt in der Order-Datenbank den Status einer Bestellung auf BILLED setzen, sobald eine Rechnung erzeugt wurde.

Beantworte:

Vorlage / Textauszug
  1. Warum ist das problematisch?
  2. Welche Alternative ist besser?
  3. Welche Events oder APIs wären sinnvoll?
  4. Welche Fehler könnten entstehen, wenn Billing direkt schreibt?

---

28. Musterlösung

Die Aussage ist problematisch, weil der Billing Service damit die fachliche Verantwortung des Order Service verletzt. Die Order-Datenbank gehört dem Order Service. Wenn Billing direkt hineinschreibt, entstehen versteckte Kopplung und schwer nachvollziehbare Fehler.

Besser ist ein definierter Kommunikationsweg. Der Billing Service kann ein InvoiceCreatedEvent veröffentlichen. Der Order Service kann dieses Event konsumieren und selbst entscheiden, ob der Bestellstatus auf BILLED geändert werden darf. Alternativ kann Billing eine interne API des Order Service aufrufen, sofern dieser Weg fachlich und technisch gewünscht ist.

Sinnvolle Events wären:

Vorlage / Textauszug

OrderCreatedEvent

InvoiceDraftCreatedEvent

InvoiceApprovedEvent

OrderBilledEvent

Direktes Schreiben kann zu falschen Statuswechseln, Race Conditions, fehlendem Audit Logging, unklaren Transaktionsgrenzen und beschädigter Datenhoheit führen.

---

29. Merksätze

Vorlage / Textauszug

Eine moderne Anwendung besteht nicht nur aus Code.

Servicegrenzen schützen fachliche Verantwortung.

Das Frontend trifft keine finalen Sicherheitsentscheidungen.

Events brauchen Idempotenz.

OpenAPI macht Schnittstellen überprüfbar.

Metriken müssen fachliche Zustände sichtbar machen.

Ein Container Image soll unverändert durch Umgebungen laufen.

Runbooks gehören direkt zum Servicewissen.

Daten, Messaging und Integration

Outbox und Eventfluss
Kompakte Themenübersicht
PortalOrder DBWrite + OutboxPublisherKafkaEventsRabbitMQoptionalBillingConsumerReportingRead ModelRESTTXpublishconsumequeueasync

1. Warum Daten und Integration der Kern eines Enterprise-Systems sind

Moderne Anwendungen bestehen nicht nur aus Benutzeroberflächen und REST-Endpunkten. Der wichtigste Teil einer Enterprise-Landschaft ist häufig der Datenfluss: Welche Daten entstehen in welchem System, wer darf sie verändern, wer darf sie lesen, welche Ereignisse werden veröffentlicht, wie werden Fehler wiederholt und wie bleibt nachvollziehbar, was passiert ist?

Im Enterprise Platform Lab sind Daten nicht an eine einzige Infrastruktur gebunden. Einige Daten liegen in PostgreSQL auf Bare Metal, andere in einer Managed Database in der Cloud, Partnerdateien liegen in Object Storage, Events laufen über Kafka oder RabbitMQ, und Anwendungen auf OpenShift konsumieren oder veröffentlichen diese Informationen. Genau dadurch entsteht ein realistisches Bild: Moderne Anwendungen arbeiten selten allein. Sie sind Teil eines Netzes aus Datenbanken, APIs, Broker-Systemen, Dateiablagen und Legacy-Schnittstellen.

Ein häufiger Fehler ist, Integration nur als technische Verbindung zu betrachten. Man sagt dann: "Service A ruft Service B auf" oder "Event X wird an Kafka gesendet". Das reicht nicht. In Enterprise-Systemen muss man zusätzlich verstehen:

Vorlage / Textauszug
  • Wer besitzt die Daten?
  • Welche Daten sind führend?
  • Welche Daten sind nur Kopien?
  • Was passiert bei Fehlern?
  • Wie werden Events erneut verarbeitet?
  • Wie verhindert man doppelte Verarbeitung?
  • Wie werden personenbezogene Daten geschützt?
  • Wie lange werden Nachrichten und Dateien aufbewahrt?
  • Wie kann der Betrieb einen Fehler nachvollziehen?

Datenarchitektur ist deshalb keine Nebendisziplin. Sie entscheidet, ob ein System zuverlässig, auditierbar und wartbar ist.

---

2. Datenbesitz statt gemeinsame Datenbank

In alten Enterprise-Landschaften greifen oft viele Anwendungen auf dieselbe Datenbank zu. Das ist am Anfang bequem, wird aber mit der Zeit gefährlich. Wenn mehrere Systeme dieselben Tabellen direkt verändern, ist unklar, wem die Daten wirklich gehören. Jede Änderung am Schema kann andere Anwendungen beschädigen. Fachliche Regeln sind verteilt, und niemand kann zuverlässig sagen, welcher Prozess einen Wert geändert hat.

Im Zielbild des Labs gilt daher eine klare Regel:

Vorlage / Textauszug

Ein Service besitzt seine fachlichen Daten selbst.

Andere Systeme greifen nicht direkt auf seine Tabellen zu.

Der Order Service besitzt Bestellungen. Der Billing Service besitzt Rechnungen. Der Reporting Service darf Daten lesen oder aus Replikaten verarbeiten, aber er darf nicht heimlich Bestellungen verändern. Das Admin Portal besitzt keine Kerndaten, sondern ruft fachliche APIs auf.

Diese Regel ist wichtig, weil sie fachliche Verantwortung sichtbar macht. Wenn eine Bestellung falsch ist, ist der Order Service verantwortlich. Wenn eine Rechnung falsch ist, ist der Billing Service verantwortlich. Wenn ein Report falsch aggregiert, ist der Reporting Service verantwortlich.

Datenbesitz bedeutet nicht, dass jeder Service zwingend eine eigene physische Datenbank braucht. In einem Lab kann man mehrere Schemas auf derselben PostgreSQL-Instanz verwenden. Wichtig ist die logische Grenze:

Vorlage / Textauszug

orders_schema gehört dem Order Service.

billing_schema gehört dem Billing Service.

reporting_schema gehört dem Reporting Service.

Andere Services dürfen diese Schemas nicht direkt verändern.

---

3. Datenmodell im Lab

Das Lab verwendet mehrere Datenbereiche.

Der Order Service speichert Bestellungen, Bestellpositionen und Outbox-Events. Der Billing Service speichert Rechnungsentwürfe, Rechnungen, Zahlungsstatus und Audit-Einträge. Der File Processing Service speichert Metadaten zu Partnerdateien. Der Reporting Service erzeugt eigene Auswertungsdaten, die aus Events, Datenbank-Views oder Exporten entstehen können.

Eine vereinfachte Sicht:

Vorlage / Textauszug

Order Service

  • orders
  • order_items
  • outbox_events

Billing Service

  • invoices
  • invoice_lines
  • payment_status
  • billing_audit_log

File Processing Service

  • file_batches
  • file_records
  • file_errors

Reporting Service

  • daily_order_summary
  • daily_billing_summary
  • partner_file_quality_summary

Diese Aufteilung verhindert, dass ein einziger Datenbankbereich zu einem unkontrollierten Sammelbecken wird.

---

4. Relationale Datenbanken

Relationale Datenbanken bleiben in modernen Enterprise-Systemen sehr wichtig. Sie sind besonders stark, wenn Daten konsistent, abfragbar und transaktional verarbeitet werden müssen. Bestellungen, Rechnungen, Kundenreferenzen, Statusänderungen und Audit-Daten sind typische Beispiele.

Im Lab verwenden wir PostgreSQL als Standarddatenbank. PostgreSQL kann lokal, auf einer VM, auf Bare Metal, in OpenShift oder als Managed Cloud Database betrieben werden. Die Architektur muss aber bewusst entscheiden, welche Variante für welchen Zweck passt.

Für produktionsnahe Kernsysteme kann Bare Metal oder eine dedizierte VM sinnvoll sein, wenn I/O-Leistung, Backup-Kontrolle oder bestehende Betriebsprozesse wichtig sind. Für Testumgebungen kann eine Managed Cloud Database praktisch sein. Für lokale Entwicklung reicht Docker Compose.

Eine wichtige Regel lautet:

Vorlage / Textauszug

Die Anwendung darf nicht wissen, ob PostgreSQL auf Bare Metal, VM oder Cloud läuft.

Sie kennt nur Host, Port, Datenbankname, Benutzer und Zertifikatskonfiguration.

Diese Entkopplung ermöglicht spätere Migrationen.

---

5. Schema-Migration mit Flyway

Datenbankschema darf nicht manuell verändert werden. Jede Änderung muss versioniert sein. Dafür verwenden wir Flyway oder Liquibase.

Ein Beispiel für Flyway im Order Service:

Skizze / Textauszug
source-material/main/resources/db/migration/
├── V001__create_order_tables.sql
├── V002__create_outbox_table.sql
├── V003__add_order_status_index.sql
└── V004__add_customer_reference.sql

Eine Migration ist ein technischer Nachweis. Sie zeigt, wann und wie das Datenmodell verändert wurde.

Beispiel:

sql
CREATE TABLE orders (
    id UUID PRIMARY KEY,
    customer_id VARCHAR(80) NOT NULL,
    status VARCHAR(40) NOT NULL,
    total_amount NUMERIC(12, 2) NOT NULL,
    currency VARCHAR(3) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL,
    updated_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_status ON orders(status);

Diese Migration enthält bereits zwei wichtige Betriebsaspekte: Abfragen nach Kunde und Status werden erwartet und deshalb indiziert. Ein Schema ist also nicht nur Datenspeicherung, sondern auch Ausdruck der erwarteten Nutzung.

---

6. Transaktionen und fachliche Konsistenz

Eine Bestellung wird nicht nur als einzelner Datensatz gespeichert. Sie enthält Positionen, Beträge, Status und möglicherweise ein fachliches Event. Diese Schritte müssen konsistent sein.

Wenn die Bestellung gespeichert wird, aber das Event nicht entsteht, kann Billing nicht reagieren. Wenn das Event entsteht, aber die Bestellung nicht gespeichert wird, verarbeitet Billing falsche Informationen.

Darum gilt:

Vorlage / Textauszug

Fachliche Zustandsänderung und Outbox-Eintrag gehören in dieselbe Transaktion.

Beispiel:

java
package com.seb4u.demo.spring.order.application;

import com.seb4u.demo.spring.order.domain.Order;
import com.seb4u.demo.spring.order.domain.OrderRepository;
import com.seb4u.demo.spring.order.outbox.OutboxEvent;
import com.seb4u.demo.spring.order.outbox.OutboxRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.util.UUID;

@Service
public class ConfirmOrderService {

    private final OrderRepository orderRepository;
    private final OutboxRepository outboxRepository;
    private final Clock clock;

    public ConfirmOrderService(
            OrderRepository orderRepository,
            OutboxRepository outboxRepository,
            Clock clock
    ) {
        this.orderRepository = orderRepository;
        this.outboxRepository = outboxRepository;
        this.clock = clock;
    }

    @Transactional
    public void confirmOrder(UUID orderId) {
        Order order = orderRepository.findByIdForUpdate(orderId)
                .orElseThrow(() -> new IllegalArgumentException(class="str">"order not found: " + orderId));

        order.confirm(Instant.now(clock));
        orderRepository.save(order);

        outboxRepository.save(OutboxEvent.orderConfirmed(
                order.id(),
                order.customerId(),
                order.totalAmount(),
                Instant.now(clock)
        ));
    }
}

Die Methode verwendet eine Transaktion. Dadurch werden Bestellung und Outbox-Eintrag gemeinsam bestätigt oder gemeinsam zurückgerollt.

---

7. Outbox Publisher

Outbox-Pattern
Kompakte Lernhilfe
Business TXorder + outboxPollerBrokerConsumerAck / Retry

Der Outbox Publisher liest unveröffentlichte Events und sendet sie an den Broker. Er ist bewusst von der fachlichen Transaktion getrennt. Dadurch bleibt die Bestellung schnell und konsistent, während die Veröffentlichung wiederholt werden kann.

Ein einfaches Beispiel:

java
package com.seb4u.demo.spring.order.outbox;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.util.List;

@Component
public class OutboxPublisherJob {

    private final OutboxRepository outboxRepository;
    private final EventBrokerClient eventBrokerClient;
    private final Clock clock;

    public OutboxPublisherJob(
            OutboxRepository outboxRepository,
            EventBrokerClient eventBrokerClient,
            Clock clock
    ) {
        this.outboxRepository = outboxRepository;
        this.eventBrokerClient = eventBrokerClient;
        this.clock = clock;
    }

    @Scheduled(fixedDelayString = class="str">"${outbox.publisher.delay:5000}")
    @Transactional
    public void publishPendingEvents() {
        List<OutboxEvent> events = outboxRepository.findNextUnpublishedBatch(50);

        for (OutboxEvent event : events) {
            try {
                eventBrokerClient.publish(event.topic(), event.key(), event.payload());
                event.markPublished(Instant.now(clock));
            } catch (Exception ex) {
                event.registerFailure(ex.getMessage());
            }
        }
    }
}

Dieses Beispiel ist bewusst einfach. In produktionsnahen Umgebungen würde man zusätzlich Sperrmechanismen, Backoff, Dead-Letter-Handling, Metriken und Tracing ergänzen.

---

8. Messaging: Kafka oder RabbitMQ?

Messaging verbindet Systeme asynchron. Das bedeutet: Ein Service muss nicht warten, bis ein anderer Service seine Arbeit sofort erledigt. Stattdessen veröffentlicht er ein Event oder eine Nachricht.

Kafka eignet sich besonders für Event Streams, hohe Durchsätze, Wiederholbarkeit, zeitliche Entkopplung und mehrere Consumer-Gruppen. RabbitMQ eignet sich stark für klassische Work Queues, Routing, bestimmte Zustellmuster und Aufgabenverteilung.

Im Lab können beide Varianten vorkommen:

Vorlage / Textauszug

Kafka:

  • OrderCreatedEvent
  • OrderConfirmedEvent
  • InvoiceCreatedEvent
  • Reporting Streams

RabbitMQ:

  • File validation jobs
  • Retry queues
  • gezielte Arbeitsaufträge
  • klassische Command-Nachrichten

Die Entscheidung hängt nicht von Mode ab, sondern von Semantik.

Ein Event sagt:

Vorlage / Textauszug

Etwas ist passiert.

Ein Command sagt:

Vorlage / Textauszug

Bitte tue etwas.

Diese Unterscheidung ist sehr wichtig.

---

9. Event-Naming

Events sollten fachlich benannt werden. Schlechte Namen sind technisch oder unklar:

Vorlage / Textauszug

OrderMessage

DataChanged

StatusUpdate

PayloadEvent

Bessere Namen:

Vorlage / Textauszug

OrderCreated

OrderConfirmed

InvoiceDraftCreated

PaymentMarkedAsOverdue

PartnerFileRejected

Ein Event beschreibt eine fachliche Tatsache in der Vergangenheit. Deshalb wird häufig die Vergangenheitsform verwendet.

Ein Event sollte nicht lauten:

Vorlage / Textauszug

CreateInvoice

Das wäre eher ein Command.

Besser:

Vorlage / Textauszug

OrderConfirmed

Der Billing Service entscheidet dann, ob daraus eine Rechnung entsteht.

---

10. Event Payload

Ein Event sollte genug Informationen enthalten, damit Consumer sinnvoll reagieren können. Es sollte aber nicht die gesamte Datenbankstruktur eines Services exportieren.

Beispiel:

json
{
  class="str">"eventId": class="str">"7c6715a3-ff91-4e96-b6e1-7f7cf4e18c50",
  class="str">"eventType": class="str">"OrderConfirmed",
  class="str">"occurredAt": class="str">"2026-07-05T18:30:00Z",
  class="str">"orderId": class="str">"0b33b8b8-81d8-4af1-9343-660d0f4a4131",
  class="str">"customerId": class="str">"CUST-4711",
  class="str">"totalAmount": 129.90,
  class="str">"currency": class="str">"EUR",
  class="str">"traceId": class="str">"82b81f5f91d342aa"
}

Wichtige Felder sind:

Vorlage / Textauszug
  • eventId für Idempotenz
  • eventType für Verarbeitung
  • occurredAt für zeitliche Einordnung
  • fachliche IDs
  • relevante Summen oder Statuswerte
  • traceId für Nachvollziehbarkeit

Nicht jedes Event muss alle Details enthalten. Wenn ein Consumer mehr Informationen braucht, kann er über eine API nachladen. Das ist ein bewusstes Design.

---

11. Idempotenz

In verteilten Systemen können Nachrichten mehrfach ankommen. Das ist kein Ausnahmefall, sondern normales Verhalten. Ein Consumer muss deshalb idempotent sein.

Idempotenz bedeutet:

Vorlage / Textauszug

Dieselbe Nachricht mehrfach zu verarbeiten führt nicht zu falschen mehrfachen Ergebnissen.

Beispiel: Wenn Billing ein OrderConfirmed Event zweimal erhält, darf nicht zweimal dieselbe Rechnung erstellt werden.

Eine einfache Lösung ist eine Tabelle für verarbeitete Events:

sql
CREATE TABLE processed_events (
    event_id UUID PRIMARY KEY,
    event_type VARCHAR(160) NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE NOT NULL
);

Der Consumer prüft vor der Verarbeitung, ob event_id bereits existiert.

java
package com.seb4u.demo.spring.billing.messaging;

import com.seb4u.demo.spring.billing.domain.InvoiceService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class OrderConfirmedConsumer {

    private final ProcessedEventRepository processedEventRepository;
    private final InvoiceService invoiceService;

    public OrderConfirmedConsumer(
            ProcessedEventRepository processedEventRepository,
            InvoiceService invoiceService
    ) {
        this.processedEventRepository = processedEventRepository;
        this.invoiceService = invoiceService;
    }

    @Transactional
    public void handle(OrderConfirmedMessage message) {
        if (processedEventRepository.existsById(message.eventId())) {
            return;
        }

        invoiceService.createInvoiceDraft(
                message.orderId(),
                message.customerId(),
                message.totalAmount(),
                message.currency()
        );

        processedEventRepository.markProcessed(message.eventId(), message.eventType());
    }
}

Ohne Idempotenz wirkt Messaging im Test oft korrekt, aber in Produktion entstehen doppelte Rechnungen, doppelte E-Mails oder doppelte Buchungen.

---

12. Dead Letter Queue

Nicht jede Nachricht kann erfolgreich verarbeitet werden. Manchmal ist das Format falsch, eine Referenz fehlt, ein externer Dienst ist nicht erreichbar oder ein fachlicher Zustand passt nicht.

Wenn ein Consumer dieselbe fehlerhafte Nachricht endlos wiederholt, blockiert er Verarbeitung. Deshalb braucht man ein Dead-Letter-Konzept.

Eine Dead Letter Queue enthält Nachrichten, die nach mehreren Versuchen nicht verarbeitet werden konnten.

Wichtig ist: Eine Dead Letter Queue ist kein Mülleimer. Sie ist ein Betriebsartefakt.

Sie braucht:

Vorlage / Textauszug
  • Grund des Fehlers
  • Originalnachricht
  • Zeitpunkt
  • Consumer-Name
  • Anzahl Versuche
  • Trace-ID
  • Runbook für Analyse und Wiederaufnahme

Beispiel für eine Fehlerstruktur:

json
{
  class="str">"failedAt": class="str">"2026-07-05T19:10:00Z",
  class="str">"consumer": class="str">"billing-order-confirmed-consumer",
  class="str">"reason": class="str">"customer reference not found",
  class="str">"attempts": 5,
  class="str">"traceId": class="str">"0a74ddfd1b7b4f12",
  class="str">"originalEvent": {
    class="str">"eventType": class="str">"OrderConfirmed",
    class="str">"orderId": class="str">"0b33b8b8-81d8-4af1-9343-660d0f4a4131",
    class="str">"customerId": class="str">"CUST-404"
  }
}

---

13. Integration über REST APIs

Nicht jede Integration ist Event-basiert. REST APIs bleiben wichtig, besonders wenn ein System eine direkte Antwort braucht.

Beispiel:

Vorlage / Textauszug

Customer Portal ruft Order Service auf, um aktuelle Bestellungen anzuzeigen.

Admin Portal ruft Billing Service auf, um Rechnungsstatus zu prüfen.

Reporting ruft eine Export-API auf, um Daten für einen Zeitraum zu laden.

REST eignet sich gut für synchrone Abfragen und direkte Benutzerinteraktionen. Es ist weniger gut für lange Hintergrundprozesse oder viele abhängige Zustandsänderungen.

Eine gute API definiert klare Ressourcen:

Vorlage / Textauszug

GET /orders/{orderId}

POST /orders

GET /customers/{customerId}/orders

GET /invoices/{invoiceId}

POST /invoices/{invoiceId}/mark-paid

Eine schlechte API versteckt Fachlichkeit hinter technischen Namen:

Vorlage / Textauszug

POST /process

POST /updateData

GET /getStatus

---

14. API-Verträge mit OpenAPI

OpenAPI macht REST-Schnittstellen dokumentierbar und prüfbar. In Enterprise-Projekten ist das wichtig, weil mehrere Teams dieselben Schnittstellen verwenden.

Ein kleiner Ausschnitt:

yaml
openapi: 3.0.3
info:
  title: Order Service API
  version: 1.0.0
paths:
  /orders:
    post:
      summary: Create a new order
      operationId: createOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: class="str">'#/components/schemas/CreateOrderRequest'
      responses:
        class="str">'201':
          description: Order created
          content:
            application/json:
              schema:
                $ref: class="str">'#/components/schemas/CreateOrderResponse'
        class="str">'400':
          description: Invalid request
components:
  schemas:
    CreateOrderRequest:
      type: object
      required:
        - customerId
        - items
      properties:
        customerId:
          type: string
        items:
          type: array
          items:
            $ref: class="str">'#/components/schemas/CreateOrderItem'
    CreateOrderItem:
      type: object
      required:
        - productId
        - quantity
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1

OpenAPI hilft nicht nur beim Lesen. Es kann auch für Client-Generierung, Contract Tests und API Reviews genutzt werden.

---

15. Anti-Corruption Layer

Wenn moderne Services mit Legacy-Systemen sprechen, dürfen Legacy-Begriffe nicht unkontrolliert in die neue Domäne gelangen.

Ein Anti-Corruption Layer übersetzt alte Schnittstellen in moderne fachliche Modelle.

Beispiel: Ein Legacy-System liefert Statuswerte:

Vorlage / Textauszug

A

B

C

X

9

Der moderne Order Service will aber klare Status:

Vorlage / Textauszug

CREATED

CONFIRMED

CANCELLED

FAILED

Die Übersetzung gehört nicht überall verteilt in Controller, Services und Repositories. Sie gehört in eine klare Integrationsschicht.

java
package com.seb4u.demo.spring.order.integration.legacy;

import com.seb4u.demo.spring.order.domain.OrderStatus;
import org.springframework.stereotype.Component;

@Component
public class LegacyOrderStatusMapper {

    public OrderStatus map(String legacyStatus) {
        return switch (legacyStatus) {
            case class="str">"A" -> OrderStatus.CREATED;
            case class="str">"B" -> OrderStatus.CONFIRMED;
            case class="str">"C" -> OrderStatus.CANCELLED;
            case class="str">"X", class="str">"9" -> OrderStatus.FAILED;
            default -> throw new IllegalArgumentException(class="str">"Unknown legacy status: " + legacyStatus);
        };
    }
}

Dieser Mapper sieht klein aus, ist aber architektonisch wichtig. Er verhindert, dass Legacy-Codes die moderne Domäne verschmutzen.

---

16. Dateibasierte Integration

Viele Enterprise-Systeme verwenden weiterhin Dateien. Das ist nicht automatisch schlecht. Dateien sind besonders nützlich, wenn Partner keine APIs anbieten, große Datenmengen übertragen werden oder rechtliche Archivierung erforderlich ist.

Ein moderner dateibasierter Ablauf sieht kontrolliert aus:

Skizze / Textauszug
Partner stellt Datei bereit
↓
Datei landet im incoming Bucket
↓
Metadaten werden gespeichert
↓
Validierung startet
↓
gültige Datensätze werden verarbeitet
↓
fehlerhafte Datensätze werden mit Gründen abgelegt
↓
Event wird veröffentlicht
↓
Reporting und Fachservices reagieren

Wichtig ist, dass Datei-Verarbeitung nicht nur ein Bash-Skript auf einem Server ist. Sie braucht Status, Wiederholbarkeit, Fehlerprotokolle und Monitoring.

---

17. File Batch Datenmodell

sql
CREATE TABLE file_batches (
    id UUID PRIMARY KEY,
    partner_id VARCHAR(80) NOT NULL,
    file_name VARCHAR(255) NOT NULL,
    object_storage_key VARCHAR(500) NOT NULL,
    status VARCHAR(40) NOT NULL,
    received_at TIMESTAMP WITH TIME ZONE NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE,
    total_records INTEGER,
    valid_records INTEGER,
    invalid_records INTEGER,
    last_error TEXT
);

CREATE TABLE file_record_errors (
    id UUID PRIMARY KEY,
    batch_id UUID NOT NULL REFERENCES file_batches(id),
    line_number INTEGER NOT NULL,
    error_code VARCHAR(80) NOT NULL,
    error_message TEXT NOT NULL,
    raw_record TEXT
);

Dieses Modell ist betriebsfreundlich. Man sieht nicht nur, dass eine Datei fehlgeschlagen ist, sondern auch, wie viele Datensätze betroffen sind und warum.

---

18. Integration mit Object Storage

Object Storage eignet sich gut für Partnerdateien, Exporte, Reports und Archivierung. Die Anwendung speichert nicht direkt auf lokaler Platte, sondern in Buckets.

Beispiel mit S3-kompatibler API:

java
package com.seb4u.demo.spring.fileprocessing.storage;

import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import org.springframework.stereotype.Component;

import java.nio.file.Path;

@Component
public class PartnerFileStorage {

    private final S3Client s3Client;
    private final PartnerFileStorageProperties properties;

    public PartnerFileStorage(S3Client s3Client, PartnerFileStorageProperties properties) {
        this.s3Client = s3Client;
        this.properties = properties;
    }

    public void storeIncomingFile(String partnerId, String fileName, Path localFile) {
        String key = class="str">"incoming/%s/%s".formatted(partnerId, fileName);

        PutObjectRequest request = PutObjectRequest.builder()
                .bucket(properties.bucket())
                .key(key)
                .metadata(java.util.Map.of(
                        class="str">"partnerId", partnerId,
                        class="str">"originalFileName", fileName
                ))
                .build();

        s3Client.putObject(request, RequestBody.fromFile(localFile));
    }
}

Auch hier gilt: Die Dateiablage ist Teil der Architektur. Bucket-Namen, Verschlüsselung, Versionierung, Aufbewahrung und Zugriff müssen dokumentiert werden.

---

19. Datenklassifizierung

Nicht alle Daten sind gleich sensibel. Eine Plattform muss unterscheiden können.

Beispiele:

Vorlage / Textauszug

Public: öffentliche Produktinformationen

Internal: interne Betriebsdaten

Confidential: Kundeninformationen, Rechnungen

Restricted: Zahlungsdaten, besonders schützenswerte Daten

Diese Klassifizierung beeinflusst:

Vorlage / Textauszug
  • Speicherort
  • Verschlüsselung
  • Zugriff
  • Logging
  • Aufbewahrung
  • Backup
  • Exportregeln
  • Maskierung in Testumgebungen

Ein häufiger Fehler ist, produktive Daten einfach in Testumgebungen zu kopieren. Das kann Datenschutz- und Compliance-Probleme erzeugen. Testdaten müssen anonymisiert, synthetisch erzeugt oder streng kontrolliert werden.

---

20. Logging von Datenflüssen

Datenflüsse müssen nachvollziehbar sein, aber Logs dürfen keine sensiblen Daten unkontrolliert enthalten.

Gute Logs enthalten:

Vorlage / Textauszug
  • traceId
  • eventId
  • orderId oder technische Referenz
  • Status
  • Verarbeitungsschritt
  • Fehlercode

Schlechte Logs enthalten:

Vorlage / Textauszug
  • vollständige Kundendaten
  • Zahlungsinformationen
  • komplette Tokens
  • Passwörter
  • vollständige Partnerdateien

Beispiel für ein gutes Log:

json
{
  class="str">"level": class="str">"INFO",
  class="str">"service": class="str">"billing-service",
  class="str">"traceId": class="str">"82b81f5f91d342aa",
  class="str">"eventId": class="str">"7c6715a3-ff91-4e96-b6e1-7f7cf4e18c50",
  class="str">"message": class="str">"invoice draft created",
  class="str">"orderId": class="str">"0b33b8b8-81d8-4af1-9343-660d0f4a4131",
  class="str">"invoiceId": class="str">"INV-2026-0004711"
}

Das Log ist nützlich, ohne unnötig sensible Details zu veröffentlichen.

---

21. Tracing über Systemgrenzen

In einer verteilten Architektur reicht ein einzelnes Log nicht aus. Eine Anfrage oder ein Event wandert durch mehrere Systeme. Tracing verbindet diese Schritte.

Beispiel:

Skizze / Textauszug
Customer Portal
↓ traceId=abc123
Order Service
↓
Outbox Publisher
↓
Kafka
↓
Billing Service
↓
Invoice Database

Wenn überall dieselbe Trace-ID sichtbar ist, kann der Betrieb den Ablauf nachvollziehen. Ohne Trace-ID sieht man nur isolierte Logzeilen.

Für HTTP wird Trace-Kontext oft über Header weitergegeben. Für Events muss man Trace-Informationen in Message Headers oder Payload-Metadaten übernehmen.

---

22. Reporting und Datenkopien

Reporting braucht oft andere Datenstrukturen als operative Anwendungen. Ein Order Service speichert Bestellungen transaktional. Reporting möchte aggregierte Zahlen pro Tag, Region, Produkt oder Partner sehen.

Es ist meist falsch, komplexe Reports direkt auf operativen Tabellen auszuführen. Das kann Produktivsysteme belasten und fachliche Grenzen verwischen.

Besser sind kontrollierte Kopien:

Skizze / Textauszug
Events → Reporting Consumer → Reporting Schema
Datenbank Export → Reporting Import → Reporting Tabellen
CDC Stream → Data Platform → Auswertungen

Im Lab genügt zunächst ein Reporting Consumer, der Events verarbeitet und eigene Summary-Tabellen pflegt.

sql
CREATE TABLE daily_order_summary (
    summary_date DATE NOT NULL,
    currency VARCHAR(3) NOT NULL,
    order_count INTEGER NOT NULL,
    total_amount NUMERIC(14, 2) NOT NULL,
    PRIMARY KEY (summary_date, currency)
);

Diese Tabelle gehört dem Reporting Service, nicht dem Order Service.

---

23. Change Data Capture

Change Data Capture, kurz CDC, liest Änderungen aus einer Datenbank und stellt sie anderen Systemen bereit. Werkzeuge wie Debezium können Änderungen aus PostgreSQL erfassen und als Events veröffentlichen.

CDC kann nützlich sein, wenn bestehende Systeme keine Events veröffentlichen können. Es ist aber kein Ersatz für fachliches Event-Design.

Ein technisches Datenbank-Update sagt nicht automatisch, was fachlich passiert ist.

Beispiel:

Vorlage / Textauszug

status changed from A to B

Das ist technisch.

Fachlich könnte es bedeuten:

Vorlage / Textauszug

OrderConfirmed

OrderReleasedForBilling

OrderApprovedByBackoffice

CDC ist daher besonders gut für Integration mit Legacy-Systemen, Replikation und Übergangslösungen. Für neue Services sollte man fachliche Events bewusst modellieren.

---

24. Konsistenzmodelle

Nicht alle Systeme sind sofort konsistent. Bei asynchroner Verarbeitung entsteht zeitweise Verzögerung.

Beispiel:

Vorlage / Textauszug

Kunde bestätigt Bestellung.

Order Service speichert Bestellung sofort.

Billing erstellt Rechnung einige Sekunden später.

Reporting zeigt Umsatz vielleicht erst nach einer Minute.

Das ist nicht automatisch ein Fehler. Es ist eventual consistency.

Wichtig ist, dass Benutzeroberflächen und Fachprozesse damit umgehen können. Das Portal darf nicht erwarten, dass eine Rechnung im selben Millisekunden-Moment existiert. Es kann stattdessen anzeigen:

Vorlage / Textauszug

Ihre Bestellung wurde bestätigt. Die Rechnung wird vorbereitet.

Eine gute Architektur erklärt solche Zustände fachlich.

---

25. Integrationstest mit Testcontainers

Integration muss getestet werden. Mock-Tests reichen nicht aus, wenn Datenbank, Migrationen und Messaging beteiligt sind.

Mit Testcontainers kann ein Test echte Infrastrukturkomponenten starten.

java
package com.seb4u.demo.spring.order;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(class="str">"postgres:16")
            .withDatabaseName(class="str">"orders")
            .withUsername(class="str">"orders")
            .withPassword(class="str">"orders");

    @DynamicPropertySource
    static void databaseProperties(DynamicPropertyRegistry registry) {
        registry.add(class="str">"spring.datasource.url", postgres::getJdbcUrl);
        registry.add(class="str">"spring.datasource.username", postgres::getUsername);
        registry.add(class="str">"spring.datasource.password", postgres::getPassword);
    }

    @Test
    void contextStartsWithRealPostgres() {
        // Der Test startet die Anwendung mit echter PostgreSQL-Datenbank.
        // Flyway-Migrationen laufen dabei ebenfalls.
    }
}

Dieser Test prüft nicht nur Java-Code. Er prüft, ob Anwendung, Konfiguration und Datenbankmigration zusammenpassen.

---

26. Runbook: Outbox wächst an

Vorlage / Textauszug

Runbook: Outbox wächst an

Symptom

Die Tabelle `outbox_events` enthält viele unveröffentlichte Events. Billing oder Reporting erhält keine neuen Ereignisse.

Erste Prüfung

oc logs -n order-prod deployment/order-service --tail=200 | grep -i outbox

Vorlage / Textauszug
Datenbank prüfen

SELECT count(*)

FROM outbox_events

WHERE published_at IS NULL;

SELECT event_type, count(*)

FROM outbox_events

WHERE published_at IS NULL

GROUP BY event_type;

SELECT id, event_type, publish_attempts, last_error, created_at

FROM outbox_events

WHERE published_at IS NULL

ORDER BY created_at

LIMIT 20;

Vorlage / Textauszug
Broker prüfen

oc get pods -n messaging

oc logs -n messaging statefulset/kafka --tail=100

Vorlage / Textauszug
Häufige Ursachen
  • Kafka oder RabbitMQ nicht erreichbar
  • falsche Broker-Konfiguration
  • Topic fehlt
  • Berechtigung fehlt
  • Netzwerkregel blockiert Egress
  • Payload kann nicht serialisiert werden
  • Publisher Job ist deaktiviert
Maßnahmen
  1. Broker-Erreichbarkeit prüfen.
  2. Anwendungskonfiguration prüfen.
  3. letzte Fehlermeldung in Outbox analysieren.
  4. Publisher nach Fehlerbehebung erneut laufen lassen.
  5. Falls Payload defekt ist, fachlich entscheiden, ob Event korrigiert, übersprungen oder manuell verarbeitet wird.
Eskalation
  • Messaging Team bei Broker-Problemen
  • Team Order bei Payload- oder Outbox-Problemen
  • Plattformteam bei Netzwerk- oder Secret-Problemen

---

27. Typische Fehler bei Daten und Integration

Ein häufiger Fehler ist direkter Datenbankzugriff zwischen Services. Dadurch entstehen versteckte Kopplungen und unklare Verantwortlichkeiten.

Ein zweiter Fehler ist fehlende Idempotenz. In Testumgebungen fällt das selten auf, in Produktion führt es zu doppelten Rechnungen, doppelten Benachrichtigungen oder falschen Reports.

Ein dritter Fehler ist zu große Event Payload. Wenn Events komplette Datenbankobjekte enthalten, werden sie schwer versionierbar und koppeln Consumer an interne Modelle.

Ein vierter Fehler ist fehlendes Dead-Letter-Konzept. Fehlerhafte Nachrichten werden endlos wiederholt oder verschwinden unkontrolliert.

Ein fünfter Fehler ist fehlende Datenklassifizierung. Dann landen sensible Informationen in Logs, Testdatenbanken oder ungeschützten Buckets.

Ein sechster Fehler ist ungeprüfte Datei-Verarbeitung. Partnerdateien werden verarbeitet, ohne Fehlerzeilen, Status und Wiederholbarkeit sauber zu dokumentieren.

---

28. Übung: Datenfluss für eine Bestellung modellieren

Modelliere den Datenfluss vom Erstellen einer Bestellung bis zur Rechnung.

Beantworte:

Vorlage / Textauszug
  1. Welche Daten speichert der Order Service?
  2. Welches Event entsteht?
  3. Welche Daten speichert der Billing Service?
  4. Wie wird doppelte Verarbeitung verhindert?
  5. Was passiert, wenn der Broker nicht erreichbar ist?
  6. Welche Logs und Metriken braucht der Betrieb?
  7. Welche Daten dürfen nicht im Log stehen?

---

29. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

Der Order Service speichert Bestellung, Bestellpositionen und den fachlichen Status. In derselben Transaktion schreibt er ein OrderConfirmed Event in die Outbox.

Der Outbox Publisher veröffentlicht das Event an Kafka. Wenn Kafka nicht erreichbar ist, bleibt das Event unveröffentlicht und wird später erneut versucht.

Der Billing Service konsumiert OrderConfirmed. Er prüft die eventId gegen eine processed_events Tabelle. Wenn das Event bereits verarbeitet wurde, beendet er die Verarbeitung ohne neue Rechnung.

Wenn das Event neu ist, erstellt Billing einen Rechnungsentwurf und markiert das Event als verarbeitet.

Der Betrieb überwacht die Anzahl unveröffentlichter Outbox Events, Consumer Lag, Dead Letter Messages, Fehlerlogs und Trace-IDs.

Logs dürfen orderId, eventId und traceId enthalten. Sie dürfen keine vollständigen Zahlungsdaten, Tokens oder sensiblen Kundendaten enthalten.

---

30. Merksätze

Vorlage / Textauszug

Datenbesitz ist wichtiger als die physische Datenbankgrenze.

Ein Event beschreibt, was passiert ist. Ein Command fordert eine Aktion an.

Outbox schützt vor auseinanderlaufender Datenbank- und Broker-Verarbeitung.

Idempotenz ist Pflicht, nicht Luxus.

Dead Letter Queues sind Betriebsartefakte, keine Müllhalden.

Dateibasierte Integration braucht Status, Fehlerprotokolle und Wiederholbarkeit.

Reporting sollte operative Systeme nicht unkontrolliert belasten.

Datenklassifizierung beeinflusst Logging, Storage, Testdaten und Zugriff.

Teil E: Observability, Betrieb, Backup, Disaster Recovery und Resilienz

Teil E: Observability, Betrieb, Backup, Disaster Recovery und Resilienz

PlanZielbildBuildUmsetzungRunBetriebTeil E: Observability, Betrieb, Backup, Disaster Recovery und Resilienz: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 9, Teil 10, Teil 38.

Observability und Betrieb

Logs, Metrics und Traces
Kompakte Themenübersicht
Services / NodesMetricsPrometheusLogsLokiTracesTempoGrafanaDashboardsAlertmanagerBenachrichtigungscrapeshipotelalerts

1. Warum Observability mehr ist als Monitoring

In vielen Projekten wird Monitoring erst am Ende eingebaut. Dann läuft die Anwendung zwar technisch, aber niemand kann zuverlässig erklären, was im Fehlerfall passiert. Ein Service ohne gute Beobachtbarkeit ist im Betrieb schwer zu verantworten. Man sieht vielleicht, dass ein Pod neu gestartet wurde, aber nicht warum. Man sieht vielleicht, dass eine API langsam ist, aber nicht, ob die Ursache im Code, in der Datenbank, im Broker, im Netzwerk oder in einer externen Abhängigkeit liegt.

Observability bedeutet, dass ein System so gebaut wird, dass sein Zustand von außen verständlich wird. Es reicht nicht, nur CPU und RAM zu messen. Für ein Enterprise-System braucht man technische Signale, fachliche Signale und Betriebsnachweise.

Ein modernes System beantwortet im Betrieb Fragen wie:

Vorlage / Textauszug

Ist der Service erreichbar?

Ist der Service nur gestartet oder wirklich funktionsfähig?

Wie viele Bestellungen wurden verarbeitet?

Wie viele Events hängen in der Outbox?

Wie lange dauert eine typische Anfrage?

Welche Abhängigkeit verursacht Latenz?

Welche Fehler treten wiederholt auf?

Welche Version läuft gerade?

Welche Konfiguration ist aktiv?

Welche Änderung wurde zuletzt deployt?

Observability ist deshalb keine Zusatzfunktion. Sie ist Teil der Architektur. Wenn ein Service keine Logs, Metriken, Traces, Health Checks, Dashboards und Runbooks hat, ist er nicht produktionsnah.

---

2. Die drei klassischen Säulen

Observability wird häufig über drei Säulen beschrieben:

Vorlage / Textauszug

Logs

Metriken

Traces

Logs beschreiben Ereignisse. Sie erzählen, was passiert ist. Ein Logeintrag kann zeigen, dass eine Bestellung erstellt wurde, dass eine Validierung fehlgeschlagen ist oder dass ein Aufruf zur Datenbank nicht funktioniert hat.

Metriken beschreiben Zahlen über die Zeit. Sie zeigen zum Beispiel Anzahl von Requests, Fehlerquote, Antwortzeiten, Speicherverbrauch, CPU-Nutzung, Queue-Länge oder Anzahl unveröffentlichter Outbox-Events.

Traces verbinden einzelne Schritte einer Anfrage über mehrere Services hinweg. Ein Trace zeigt, dass ein Request vom Customer Portal über das API Gateway zum Order Service, zur Datenbank, zur Outbox und später zum Billing Service geführt hat.

Diese drei Signale ergänzen sich. Logs ohne Metriken führen zu mühsamer Suche. Metriken ohne Logs zeigen nur Symptome. Traces ohne fachliche Informationen erklären oft nicht genug. Erst zusammen entsteht ein brauchbares Betriebsbild.

---

3. Observability-Zielbild im Lab

Observability-Zielbild
Kompakte Lernhilfe
ServiceLogsLokiMetricsPrometheusTracesTempoGrafanaSichtenAlertmanagerReaktion / Eskalation

Im Enterprise Platform Lab verwenden wir folgende Zielarchitektur:

mermaid
flowchart LR
    Portal[Customer Portal] --> Gateway[API Gateway]
    Gateway --> Order[Order Service]
    Order --> DB[(PostgreSQL)]
    Order --> Outbox[Outbox]
    Outbox --> Broker[Kafka / RabbitMQ]
    Broker --> Billing[Billing Service]

    Order --> Logs[Loki]
    Billing --> Logs
    Gateway --> Logs

    Order --> Metrics[Prometheus]
    Billing --> Metrics
    DB --> Metrics
    Broker --> Metrics

    Order --> Traces[Tempo]
    Billing --> Traces
    Gateway --> Traces

    Metrics --> Grafana[Grafana]
    Logs --> Grafana
    Traces --> Grafana

Grafana ist dabei nicht die Datenquelle selbst. Grafana visualisiert Daten aus Prometheus, Loki und Tempo. Prometheus sammelt Metriken. Loki speichert Logs. Tempo speichert Traces. In anderen Unternehmen können andere Werkzeuge genutzt werden, zum Beispiel Elastic, Splunk, Datadog, New Relic oder OpenTelemetry Collector mit verschiedenen Backends. Das Prinzip bleibt gleich.

---

4. Health Checks: gestartet ist nicht gesund

Ein Prozess kann laufen und trotzdem fachlich nicht funktionieren. Deshalb braucht ein Service Health Checks.

Spring Boot unterscheidet typischerweise zwischen Liveness und Readiness.

Liveness beantwortet: Muss der Container neu gestartet werden?

Readiness beantwortet: Darf der Service Traffic erhalten?

Ein Service kann live, aber nicht ready sein. Beispiel: Die Anwendung läuft, aber die Datenbank ist nicht erreichbar. Dann soll Kubernetes den Container nicht unbedingt sofort töten, aber der Service soll vorübergehend keinen Traffic bekommen.

Beispiel für OpenShift/Kubernetes:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.4.2
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 40
            periodSeconds: 20
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
            failureThreshold: 2

Ein häufiger Fehler ist, für beide Probes denselben Endpunkt zu verwenden. Wenn die Readiness wegen einer abhängigen Datenbank fehlschlägt, ist das sinnvoll. Wenn aber die Liveness wegen einer kurzzeitig nicht erreichbaren Datenbank fehlschlägt, kann Kubernetes den Pod unnötig oft neu starten. Dadurch wird die Störung schlimmer.

---

5. Spring Boot Actuator richtig nutzen

Spring Boot Actuator liefert viele Betriebsendpunkte. Diese dürfen aber nicht unkontrolliert nach außen exponiert werden.

Beispielkonfiguration:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
      show-details: never
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true
  metrics:
    tags:
      application: order-service
      environment: prod

Für produktionsnahe Umgebungen gilt: Health, Info und Prometheus sind nützlich. Detaillierte interne Informationen sollten nicht öffentlich erreichbar sein. Debug- oder Env-Endpunkte gehören nicht offen ins Internet.

Ein sauberer Betrieb trennt zwischen internen Management-Endpunkten und externen API-Endpunkten. In OpenShift kann das über Services, Routes, NetworkPolicies und Authentifizierung geregelt werden.

---

6. Strukturierte Logs

Logs sollten maschinenlesbar sein. Freitext-Logs sind schwer zu durchsuchen. Strukturierte Logs enthalten Felder wie Zeitstempel, Level, Service, Trace-ID, Request-ID, Benutzerkontext, Fehlercode und fachlichen Bezug.

Ein guter Logeintrag beantwortet:

Vorlage / Textauszug

Wann ist es passiert?

In welchem Service?

In welcher Version?

Für welche Anfrage?

Für welchen fachlichen Vorgang?

Was ist passiert?

Wie schwer ist es?

Welche Korrelation gibt es zu anderen Services?

Beispiel als JSON-Log:

json
{
  class="str">"timestamp": class="str">"2026-07-05T20:42:11.450Z",
  class="str">"level": class="str">"WARN",
  class="str">"service": class="str">"order-service",
  class="str">"environment": class="str">"prod",
  class="str">"traceId": class="str">"7a5f0f3e8d1a91c2",
  class="str">"spanId": class="str">"42ac917e2f1b",
  class="str">"orderId": class="str">"8c7db3d2-5b42-4f37-9ef8-8be092f2d011",
  class="str">"event": class="str">"OUTBOX_PUBLISH_RETRY",
  class="str">"message": class="str">"Outbox event could not be published. Retry scheduled.",
  class="str">"attempt": 3,
  class="str">"broker": class="str">"kafka-orders",
  class="str">"errorClass": class="str">"TimeoutException"
}

Dieser Logeintrag ist besser als:

Vorlage / Textauszug

Kafka geht nicht.

Denn er enthält Kontext, Wiederholungsversuch, Event, Service und Korrelation.

---

7. Logback-Konfiguration für JSON Logs

In einem Spring Boot Service kann JSON Logging zum Beispiel über einen JSON Encoder umgesetzt werden. Wichtig ist nicht die konkrete Library, sondern das Ergebnis: Logs sollen strukturiert und einheitlich sein.

Beispielhafte logback-spring.xml:

xml
<configuration>
    <springProperty scope=class="str">"context" name=class="str">"appName" source=class="str">"spring.application.name"/>

    <appender name=class="str">"CONSOLE" class=class="str">"ch.qos.logback.core.ConsoleAppender">
        <encoder class=class="str">"net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp/>
                <logLevel/>
                <loggerName/>
                <threadName/>
                <message/>
                <mdc/>
                <arguments/>
                <stackTrace/>
                <pattern>
                    <pattern>
                        {
                          class="str">"service": class="str">"${appName}",
                          class="str">"runtime": class="str">"spring-boot"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <root level=class="str">"INFO">
        <appender-ref ref=class="str">"CONSOLE"/>
    </root>
</configuration>

Im Container sollten Logs nach stdout/stderr geschrieben werden. Der Container schreibt nicht selbst in lokale Dateien, die später niemand findet. Die Plattform sammelt stdout/stderr zentral ein.

---

8. Trace-ID und Correlation-ID

In verteilten Systemen ist Korrelation entscheidend. Eine Benutzeraktion löst oft mehrere technische Schritte aus. Ohne gemeinsame ID ist die Suche schwer.

Beispielablauf:

Skizze / Textauszug
Customer Portal
↓ traceId=abc123
API Gateway
↓ traceId=abc123
Order Service
↓ traceId=abc123
PostgreSQL
↓
Outbox Event
↓ traceId=abc123 im Event Header
Kafka
↓
Billing Service

Wenn die Trace-ID nur im HTTP Request existiert, aber nicht im Event Header, bricht die Kette. Deshalb muss bei Messaging darauf geachtet werden, Korrelation weiterzugeben.

Beispiel für einen Kafka Header:

java
package com.seb4u.demo.spring.order.infrastructure.messaging;

import org.apache.kafka.clients.producer.ProducerRecord;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

@Component
public class OrderEventRecordFactory {

    public ProducerRecord<String, String> createRecord(
            String topic,
            String key,
            String payload,
            String traceId,
            String eventType
    ) {
        ProducerRecord<String, String> record = new ProducerRecord<>(topic, key, payload);
        record.headers().add(class="str">"trace-id", traceId.getBytes(StandardCharsets.UTF_8));
        record.headers().add(class="str">"event-type", eventType.getBytes(StandardCharsets.UTF_8));
        record.headers().add(class="str">"producer", class="str">"order-service".getBytes(StandardCharsets.UTF_8));
        return record;
    }
}

Das ist kein Luxus. Ohne solche Header wird Fehleranalyse über HTTP und Messaging hinweg deutlich schwieriger.

---

9. Metriken: technische und fachliche Werte

Metriken sollten nicht nur Infrastrukturwerte zeigen. CPU, RAM und Pod-Restarts sind wichtig, aber sie erklären nicht, ob der fachliche Prozess funktioniert.

Für den Order Service brauchen wir zum Beispiel:

Vorlage / Textauszug

Technische Metriken:

  • HTTP Request Count
  • HTTP Error Rate
  • Request Duration
  • JVM Memory
  • CPU Usage
  • Database Connection Pool
  • Pod Restarts

Fachliche Metriken:

  • Anzahl erstellter Bestellungen
  • Anzahl abgelehnter Bestellungen
  • Anzahl offener Outbox Events
  • ältestes unveröffentlichtes Outbox Event
  • Anzahl fehlgeschlagener Publishing-Versuche
  • Anzahl doppelt erkannter Events

Fachliche Metriken sind oft wertvoller als reine Infrastrukturwerte. Wenn die CPU niedrig ist, aber keine Bestellungen mehr verarbeitet werden, ist das System trotzdem gestört.

---

10. Eigene Metriken in Spring Boot

Mit Micrometer können eigene Metriken erzeugt werden.

java
package com.seb4u.demo.spring.order.observability;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicLong;

@Component
public class OrderMetrics {

    private final Counter createdOrders;
    private final Counter rejectedOrders;
    private final Counter outboxPublishFailures;
    private final AtomicLong unpublishedOutboxEvents = new AtomicLong(0);

    public OrderMetrics(MeterRegistry meterRegistry) {
        this.createdOrders = Counter.builder(class="str">"orders_created_total")
                .description(class="str">"Number of successfully created orders")
                .tag(class="str">"service", class="str">"order-service")
                .register(meterRegistry);

        this.rejectedOrders = Counter.builder(class="str">"orders_rejected_total")
                .description(class="str">"Number of rejected order requests")
                .tag(class="str">"service", class="str">"order-service")
                .register(meterRegistry);

        this.outboxPublishFailures = Counter.builder(class="str">"outbox_publish_failures_total")
                .description(class="str">"Number of failed outbox publish attempts")
                .tag(class="str">"service", class="str">"order-service")
                .register(meterRegistry);

        Gauge.builder(class="str">"outbox_unpublished_events", unpublishedOutboxEvents, AtomicLong::get)
                .description(class="str">"Number of outbox events not yet published")
                .tag(class="str">"service", class="str">"order-service")
                .register(meterRegistry);
    }

    public void orderCreated() {
        createdOrders.increment();
    }

    public void orderRejected() {
        rejectedOrders.increment();
    }

    public void outboxPublishFailed() {
        outboxPublishFailures.increment();
    }

    public void updateUnpublishedOutboxEvents(long value) {
        unpublishedOutboxEvents.set(value);
    }
}

Die Metrik outbox_unpublished_events kann später direkt für Alerts verwendet werden. Wenn sie längere Zeit steigt, hängt die Event-Verarbeitung.

---

11. ServiceMonitor für Prometheus

In OpenShift mit Prometheus Operator kann ein ServiceMonitor beschreiben, wie Prometheus Metriken abholt.

yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: order-service
  namespace: order-prod
  labels:
    team: order
spec:
  selector:
    matchLabels:
      app: order-service
  namespaceSelector:
    matchNames:
      - order-prod
  endpoints:
    - port: http
      path: /actuator/prometheus
      interval: 30s
      scrapeTimeout: 10s

Dazu muss der Kubernetes Service einen passenden Portnamen haben.

yaml
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: order-prod
  labels:
    app: order-service
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 8080
      targetPort: 8080

Ein häufiger Fehler ist, dass ServiceMonitor und Service nicht zusammenpassen. Dann läuft die Anwendung, aber Prometheus sammelt keine Metriken.

---

12. Alerting: gute und schlechte Alarme

Ein Alarm ist nur nützlich, wenn er eine klare Handlung auslöst. Schlechte Alarme erzeugen Lärm. Gute Alarme zeigen einen Zustand, der Aufmerksamkeit braucht, und verweisen auf ein Runbook.

Schlechter Alarm:

Vorlage / Textauszug

CPU über 70 Prozent.

Warum schlecht? Eine hohe CPU kann normal sein. Ohne Kontext weiß niemand, ob eine Handlung nötig ist.

Besser:

Vorlage / Textauszug

Order Service Error Rate über 5 Prozent für 10 Minuten.

Runbook: docs/runbooks/order-service-errors.md

Noch besser:

Vorlage / Textauszug

Outbox unveröffentlichte Events steigen seit 15 Minuten und ältestes Event ist älter als 10 Minuten.

Wahrscheinliche Ursache: Broker nicht erreichbar oder Publisher hängt.

Runbook: docs/runbooks/outbox-stuck.md

Alerts sollten fachlich priorisiert werden. Nicht jeder technische Ausschlag ist ein Incident.

---

13. PrometheusRule für Outbox

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: order-service-alerts
  namespace: order-prod
  labels:
    team: order
spec:
  groups:
    - name: order-service.rules
      rules:
        - alert: OrderOutboxBacklogGrowing
          expr: outbox_unpublished_events{service=class="str">"order-service"} > 100
          for: 15m
          labels:
            severity: warning
            team: order
          annotations:
            summary: class="str">"Order Service outbox backlog is growing"
            description: class="str">"More than 100 unpublished outbox events for at least 15 minutes. Check broker connectivity and publisher logs."
            runbook_url: class="str">"docs/runbooks/outbox-stuck.md"

        - alert: OrderServiceHighErrorRate
          expr: |
            sum(rate(http_server_requests_seconds_count{application=class="str">"order-service",status=~class="str">"5.."}[5m]))
            /
            sum(rate(http_server_requests_seconds_count{application=class="str">"order-service"}[5m]))
            > 0.05
          for: 10m
          labels:
            severity: critical
            team: order
          annotations:
            summary: class="str">"Order Service has high 5xx error rate"
            description: class="str">"More than 5% of requests fail with 5xx responses for 10 minutes."
            runbook_url: class="str">"docs/runbooks/order-service-errors.md"

Diese Regeln sind besser als reine CPU-Alarme, weil sie näher am Serviceverhalten sind.

---

14. Dashboards: vom Management bis zum Entwickler

Ein Dashboard sollte nicht nur hübsch aussehen. Es muss Entscheidungen unterstützen.

Für den Order Service brauchen unterschiedliche Zielgruppen unterschiedliche Sichten.

Eine technische Sicht zeigt:

Vorlage / Textauszug

Request Rate

Error Rate

Request Duration

Pod Restarts

CPU und Memory

Datenbankverbindungen

Broker-Verbindungsfehler

Eine fachliche Sicht zeigt:

Vorlage / Textauszug

Bestellungen pro Minute

abgelehnte Bestellungen

Outbox Backlog

Publishing-Latenz

Billing-Verarbeitungsverzug

Eine Betriebsübersicht zeigt:

Vorlage / Textauszug

aktuelle Version

letztes Deployment

aktive Alerts

SLO-Erfüllung

Top-Fehlercodes

abhängige Systeme

Dashboards sollten nicht mit zu vielen Panels überladen werden. Ein gutes Dashboard zeigt zuerst Symptome und erlaubt dann Drilldown.

---

15. SLI, SLO und SLA

Ein SLI ist ein messbarer Indikator. Ein SLO ist ein Zielwert. Ein SLA ist eine vertragliche Zusage.

Beispiel:

Vorlage / Textauszug

SLI: Anteil erfolgreicher Order API Requests

SLO: 99,5 Prozent der Requests sind innerhalb eines Monats erfolgreich

SLA: vertragliche Zusage gegenüber Kunden oder Partnern

Für interne Plattformen arbeitet man häufig zuerst mit SLOs, nicht sofort mit harten SLAs.

Mögliche SLOs im Lab:

Vorlage / Textauszug

Order API Availability: 99,5 Prozent pro Monat

Order API Latency: 95 Prozent der Requests unter 500 ms

Outbox Freshness: 99 Prozent der Events innerhalb von 2 Minuten veröffentlicht

Billing Processing Delay: 95 Prozent der Billing Events innerhalb von 5 Minuten verarbeitet

SLOs zwingen Teams, über messbare Qualität zu sprechen. Ohne SLO ist „läuft gut“ nur ein Gefühl.

---

16. Betrieb über Infrastrukturgrenzen hinweg

Das Lab ist hybrid. Deshalb müssen nicht nur Kubernetes-Services beobachtet werden. Auch VMs, Bare-Metal-Systeme und Cloud-Ressourcen gehören in das Betriebsbild.

Für OpenShift sammeln wir:

Vorlage / Textauszug

Pod Status

Deployments

Routes

ServiceMonitors

Resource Usage

Events

Container Logs

Für VMs sammeln wir:

Vorlage / Textauszug

CPU

RAM

Disk

systemd Services

Journal Logs

Patchstatus

Prozessstatus

Für Bare Metal sammeln wir zusätzlich:

Vorlage / Textauszug

Hardware Health

RAID Status

Disk I/O

Netzwerkinterfaces

Temperatur oder Hardwarealarme

Backup-Status

Für Cloud sammeln wir:

Vorlage / Textauszug

Object Storage Fehler

Bucket-Größe

API Errors

Kostenentwicklung

IAM Fehlversuche

Managed Database Metriken

Observability darf nicht an der Clustergrenze enden.

---

17. Node Exporter für VM und Bare Metal

Node Exporter kann Linux-Systemmetriken für Prometheus bereitstellen.

Beispiel systemd Unit:

ini
[Unit]
Description=Prometheus Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=:9100 \
  --collector.systemd \
  --collector.filesystem.mount-points-exclude=class="str">'^/(sys|proc|dev|run)($|/)'
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Dazu gehört eine Firewall-Regel. Prometheus darf den Node Exporter erreichen, aber nicht das ganze Netzwerk.

Vorlage / Textauszug

Quelle: Monitoring Zone

Ziel: VM oder Bare-Metal-Server

Port: 9100 TCP

Zweck: Node Exporter Scraping

Auch hier gilt: Observability braucht Netzwerkfreigaben und Dokumentation.

---

18. Logging für VM-Dienste

Ein File Processing Service auf einer VM schreibt möglicherweise in journald oder in lokale Dateien. Für Enterprise-Betrieb sollte er zentral angebunden werden.

Mögliche Wege:

Vorlage / Textauszug

Promtail liest journald und sendet an Loki.

Fluent Bit liest Logdateien und sendet an ein zentrales Backend.

rsyslog leitet systemnahe Logs weiter.

Beispiel Promtail-Konfiguration:

yaml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: https://loki.internal.alpine.example.com/loki/api/v1/push

scrape_configs:
  - job_name: journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
        host: file-worker-01
    relabel_configs:
      - source_labels: [class="str">'__journal__systemd_unit']
        target_label: class="str">'unit'
      - source_labels: [class="str">'__journal_priority_keyword']
        target_label: class="str">'level'

Dadurch können Fehler aus VM-Diensten zusammen mit OpenShift-Logs betrachtet werden.

---

19. OpenTelemetry

OpenTelemetry ist ein Standard für Telemetriedaten. Es hilft, Traces, Metriken und Logs einheitlicher zu erzeugen und weiterzuleiten.

Im Lab kann OpenTelemetry genutzt werden, um Service-zu-Service-Aufrufe sichtbar zu machen.

Beispiel für Spring Boot Konfiguration:

yaml
management:
  tracing:
    sampling:
      probability: 1.0
  otlp:
    tracing:
      endpoint: http://otel-collector.observability.svc:4318/v1/traces

In Produktion wird Sampling oft niedriger gesetzt. Für Lern- und Testumgebungen kann 100 Prozent Sampling hilfreich sein, damit man alle Abläufe sieht.

OpenTelemetry ersetzt nicht automatisch gutes Logging oder gute Metriken. Es ergänzt sie.

---

20. OpenTelemetry Collector

Der Collector nimmt Telemetriedaten entgegen und leitet sie weiter.

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: otel-collector-config
  namespace: observability
data:
  collector.yaml: |
    receivers:
      otlp:
        protocols:
          grpc:
          http:

    processors:
      batch:

    exporters:
      otlp/tempo:
        endpoint: tempo.observability.svc:4317
        tls:
          insecure: true

    service:
      pipelines:
        traces:
          receivers: [otlp]
          processors: [batch]
          exporters: [otlp/tempo]

Der Vorteil: Anwendungen müssen nicht jedes Backend direkt kennen. Sie senden an den Collector. Der Betrieb kann später das Backend ändern oder zusätzliche Exporte ergänzen.

---

21. Incident Management

Ein Incident ist eine Störung mit Auswirkung. Nicht jeder Fehler ist ein Incident. Ein einzelner 400-Fehler durch falsche Benutzereingabe ist normal. Eine dauerhaft steigende 500-Fehlerquote ist ein Incident-Kandidat.

Ein Incident-Prozess klärt:

Vorlage / Textauszug

Wie wird die Störung erkannt?

Wer übernimmt die Koordination?

Welche Teams werden informiert?

Welche Systeme sind betroffen?

Wie wird der Schaden begrenzt?

Wie wird die Ursache analysiert?

Wie wird kommuniziert?

Wie wird nachbereitet?

Ohne Incident-Prozess reagieren Teams zufällig. Manche schauen Logs, andere ändern Konfiguration, jemand startet Pods neu, aber niemand koordiniert das Gesamtbild.

---

22. Runbook: Order Service hohe Fehlerquote

Vorlage / Textauszug

Runbook: Order Service hohe 5xx-Fehlerquote

Symptom

Alert `OrderServiceHighErrorRate` ist aktiv.

Sofort prüfen

oc get pods -n order-prod -l app=order-service

oc get events -n order-prod --sort-by=.lastTimestamp | tail -50

oc logs -n order-prod deployment/order-service --tail=200

Vorlage / Textauszug
Dashboard prüfen
  • Request Rate
  • Error Rate
  • p95 Latenz
  • Datenbank Connection Pool
  • Outbox Backlog
  • Pod Restarts
Abhängigkeiten prüfen

oc rsh -n order-prod deployment/order-service

nc -vz orders-db.internal.alpine.example.com 5432

nc -vz kafka.messaging.svc 9092

Vorlage / Textauszug
Typische Ursachen
  • Datenbank nicht erreichbar
  • Connection Pool erschöpft
  • Broker nicht erreichbar
  • neue Version enthält Fehler
  • falsches Secret nach Deployment
  • NetworkPolicy blockiert Egress
  • externe API antwortet langsam
Entscheidung: Rollback?

Rollback prüfen, wenn der Fehler direkt nach einem Deployment begonnen hat.

oc rollout history deployment/order-service -n order-prod

oc rollout undo deployment/order-service -n order-prod

Vorlage / Textauszug
Kommunikation

Bei Kundenauswirkung Incident-Kanal öffnen und Status alle 15 Minuten aktualisieren.

Nachbereitung
  • Ursache dokumentieren
  • Alert prüfen
  • Test ergänzen
  • Runbook verbessern

Dieses Runbook verbindet technische Prüfung, Entscheidung und Kommunikation.

---

23. Change und Release im Betrieb

Viele Incidents entstehen nach Änderungen. Deshalb muss Observability mit Release-Management verbunden sein.

Ein Dashboard sollte anzeigen:

Vorlage / Textauszug

Welche Version läuft?

Wann wurde sie deployt?

Welche Git-Commit-ID gehört dazu?

Welche Konfiguration wurde geändert?

Welche Alerts begannen nach dem Deployment?

Ein Service kann seine Build-Informationen über Actuator Info bereitstellen.

yaml
management:
  info:
    git:
      mode: full
    env:
      enabled: true
info:
  application:
    name: order-service
    owner: team-order

Im Build sollte die Git-Commit-ID ins Artefakt geschrieben werden. Dadurch kann Betrieb später genau erkennen, welche Version läuft.

---

24. Fehlerbudget

Ein Fehlerbudget beschreibt, wie viel Unzuverlässigkeit innerhalb eines SLO toleriert wird. Wenn ein Service ein SLO von 99,5 Prozent hat, darf er im Monat eine bestimmte Zeit oder Fehlerquote verbrauchen.

Das Fehlerbudget hilft bei Entscheidungen:

Vorlage / Textauszug

Wenn viel Budget übrig ist, können Releases schneller erfolgen.

Wenn Budget fast verbraucht ist, müssen Stabilität und Fehlerbehebung Vorrang haben.

Das ist besonders wichtig, wenn Entwicklung und Betrieb unterschiedliche Ziele haben. Entwicklung möchte Features liefern. Betrieb möchte Stabilität. Fehlerbudget verbindet beide Sichtweisen über messbare Qualität.

---

25. Capacity Management

Betrieb bedeutet nicht nur Störungen beheben. Betrieb bedeutet auch vorausplanen.

Capacity Management fragt:

Vorlage / Textauszug

Reichen CPU und Memory für den nächsten Monat?

Wächst die Datenbank schneller als erwartet?

Steigt die Outbox-Latenz bei Last?

Wann wird Storage knapp?

Wie viele Kafka Partitionen werden gebraucht?

Wie viele Pods braucht der Billing Service bei Monatsabschluss?

Für das Lab kann man Lastprofile definieren:

Vorlage / Textauszug

Normalbetrieb: 20 Bestellungen pro Minute

Kampagne: 200 Bestellungen pro Minute

Monatsabschluss: hohe Billing-Last

Partnerimport: große Datei nachts um 02:00

Diese Profile helfen, Tests und Alerts realistisch zu gestalten.

---

26. Lasttest als Betriebsartefakt

Ein Lasttest ist nicht nur Entwicklung. Er ist ein Betriebsartefakt, weil er zeigt, wie sich das System unter Druck verhält.

Beispiel mit k6:

javascript
import http from class="str">'k6/http';
import { check, sleep } from class="str">'k6';

export const options = {
  stages: [
    { duration: class="str">'2m', target: 20 },
    { duration: class="str">'5m', target: 100 },
    { duration: class="str">'2m', target: 0 }
  ],
  thresholds: {
    http_req_failed: [class="str">'rate<0.02'],
    http_req_duration: [class="str">'p(95)<500']
  }
};

export default function () {
  const payload = JSON.stringify({
    customerId: class="str">'C-10001',
    items: [
      { productId: class="str">'P-100', quantity: 1, unitPrice: 29.90 }
    ]
  });

  const params = {
    headers: {
      class="str">'Content-Type': class="str">'application/json',
      class="str">'Authorization': `Bearer ${__ENV.ACCESS_TOKEN}`
    }
  };

  const response = http.post(`${__ENV.API_URL}/orders`, payload, params);

  check(response, {
    class="str">'status is 201': (r) => r.status === 201,
    class="str">'response time below 500ms': (r) => r.timings.duration < 500
  });

  sleep(1);
}

Der Lasttest gehört in Git. Ergebnisse sollten mit Version, Umgebung und Konfiguration dokumentiert werden.

---

27. Betrieb von Batch und CronJobs

Nicht alle Workloads sind APIs. Reporting, File Processing und Abgleichjobs laufen oft zeitgesteuert.

Bei CronJobs sind andere Fragen wichtig:

Vorlage / Textauszug

Ist der Job gestartet?

Ist er erfolgreich beendet?

Wie lange hat er gedauert?

Hat er Daten verarbeitet?

Wurde er doppelt gestartet?

Was passiert bei Fehlern?

Gibt es ein Retry-Konzept?

Beispiel Kubernetes CronJob:

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report-export
  namespace: reporting-prod
spec:
  schedule: class="str">"0 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: report-export
              image: registry.example.com/alpine/report-export:1.2.0
              envFrom:
                - secretRef:
                    name: reporting-db-secret

concurrencyPolicy: Forbid verhindert, dass ein neuer Lauf startet, während der alte noch läuft. Das ist wichtig für viele Batchprozesse.

---

28. Runbook: CronJob fehlgeschlagen

Vorlage / Textauszug

Runbook: Nightly Report Export fehlgeschlagen

Symptom

CronJob `nightly-report-export` hat den letzten Lauf nicht erfolgreich beendet.

Prüfung

oc get cronjob -n reporting-prod nightly-report-export

oc get jobs -n reporting-prod --sort-by=.metadata.creationTimestamp

oc logs -n reporting-prod job/<job-name>

Vorlage / Textauszug
Häufige Ursachen
  • Datenbank nicht erreichbar
  • Export-Bucket nicht erreichbar
  • Secret abgelaufen
  • zu wenig Speicher
  • alter Job läuft noch
  • Datenformat unerwartet
Wiederholung

Manuellen Job aus CronJob erzeugen:

oc create job -n reporting-prod manual-report-export-$(date +%Y%m%d%H%M) \

--from=cronjob/nightly-report-export

Vorlage / Textauszug
Nachweis
  • Exportdatei im Object Storage prüfen
  • Logeintrag mit verarbeiteter Datensatzanzahl prüfen
  • Dashboard für Laufzeit und Erfolg prüfen

Batchbetrieb braucht genauso klare Runbooks wie APIs.

---

29. Postmortem statt Schuldsuche

Nach einem Incident sollte ein Postmortem erstellt werden. Das Ziel ist Lernen, nicht Schuldzuweisung.

Ein gutes Postmortem enthält:

Vorlage / Textauszug

Was ist passiert?

Wann begann die Störung?

Wie wurde sie erkannt?

Welche Kunden oder Systeme waren betroffen?

Was war die technische Ursache?

Was hat gut funktioniert?

Was hat nicht funktioniert?

Welche Maßnahmen verhindern Wiederholung?

Welche Alerts oder Runbooks müssen verbessert werden?

Wichtig ist, konkrete Verbesserungen abzuleiten. Ein Postmortem ohne Maßnahmen ist nur ein Bericht.

---

30. Beispiel-Postmortem Kurzfassung

Vorlage / Textauszug

Postmortem: Outbox Backlog im Order Service

Zusammenfassung

Am 2026-07-05 stieg der Outbox Backlog im Order Service über 45 Minuten kontinuierlich an. Bestellungen wurden gespeichert, aber Billing erhielt Events verzögert.

Auswirkung

Kunden konnten Bestellungen erstellen. Rechnungsinformationen wurden verzögert angezeigt.

Ursache

Eine neue NetworkPolicy erlaubte dem Order Service den Zugriff auf PostgreSQL, aber nicht mehr auf Kafka.

Erkennung

Der Alert `OrderOutboxBacklogGrowing` wurde nach 15 Minuten ausgelöst.

Was gut funktioniert hat
  • Bestellungserstellung blieb möglich.
  • Outbox verhinderte Datenverlust.
  • Alert zeigte die Ursache schnell in Richtung Event-Verarbeitung.
Was nicht gut funktioniert hat
  • NetworkPolicy wurde ohne Integrationstest deployt.
  • Runbook enthielt keinen direkten Hinweis auf NetworkPolicy-Prüfung.
Maßnahmen
  • Integrationstest für Kafka-Verbindung ergänzen.
  • NetworkPolicy Review in Pull Request Checkliste aufnehmen.
  • Runbook `outbox-stuck.md` erweitern.
  • Dashboard um Kafka-Verbindungsstatus ergänzen.

Dieses Beispiel zeigt, dass ein technischer Fehler in Netzwerkpolicy, Deploymentprozess, Testabdeckung und Runbook sichtbar wird.

---

31. Übung: Observability-Konzept für Billing Service

Erstelle ein Observability-Konzept für den Billing Service.

Es soll enthalten:

Vorlage / Textauszug
  1. Welche Logs braucht der Service?
  2. Welche technischen Metriken braucht er?
  3. Welche fachlichen Metriken braucht er?
  4. Welche Traces sind wichtig?
  5. Welche Alerts sind sinnvoll?
  6. Welche Dashboards braucht Betrieb?
  7. Welche Runbooks müssen existieren?
  8. Welche Abhängigkeiten müssen überwacht werden?

Denke daran: Billing ist eventgetrieben. Es reicht nicht, HTTP Requests zu betrachten. Der Service muss Broker, Event-Lag, Idempotenz, Datenbank und fachliche Rechnungsverarbeitung sichtbar machen.

---

32. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

Der Billing Service schreibt strukturierte JSON Logs mit traceId, eventId, orderId, invoiceId, eventType und processingResult.

Technische Metriken sind JVM Memory, CPU, Pod Restarts, Datenbank Connection Pool, Kafka Consumer Lag und Fehlerquote beim Speichern von Rechnungen.

Fachliche Metriken sind erzeugte Rechnungen, abgelehnte Events, doppelt erkannte Events, fehlgeschlagene Rechnungserstellung und durchschnittliche Zeit zwischen OrderCreatedEvent und InvoiceCreated.

Traces sollen den Weg vom konsumierten Event bis zur gespeicherten Rechnung zeigen. Die traceId aus dem Event Header wird übernommen.

Sinnvolle Alerts sind Consumer Lag zu hoch, Billing Error Rate zu hoch, Datenbank nicht erreichbar, doppelte Events steigen stark und keine Events verarbeitet trotz eingehender Bestellungen.

Dashboards zeigen Event Throughput, Lag, Fehlerquote, Rechnungen pro Zeitraum, p95 Verarbeitungsdauer und aktuelle Version.

Runbooks braucht es für Broker nicht erreichbar, Consumer Lag, Datenbankfehler, fehlerhafte Event Payload und Rollback nach fehlerhaftem Deployment.

Diese Lösung zeigt den Unterschied zwischen API-Monitoring und Event-Monitoring.

---

33. Merksätze

Vorlage / Textauszug

Observability ist Teil der Architektur, nicht Nacharbeit.

Logs erzählen, was passiert ist. Metriken zeigen Trends. Traces verbinden Schritte.

Ein Service ist nicht gesund, nur weil der Prozess läuft.

Fachliche Metriken sind oft wichtiger als CPU und RAM.

Ein Alert ohne Runbook erzeugt Stress statt Betriebssicherheit.

Observability muss Cloud, OpenShift, VM und Bare Metal gemeinsam betrachten.

Postmortems dienen dem Lernen, nicht der Schuldzuweisung.

Backup, Restore und Disaster Recovery

Backup- und Restore-Kette
Kompakte Themenübersicht
ProduktivdatenBackup JobpgDumpVeleroBackup StorageS3 / ResticRecovery RepoRunbooksRestoreTestRTO/RPOBackupDokumentationWiederanlaufValidierung

1. Warum Backup nicht erst am Ende geplant wird

Backup wird in vielen Projekten zu spät betrachtet. Eine Anwendung wird entwickelt, deployt, überwacht und erst danach fragt jemand, wie die Daten wiederhergestellt werden können. In einem Enterprise-Projekt ist das falsch. Backup, Restore und Disaster Recovery gehören von Anfang an zur Architektur.

Der Grund ist einfach: Eine Plattform ist nur so belastbar wie ihre Wiederherstellung. Es reicht nicht, dass ein Service heute läuft. Entscheidend ist, ob er nach einem Fehlerzustand wieder kontrolliert in einen brauchbaren Zustand gebracht werden kann.

Fehler können sehr unterschiedlich sein:

Vorlage / Textauszug

Ein Pod stürzt ab.

Eine Datenbanktabelle wird versehentlich gelöscht.

Ein Deployment enthält einen schweren Fehler.

Ein Secret wurde falsch rotiert.

Ein Storage Volume ist beschädigt.

Ein Rechenzentrum fällt aus.

Ein Cloud-Bucket wurde falsch konfiguriert.

Ein Ransomware-Angriff verändert Daten.

Ein Batchjob verarbeitet fehlerhafte Partnerdateien.

Nicht jeder Fehler braucht dieselbe Antwort. Ein Pod-Neustart ist kein Disaster Recovery. Eine versehentlich gelöschte Datenbank ist ein Restore-Fall. Der Ausfall eines Standorts ist ein Disaster-Recovery-Fall. Eine fehlerhafte fachliche Verarbeitung kann wiederum einen fachlichen Korrekturprozess brauchen, nicht nur ein technisches Backup.

Deshalb trennt dieses Kapitel drei Begriffe:

Vorlage / Textauszug

Backup: Daten oder Konfigurationen werden regelmäßig gesichert.

Restore: eine Sicherung wird zurückgespielt.

Disaster Recovery: ein größerer Ausfall wird nach vorbereitetem Plan bewältigt.

Eine moderne Hybrid-Plattform braucht alle drei Ebenen.

---

2. RPO und RTO

RPO/RTO verstehen
Kompakte Lernhilfe
StörungRPOwie viel Datenverlust?RTOwie lange Ausfall?RunbookReihenfolge

Zwei Begriffe sind für Backup und Disaster Recovery besonders wichtig.

RPO bedeutet Recovery Point Objective. Es beschreibt, wie viel Datenverlust maximal akzeptiert wird.

RTO bedeutet Recovery Time Objective. Es beschreibt, wie lange die Wiederherstellung maximal dauern darf.

Beispiel:

Vorlage / Textauszug

RPO: 15 Minuten

RTO: 2 Stunden

Das bedeutet: Im Fehlerfall darf das Unternehmen höchstens Daten aus den letzten 15 Minuten verlieren, und der Dienst soll innerhalb von zwei Stunden wieder nutzbar sein.

Diese Werte sind keine technischen Spielereien. Sie sind Geschäftsentscheidungen. Ein Customer Portal darf vielleicht 30 Minuten ausfallen. Eine Zahlungsabrechnung darf eventuell länger pausieren, darf aber keine Daten verlieren. Ein Reporting-System kann vielleicht einen Tag später wiederhergestellt werden. Ein Identity-System wie Keycloak ist kritischer, weil ohne Login viele andere Systeme nicht nutzbar sind.

Für das Lab definieren wir beispielhaft:

Vorlage / Textauszug

Customer Portal:

RPO: nicht relevant für statisches Frontend

RTO: 1 Stunde

Order Service Datenbank:

RPO: 15 Minuten

RTO: 2 Stunden

Billing Service Datenbank:

RPO: 15 Minuten

RTO: 4 Stunden

Reporting Daten:

RPO: 24 Stunden

RTO: 24 Stunden

Keycloak:

RPO: 1 Stunde

RTO: 2 Stunden

Object Storage Partnerdateien:

RPO: 0 bis 1 Stunde je nach Versionierung

RTO: 4 Stunden

Diese Werte sind nur Beispiele. Wichtig ist die Denkweise: Nicht alle Systeme bekommen automatisch dieselben Ziele.

---

3. Was überhaupt gesichert werden muss

Backup bedeutet nicht nur Datenbankdump. In einer Hybrid-Plattform gibt es viele sicherungswürdige Artefakte.

Gesichert werden müssen unter anderem:

Vorlage / Textauszug

Datenbanken

Object Storage Buckets

Persistent Volumes

Git-Repositories

Kubernetes/OpenShift Manifeste

Secrets oder Secret-Referenzen

Keycloak Realms und Clients

CI/CD-Konfigurationen

Argo-CD-Anwendungen

VM-Konfigurationen

Bare-Metal-Konfigurationen

Zertifikate und CA-Informationen

Runbooks und ADRs

Monitoring Dashboards

Alerting-Regeln

Dabei ist wichtig: Nicht alles wird gleich gesichert. Git-Repositories enthalten deklarative Plattformdefinitionen. Sie werden über Git selbst gesichert. Datenbanken brauchen konsistente Datenbanksicherungen. Object Storage braucht Versionierung, Replikation oder Snapshots. Kubernetes-Secrets dürfen nicht einfach unverschlüsselt exportiert werden.

Ein häufiger Fehler ist, nur die Datenbank zu sichern und die Plattformdefinitionen zu vergessen. Dann hat man zwar Daten, aber keine saubere Möglichkeit, die Umgebung wieder aufzubauen.

Ein anderer Fehler ist, nur Kubernetes-Ressourcen zu exportieren und zu glauben, damit sei die Anwendung vollständig gesichert. Persistent Volumes, externe Datenbanken und Cloud-Ressourcen fehlen dann oft.

---

4. Backup-Strategie für PostgreSQL

PostgreSQL ist im Lab zentral. Order Service und Billing Service verwenden eigene Datenbanken. Reporting kann Daten aus beiden Bereichen lesen oder eigene Kopien verwenden.

Für produktionsnahe PostgreSQL-Systeme reicht ein gelegentlicher Dump nicht aus. Eine bessere Strategie kombiniert:

Vorlage / Textauszug

regelmäßige Full Backups

WAL-Archivierung

Restore-Tests

Monitoring der Backup-Jobs

getrennte Backup-Aufbewahrung

Dokumentation der Wiederherstellung

WAL steht für Write-Ahead Log. Über WAL-Archivierung kann PostgreSQL Änderungen zwischen Full Backups nachvollziehen. Dadurch wird Point-in-Time-Recovery möglich.

Point-in-Time-Recovery bedeutet: Die Datenbank kann auf einen bestimmten Zeitpunkt zurückgesetzt werden, zum Beispiel kurz vor einer fehlerhaften Migration.

Ein einfaches Backup-Konzept für Orders könnte so aussehen:

Vorlage / Textauszug

Täglich 02:00: Full Backup

Fortlaufend: WAL-Archivierung alle 5 Minuten oder kontinuierlich

Aufbewahrung: 14 Tage tägliche Backups, 3 Monate Wochenbackups

Restore-Test: monatlich in isolierter Umgebung

Monitoring: Alert bei fehlgeschlagenem Backup

Wichtig: Ein Backup, das nie zurückgespielt wurde, ist nur eine Hoffnung.

---

5. Beispiel: PostgreSQL Backup-Skript

Für ein Lab kann ein einfaches Skript genutzt werden. In Produktion würde man häufig spezialisierte Werkzeuge verwenden, aber das Prinzip bleibt gleich.

bash
#!/usr/bin/env bash
set -euo pipefail

APP_NAME=class="str">"orders-db"
BACKUP_DIR=class="str">"/backup/postgresql/orders"
DATE=class="str">"$(date +%Y%m%d-%H%M%S)"
FILE=class="str">"${BACKUP_DIR}/${APP_NAME}-${DATE}.dump"
LOG_FILE=class="str">"${BACKUP_DIR}/${APP_NAME}-${DATE}.log"

mkdir -p class="str">"${BACKUP_DIR}"

export PGHOST=class="str">"${PGHOST:-orders-db.internal.alpine.example.com}"
export PGPORT=class="str">"${PGPORT:-5432}"
export PGDATABASE=class="str">"${PGDATABASE:-orders}"
export PGUSER=class="str">"${PGUSER:-backup_user}"

{
  echo class="str">"Starting backup for ${PGDATABASE} at ${DATE}"

  pg_dump \
    --format=custom \
    --verbose \
    --file=class="str">"${FILE}" \
    class="str">"${PGDATABASE}"

  sha256sum class="str">"${FILE}" > class="str">"${FILE}.sha256"

  echo class="str">"Backup completed: ${FILE}"
  echo class="str">"Checksum written: ${FILE}.sha256"
} 2>&1 | tee class="str">"${LOG_FILE}"

Dieses Skript erzeugt einen komprimierten Dump im Custom-Format und zusätzlich eine Prüfsumme. Die Prüfsumme hilft zu erkennen, ob die Datei später beschädigt wurde.

Für Enterprise-Betrieb fehlen hier noch Dinge wie Secret Handling, zentrale Logs, Aufbewahrungsregeln, Upload in ein Backup-Repository und Monitoring. Trotzdem zeigt das Beispiel die Grundlage.

---

6. Restore ist wichtiger als Backup

Viele Teams können ein Backup erzeugen. Weniger Teams können zuverlässig wiederherstellen.

Restore muss geübt werden. Ein Restore-Test beantwortet Fragen wie:

Vorlage / Textauszug

Ist die Backup-Datei lesbar?

Ist die PostgreSQL-Version kompatibel?

Sind Rollen und Berechtigungen vorhanden?

Sind Extensions installiert?

Sind Secrets verfügbar?

Wie lange dauert der Restore?

Welche Anwendung muss gestoppt werden?

Wie wird geprüft, ob die Daten korrekt sind?

Ein Restore-Test sollte nicht auf Produktion durchgeführt werden. Er läuft in einer isolierten Umgebung, zum Beispiel in einem Test-Namespace oder auf einer separaten VM.

---

7. Beispiel: PostgreSQL Restore in Testumgebung

bash
#!/usr/bin/env bash
set -euo pipefail

BACKUP_FILE=class="str">"$1"
TARGET_DB=class="str">"orders_restore_test"

if [[ ! -f class="str">"${BACKUP_FILE}" ]]; then
  echo class="str">"Backup file not found: ${BACKUP_FILE}" >&2
  exit 1
fi

createdb class="str">"${TARGET_DB}" || true

pg_restore \
  --clean \
  --if-exists \
  --no-owner \
  --dbname=class="str">"${TARGET_DB}" \
  class="str">"${BACKUP_FILE}"

psql class="str">"${TARGET_DB}" -c class="str">"SELECT count(*) AS orders_count FROM orders;"
psql class="str">"${TARGET_DB}" -c class="str">"SELECT count(*) AS outbox_count FROM outbox_events;"

Nach dem Restore reicht es nicht, dass der Befehl erfolgreich war. Es müssen fachliche Prüfungen erfolgen. Zum Beispiel: Anzahl der Bestellungen, Statusverteilung, offene Outbox Events und Datenkonsistenz zwischen Orders und Order Items.

---

8. Persistent Volumes in OpenShift

OpenShift-Anwendungen können Persistent Volumes verwenden. Dabei muss man verstehen, was gesichert wird.

Ein Deployment selbst ist deklarativ und liegt idealerweise in Git. Ein Persistent Volume enthält Laufzeitdaten. Wenn diese Daten wichtig sind, braucht das Volume eine Backup-Strategie.

Beispiele für persistente Daten im Cluster:

Vorlage / Textauszug

hochgeladene Dateien

temporäre Verarbeitungsstände

lokale Datenbanken in Lab-Umgebungen

Queue-Daten bei bestimmten Broker-Setups

Konfigurationsdaten einzelner Plattformdienste

In produktionsnahen Architekturen sollte man kritisch prüfen, ob wichtige Daten wirklich nur in einem Kubernetes-Volume liegen sollen. Häufig ist eine externe Datenbank oder Object Storage besser geeignet.

Wenn Persistent Volumes verwendet werden, muss klar sein:

Vorlage / Textauszug

Welcher StorageClass gehört das Volume an?

Gibt es Snapshots?

Wie lange werden Snapshots gehalten?

Kann ein Snapshot in einem anderen Namespace wiederhergestellt werden?

Ist das Volume ReadWriteOnce oder ReadWriteMany?

Was passiert bei Node-Ausfall?

---

9. VolumeSnapshot Beispiel

Wenn der Cluster VolumeSnapshots unterstützt, kann ein Snapshot so aussehen:

yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: reporting-data-snapshot-2026-07-05
  namespace: reporting-prod
spec:
  volumeSnapshotClassName: csi-snapshot-class
  source:
    persistentVolumeClaimName: reporting-data

Ein Snapshot ist aber kein vollständiges Backup-Konzept. Er liegt oft im selben Storage-System. Wenn dieses Storage-System ausfällt, ist auch der Snapshot betroffen. Für Disaster Recovery braucht man zusätzliche Kopien oder Replikation in eine andere Zone oder einen anderen Standort.

---

10. Object Storage und Versionierung

Object Storage eignet sich sehr gut für Partnerdateien, Exporte, Reports und Backup-Artefakte. Im Lab kann Object Storage durch Cloud-S3 oder MinIO bereitgestellt werden.

Für wichtige Buckets sollten folgende Funktionen geprüft werden:

Vorlage / Textauszug

Versionierung

Server-side Encryption

Lifecycle-Regeln

Object Lock oder Retention

Zugriffsprotokollierung

Replikation

separate technische Benutzer

Versionierung schützt vor versehentlichem Überschreiben. Lifecycle-Regeln verschieben alte Objekte in günstigere Speicherklassen oder löschen temporäre Dateien nach definierter Zeit. Object Lock kann vor absichtlichem oder versehentlichem Löschen schützen, ist aber organisatorisch sorgfältig zu planen.

Beispiel für Bucket-Regeln:

Vorlage / Textauszug

incoming-partner-files:

Versionierung: aktiv

Retention: 30 Tage

Lifecycle: nach 90 Tagen archivieren

Zugriff: nur File Processing und Integration Team

Logging: aktiv

processed-partner-files:

Versionierung: aktiv

Retention: 180 Tage

Lifecycle: nach 365 Tagen löschen oder archivieren

Zugriff: Reporting und File Processing

backup-artifacts:

Versionierung: aktiv

Object Lock: aktiv

Retention: 90 Tage unveränderbar

Zugriff: Backup-System, kein normaler App-Zugriff

---

11. Beispiel: MinIO Client für Backup-Kopie

bash
#!/usr/bin/env bash
set -euo pipefail

BACKUP_FILE=class="str">"$1"
BUCKET=class="str">"backup-artifacts/postgresql/orders"
ALIAS=class="str">"labminio"

if [[ ! -f class="str">"${BACKUP_FILE}" ]]; then
  echo class="str">"Backup file not found: ${BACKUP_FILE}" >&2
  exit 1
fi

mc cp class="str">"${BACKUP_FILE}" class="str">"${ALIAS}/${BUCKET}/"
mc cp class="str">"${BACKUP_FILE}.sha256" class="str">"${ALIAS}/${BUCKET}/"

mc ls class="str">"${ALIAS}/${BUCKET}/"

Die Kopie in Object Storage ist nützlich, weil sie Backup-Dateien von der Datenbankmaschine trennt. Wenn die Datenbankmaschine verloren geht, liegt das Backup nicht nur lokal auf demselben System.

---

12. Keycloak Backup

Keycloak ist kritisch, weil viele Anwendungen von Login und Token-Ausstellung abhängen. Ohne Keycloak funktionieren Customer Portal, Admin Portal und API-Zugriffe nicht korrekt.

Bei Keycloak müssen zwei Dinge betrachtet werden:

Vorlage / Textauszug

Keycloak-Datenbank

Realm-Konfiguration

Wenn Keycloak eine PostgreSQL-Datenbank verwendet, wird diese wie andere PostgreSQL-Datenbanken gesichert. Zusätzlich kann die Realm-Konfiguration exportiert werden, damit Clients, Rollen, Gruppen und Mappings nachvollziehbar bleiben.

Ein Realm-Export ist kein Ersatz für ein konsistentes Datenbankbackup, aber ein wichtiger zusätzlicher Nachweis.

Beispiel:

bash
/opt/keycloak/bin/kc.sh export \
  --realm alpine \
  --file /backup/keycloak/alpine-realm-$(date +%Y%m%d).json

In einem GitOps-orientierten Projekt sollten Keycloak-Clients und Rollen möglichst deklarativ beschrieben werden. Dann ist ein Realm nicht nur ein Zustand in einer Oberfläche, sondern Teil der Plattformdefinition.

---

13. Git als Wiederherstellungsquelle

Git ist nicht nur für Anwendungscode wichtig. Git enthält im Zielbild auch:

Vorlage / Textauszug

Kubernetes-Manifeste

Helm-Werte

Kustomize-Overlays

Argo-CD-Anwendungen

ADRs

Runbooks

CI/CD-Pipelines

Dokumentation

Security Policies

Wenn ein Cluster neu aufgebaut werden muss, ist Git die wichtigste Quelle für den gewünschten Zustand.

Aber Git allein reicht nicht. Git enthält nicht automatisch Datenbanken, Object Storage Inhalte oder dynamische Secrets. Deshalb muss klar getrennt werden:

Vorlage / Textauszug

Git stellt Struktur und gewünschte Konfiguration wieder her.

Backups stellen Daten wieder her.

Secret Management stellt sensible Werte kontrolliert wieder bereit.

Ein Disaster-Recovery-Plan muss alle drei Ebenen verbinden.

---

14. Argo CD und GitOps Recovery

Mit Argo CD kann ein Cluster nach einem Ausfall teilweise wieder aufgebaut werden, wenn die Plattformdefinitionen in Git liegen.

Ablauf:

Vorlage / Textauszug

Neuer Cluster oder neuer Namespace wird bereitgestellt.

Argo CD wird installiert.

Argo-CD-Applications werden aus Git geladen.

Namespaces, Deployments, Services, Routes und Policies werden synchronisiert.

Secrets werden über External Secrets oder Vault eingebunden.

Daten werden aus Backups wiederhergestellt.

Anwendungen werden schrittweise aktiviert.

Das ist wesentlich kontrollierter als manuelle Wiederherstellung per Copy-and-Paste.

Beispiel einer Argo-CD-Application:

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: openshift-gitops
spec:
  project: production
  source:
    repoURL: https://git.example.com/alpine/platform-config.git
    targetRevision: main
    path: platform/openshift/apps/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

Für Recovery ist wichtig, prune vorsichtig zu verwenden. In einer Störung will man nicht versehentlich Ressourcen löschen, die noch analysiert werden müssen.

---

15. Disaster-Recovery-Szenarien

DR-Wiederanlauf kompakt
Kompakte Lernhilfe
AusfallRestore DBRestore ConfigGit / SecretsApp StartOrder / BillingValidierungSmoke Test1234

Disaster Recovery beginnt mit Szenarien. Ohne Szenarien bleibt DR abstrakt.

Für das Lab definieren wir fünf Szenarien.

Szenario 1: Einzelner Pod fällt aus

Das ist kein DR-Fall. Kubernetes oder OpenShift startet den Pod neu. Wichtig sind Liveness, Readiness und ausreichende Replikas.

Szenario 2: Namespace wurde fehlerhaft verändert

Hier helfen GitOps, Rollback und gezielte Wiederherstellung von Ressourcen. Daten müssen meist nicht aus Backup wiederhergestellt werden.

Szenario 3: Datenbank wurde beschädigt

Das ist ein Restore-Fall. Man benötigt Datenbankbackup, WAL-Archivierung, Restore-Prozess und fachliche Validierung.

Szenario 4: OpenShift Cluster ist verloren

Das ist ein großer Plattform-Recovery-Fall. Man braucht Cluster-Bootstrap, GitOps-Konfiguration, Secret-Wiederherstellung, Storage-Strategie und Datenbank-/Object-Storage-Zugriff.

Szenario 5: Standort oder Cloud-Region fällt aus

Das ist Disaster Recovery im engeren Sinn. Man braucht alternative Infrastruktur, replizierte Daten, DNS-Umschaltung, klare Prioritäten und Kommunikationsplan.

---

16. Priorisierung im Wiederanlauf

Nach einem größeren Ausfall darf nicht alles gleichzeitig gestartet werden. Es braucht eine Reihenfolge.

Für das Lab könnte die Reihenfolge so aussehen:

Vorlage / Textauszug
  1. Netzwerk, DNS und Zertifikate prüfen
  2. Identity-Systeme bereitstellen: Keycloak, LDAP-Anbindung
  3. Datenbanken und Object Storage verfügbar machen
  4. Messaging bereitstellen: Kafka oder RabbitMQ
  5. Plattformdienste starten: GitOps, Monitoring, Logging
  6. Kernservices starten: Order Service, Billing Service
  7. Portale starten: Customer Portal, Admin Portal
  8. Reporting und File Processing aktivieren
  9. fachliche Validierung durchführen
  10. externe Kommunikation freigeben

Diese Reihenfolge ist wichtig. Ein Customer Portal ohne Identity und APIs bringt wenig. Ein Billing Service ohne Datenbank und Broker kann nicht korrekt arbeiten. Monitoring sollte früh verfügbar sein, damit der Wiederanlauf beobachtet werden kann.

---

17. Beispiel: DR-Runbook für Order Platform

Vorlage / Textauszug

Runbook: Disaster Recovery für Order Platform

Ziel

Wiederherstellung der Order Platform nach schwerem Plattformausfall.

Betroffene Komponenten
  • order-prod Namespace
  • Order Service
  • PostgreSQL Orders
  • Kafka Topic order.events
  • OpenShift Route api.alpine.example.com
  • Keycloak Client order-service
  • Monitoring Dashboard Order
Voraussetzungen
  • neuer oder funktionsfähiger OpenShift Cluster
  • Zugriff auf Git Repository platform-config
  • Zugriff auf Backup Repository
  • Zugriff auf Vault oder External Secrets Backend
  • DNS-Änderungen möglich
Schritt 1: Plattformbasis prüfen

oc get nodes

oc get clusterversion

oc get co

Vorlage / Textauszug
Schritt 2: GitOps prüfen

oc get pods -n openshift-gitops

oc get applications -n openshift-gitops

Vorlage / Textauszug
Schritt 3: Namespace wiederherstellen

oc apply -f platform/openshift/namespaces/order-prod.yaml

Vorlage / Textauszug
Schritt 4: Datenbank wiederherstellen
  • letztes gültiges Full Backup auswählen
  • WAL bis Zielzeitpunkt anwenden
  • fachliche Prüfqueries ausführen
  • Restore-Ergebnis dokumentieren
Schritt 5: Secrets prüfen

oc get externalsecret -n order-prod

oc get secret -n order-prod

Vorlage / Textauszug
Schritt 6: Anwendung synchronisieren

argocd app sync order-service-prod

argocd app wait order-service-prod

Vorlage / Textauszug
Schritt 7: Funktionstest

curl -k https://api.alpine.example.com/orders/actuator/health

Vorlage / Textauszug
Schritt 8: fachliche Validierung
  • Testbestellung anlegen
  • Datenbankeintrag prüfen
  • Outbox Event prüfen
  • Kafka Event prüfen
  • Billing-Verarbeitung prüfen
Schritt 9: Abschluss
  • Incident-Ticket aktualisieren
  • Restore-Zeit dokumentieren
  • RPO/RTO bewerten
  • Postmortem planen

Ein gutes Runbook ist konkret. Es nennt Befehle, Reihenfolge, Prüfungen und Verantwortlichkeiten.

---

18. Backup-Monitoring

Ein Backup ohne Monitoring ist riskant. Wenn ein Backup-Job seit zwei Wochen fehlschlägt, merkt man es sonst erst im Restore-Fall.

Monitoring sollte mindestens erfassen:

Vorlage / Textauszug

letzter erfolgreicher Backup-Zeitpunkt

Dauer des Backups

Größe des Backups

Fehlerstatus

verfügbarer Speicher im Backup-Repository

Alter des letzten Restore-Tests

Eine einfache Prometheus-Metrik könnte so aussehen:

Vorlage / Textauszug

backup_last_success_timestamp{system=class="str">"orders-db"} 1783288800

backup_duration_seconds{system=class="str">"orders-db"} 842

backup_size_bytes{system=class="str">"orders-db"} 1849238842

backup_success{system=class="str">"orders-db"} 1

Daraus lassen sich Alerts ableiten.

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: backup-alerts
  namespace: platform-monitoring
spec:
  groups:
    - name: backup.rules
      rules:
        - alert: BackupMissing
          expr: time() - backup_last_success_timestamp{system=class="str">"orders-db"} > 90000
          for: 15m
          labels:
            severity: critical
          annotations:
            summary: class="str">"No recent backup for orders-db"
            description: class="str">"The last successful backup for orders-db is older than 25 hours."

Dieser Alert ist konkreter als ein allgemeiner Systemalarm. Er beantwortet direkt: Welches Backup fehlt?

---

19. Restore-Validierung

Nach einem Restore braucht man technische und fachliche Prüfungen.

Technische Prüfungen:

Vorlage / Textauszug

Datenbank startet

Schemas vorhanden

Tabellen vorhanden

Indizes vorhanden

Migrationstabelle konsistent

Anwendung verbindet sich erfolgreich

Health Checks grün

Fachliche Prüfungen:

Vorlage / Textauszug

Bestellungen haben plausible Statuswerte

keine Order Items ohne Order

keine negativen Rechnungsbeträge

Outbox Events sind nicht doppelt veröffentlicht

Billing-Status passt zu Order-Status

Reporting kann Daten lesen

Beispiel-SQL:

sql
SELECT status, count(*)
FROM orders
GROUP BY status
ORDER BY status;

SELECT count(*) AS orphan_items
FROM order_items oi
LEFT JOIN orders o ON o.id = oi.order_id
WHERE o.id IS NULL;

SELECT count(*) AS unpublished_outbox_events
FROM outbox_events
WHERE published_at IS NULL;

Diese Prüfungen zeigen, ob die Datenbank nicht nur technisch, sondern auch fachlich brauchbar ist.

---

20. Backup und Security

Backups enthalten oft sensible Daten. Deshalb müssen sie geschützt werden.

Security-Anforderungen:

Vorlage / Textauszug

Backups verschlüsseln

Zugriff stark beschränken

Backup-Zugriffe protokollieren

Backup-Repository vom normalen App-Netz trennen

Secrets nicht unkontrolliert exportieren

Restore nur in genehmigten Umgebungen erlauben

Testdaten anonymisieren, wenn nötig

Ein gefährlicher Fehler ist, Produktionsbackups unverschlüsselt in Testumgebungen zu kopieren. Dadurch entstehen Datenschutz- und Sicherheitsprobleme.

Für das Lab gilt: Restore-Tests dürfen fachlich realistisch sein, aber sensible Daten müssen geschützt oder anonymisiert werden.

---

21. Fachliche Wiederherstellung statt nur technischer Restore

Nicht jeder Fehler lässt sich mit einem technischen Restore lösen.

Beispiel: Ein fehlerhafter Billing-Job hat falsche Rechnungsentwürfe erzeugt. Ein kompletter Datenbank-Restore würde vielleicht auch gültige Bestellungen und andere Änderungen zurückdrehen. Das kann schlimmer sein als der ursprüngliche Fehler.

In solchen Fällen braucht man fachliche Korrekturprozesse:

Vorlage / Textauszug

fehlerhafte Datensätze identifizieren

Korrekturstatus setzen

Gutschriften oder Storno erzeugen

Events zur Korrektur veröffentlichen

Audit Trail erhalten

Kundenkommunikation vorbereiten

Deshalb müssen Anwendungen so gebaut werden, dass sie Korrekturen erlauben. Audit Logging, Statusmodelle und Idempotenz sind dafür wichtig.

---

22. Beispiel: Korrekturjob für fehlerhafte Rechnungen

java
package com.seb4u.demo.spring.billing.application;

import com.seb4u.demo.spring.billing.domain.Invoice;
import com.seb4u.demo.spring.billing.domain.InvoiceRepository;
import com.seb4u.demo.spring.billing.domain.InvoiceStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.util.List;

@Service
public class InvoiceCorrectionService {

    private final InvoiceRepository invoiceRepository;
    private final Clock clock;

    public InvoiceCorrectionService(InvoiceRepository invoiceRepository, Clock clock) {
        this.invoiceRepository = invoiceRepository;
        this.clock = clock;
    }

    @Transactional
    public CorrectionResult markIncorrectDraftsAsBlocked(String incidentId, List<String> invoiceIds) {
        int changed = 0;

        for (String invoiceId : invoiceIds) {
            Invoice invoice = invoiceRepository.findById(invoiceId)
                    .orElseThrow(() -> new IllegalArgumentException(class="str">"Invoice not found: " + invoiceId));

            if (invoice.status() != InvoiceStatus.DRAFT) {
                continue;
            }

            invoice.blockForCorrection(
                    incidentId,
                    class="str">"Blocked because of billing incident " + incidentId,
                    Instant.now(clock)
            );

            invoiceRepository.save(invoice);
            changed++;
        }

        return new CorrectionResult(incidentId, changed);
    }
}

Dieses Beispiel zeigt: Fachliche Fehler werden nicht immer durch Restore gelöst. Manchmal ist eine kontrollierte Korrektur besser.

---

23. DR-Test als Übung

Ein Disaster-Recovery-Plan ist nur glaubwürdig, wenn er getestet wird. Ein Test muss nicht immer den ganzen Standortausfall simulieren. Es kann auch ein Teiltest sein.

Beispiele:

Vorlage / Textauszug

Restore einer PostgreSQL-Datenbank in Testumgebung

Wiederaufbau eines Namespace aus GitOps

Wiederherstellung eines Keycloak-Realm-Exports

Restore eines Object-Storage-Objekts aus Versionierung

Umschaltung einer Test-DNS-Zone

Start eines Services mit neu erzeugten Secrets

Nach jedem Test wird dokumentiert:

Vorlage / Textauszug

Was wurde getestet?

Wie lange hat es gedauert?

Welche Probleme traten auf?

Wurde RTO eingehalten?

Wurde RPO eingehalten?

Welche Runbook-Schritte waren unklar?

Welche Automatisierung fehlt?

---

24. Übung: Backup-Konzept für das Lab erstellen

Erstelle ein Backup- und Restore-Konzept für folgende Komponenten:

Vorlage / Textauszug

Order Service Datenbank

Billing Service Datenbank

Keycloak

Object Storage für Partnerdateien

GitOps Repository

OpenShift Manifeste

Reporting Exportdaten

Beschreibe je Komponente:

Vorlage / Textauszug

Was wird gesichert?

Wie oft wird gesichert?

Wo wird es gespeichert?

Wie wird Restore getestet?

Welches RPO gilt?

Welches RTO gilt?

Welche Security-Regeln gelten?

---

25. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

Order Service Datenbank:

Tägliches Full Backup und kontinuierliche WAL-Archivierung. Speicherung im getrennten Backup-Repository und zusätzlich in Object Storage mit Versionierung. RPO 15 Minuten, RTO 2 Stunden. Monatlicher Restore-Test.

Billing Service Datenbank:

Tägliches Full Backup und WAL-Archivierung. RPO 15 Minuten, RTO 4 Stunden. Vor Restore wird geprüft, ob fachliche Korrektur statt vollständigem Restore sinnvoller ist.

Keycloak:

PostgreSQL-Backup plus regelmäßiger Realm-Export. RPO 1 Stunde, RTO 2 Stunden. Restore-Test prüft Login, Token-Ausstellung und Client-Konfiguration.

Object Storage Partnerdateien:

Versionierung, Lifecycle-Regeln und Replikation. RPO abhängig von Replikation, Ziel maximal 1 Stunde. Restore-Test über Wiederherstellung einer gelöschten Testdatei.

GitOps Repository:

Git-Server-Backup und Spiegelung in zweites Repository. RPO 1 Stunde, RTO 4 Stunden. Restore-Test durch Klonen und Argo-CD-Synchronisation in Testcluster.

OpenShift Manifeste:

Primär in Git gesichert. Clusterzustand wird nicht als alleinige Wahrheit betrachtet. Restore erfolgt über GitOps.

Reporting Exportdaten:

Ablage in Object Storage mit Lifecycle. RPO 24 Stunden, RTO 24 Stunden. Kritische Reports können neu erzeugt werden, wenn Quelldaten vorhanden sind.

Diese Lösung zeigt, dass Backup nicht überall gleich ist. Jede Komponente bekommt eine passende Strategie.

---

26. Merksätze

Vorlage / Textauszug

Ein Backup ist wertlos, wenn niemand den Restore beherrscht.

RPO beschreibt akzeptierten Datenverlust.

RTO beschreibt akzeptierte Wiederherstellungszeit.

Git stellt gewünschte Konfiguration wieder her, aber keine Daten.

Object Storage Versionierung schützt vor versehentlichem Überschreiben, ersetzt aber kein vollständiges DR-Konzept.

Nicht jeder fachliche Fehler sollte mit Datenbank-Restore gelöst werden.

Disaster Recovery ist ein geübter Ablauf, kein Dokument für den Notfallordner.

Resilienz, Lasttests und Produktionsreife

Resilienzpfad unter Last
Kompakte Themenübersicht
Load TestIngressRate LimitServiceTimeout / CBQueueBackpressureScalingHPA / Capacity

1. Warum Produktionsreife mehr ist als ein erfolgreicher Test

Ein Service ist nicht produktionsreif, nur weil er lokal startet, einen Controller-Test besteht und in OpenShift ein grünes Pod-Symbol zeigt. Produktionsreife beginnt dort, wo das System unter Last, bei Störungen, bei langsamen Abhängigkeiten und bei unvollständigen Daten trotzdem kontrolliert reagiert. In einem Hybrid-Enterprise-Lab ist diese Frage besonders wichtig, weil moderne Services auf OpenShift mit Datenbanken auf Bare Metal, Messaging-Systemen, VM-basierten Reporting-Prozessen, Cloud-Object-Storage und Identity-Komponenten zusammenspielen.

Produktionsreife bedeutet deshalb nicht Perfektion. Produktionsreife bedeutet, dass bekannte Fehlerbilder erwartet, begrenzt, überwacht und geübt werden. Ein Order Service darf ausfallen können, ohne dass Billing dauerhaft inkonsistent wird. Eine Datenbank darf kurzzeitig langsam sein, ohne dass alle Web-Threads blockieren. Ein Broker darf Events verzögert ausliefern, ohne dass Bestellungen doppelt fakturiert werden. Ein Cloud-Bucket darf höhere Latenz zeigen, ohne dass der File Processing Worker endlos unkontrolliert neue Prozesse startet.

Im Lab wird Produktionsreife über konkrete Nachweise sichtbar gemacht. Dazu gehören Lasttests, Timeout-Strategien, Retry-Regeln, Circuit Breaker, Rate Limits, Backpressure, Health Checks, Dashboards, Alerts, Runbooks und Go-Live-Checklisten. Diese Artefakte sind kein Zusatz am Ende. Sie gehören direkt in den Entwicklungs- und Plattformprozess.

Vorlage / Textauszug

Produktionsreife = fachliche Korrektheit + technische Stabilität + beobachtbarer Betrieb + geübte Wiederherstellung.

2. Resilienz als Architekturentscheidung

Resilienz bedeutet, dass ein System bei Problemen nicht unkontrolliert zusammenbricht. Das heißt aber nicht, dass jeder Service alle Fehler selbst verstecken soll. Ein resilienter Service unterscheidet zwischen kurzfristigen technischen Fehlern, dauerhaften fachlichen Fehlern und gefährlichen Zuständen, bei denen ein schneller Abbruch besser ist als endloses Weiterprobieren.

Ein Beispiel: Wenn der Billing Service ein Event empfängt und die Billing-Datenbank für wenige Sekunden nicht erreichbar ist, kann ein Retry sinnvoll sein. Wenn das Event aber fachlich ungültig ist, weil eine Pflichtinformation fehlt, darf kein endloser Retry erfolgen. Das Event muss in eine Dead Letter Queue oder in einen manuellen Klärungsprozess. Wenn die Datenbank seit zehn Minuten nicht erreichbar ist, helfen schnelle Wiederholungen ebenfalls nicht. Dann muss das System die Last reduzieren, Alarme auslösen und den Betrieb informieren.

In der Architektur werden deshalb folgende Regeln festgelegt:

Vorlage / Textauszug

Kurzfristiger technischer Fehler: begrenzter Retry mit Wartezeit.

Fachlicher Fehler: kein technischer Retry, sondern fachliche Fehlerablage.

Längerer Ausfall: Circuit Breaker öffnet, Alerts werden ausgelöst.

Überlast: Rate Limit oder Backpressure schützt nachgelagerte Systeme.

Unklarer Zustand: Verarbeitung stoppen, Nachweis sichern, Runbook starten.

Diese Regeln verhindern, dass ein kleiner Fehler zum Plattformproblem wird.

3. Timeout-Strategie für synchrone APIs

Timeout-Kette
Kompakte Lernhilfe
ClientGatewayTimeoutAPIkurz + klarDownstreamnoch kürzerFallbackFehlerbild

Synchronous APIs sind angenehm, weil der Aufrufer sofort eine Antwort bekommt. Sie sind aber gefährlich, wenn Timeouts nicht sauber definiert sind. Ohne Timeout kann ein Thread lange blockieren. Viele blockierte Threads führen zu erschöpften Connection Pools, langsamen Pods, fehlgeschlagenen Readiness Probes und letztlich zu Kaskadeneffekten.

Für das Lab gilt: Jeder externe HTTP-Aufruf braucht einen klaren Timeout. Dazu gehören Verbindungsaufbau, Antwortzeit und Gesamtzeit. Der Timeout muss kürzer sein als die Zeit, nach der der Aufrufer selbst abbrechen würde. Sonst wartet die obere Schicht länger als sinnvoll und blockiert Ressourcen.

Beispiel für einen Spring Boot HTTP Client mit klaren Timeouts:

java
package com.seb4u.demo.spring.order.infrastructure.customer;

import io.netty.channel.ChannelOption;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@Configuration
public class CustomerClientConfig {

    @Bean
    WebClient customerWebClient(WebClient.Builder builder) {
        HttpClient httpClient = HttpClient.create()
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1500)
                .responseTimeout(Duration.ofSeconds(3))
                .doOnConnected(connection -> connection
                        .addHandlerLast(new ReadTimeoutHandler(3, TimeUnit.SECONDS))
                        .addHandlerLast(new WriteTimeoutHandler(2, TimeUnit.SECONDS)));

        return builder
                .baseUrl(class="str">"https://customer-api.internal.alpine.example.com")
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }
}

Dieser Code zeigt nicht nur Technik. Er zeigt eine Betriebsentscheidung: Der Order Service wartet nicht endlos auf die Customer API. Wenn die Abhängigkeit zu langsam ist, wird der Fehler sichtbar und kontrollierbar.

4. Retry ist kein Allheilmittel

Retries werden oft falsch eingesetzt. Ein Retry kann einen kurzen Netzwerkfehler überbrücken. Er kann aber auch eine Störung verschlimmern. Wenn ein abhängiges System bereits überlastet ist und alle Clients sofort wiederholen, steigt die Last weiter. Dann entsteht ein Retry Storm.

Ein sinnvoller Retry hat deshalb Begrenzungen:

Vorlage / Textauszug
  • maximale Anzahl an Versuchen
  • Wartezeit zwischen Versuchen
  • exponentieller Backoff
  • Jitter gegen gleichzeitige Wiederholung
  • keine Wiederholung bei fachlichen Fehlern
  • Metriken pro Retry-Grund

Beispiel mit Resilience4j:

java
package com.seb4u.demo.spring.order.infrastructure.billing;

import io.github.resilience4j.retry.annotation.Retry;
import org.springframework.stereotype.Component;

@Component
public class BillingReadinessClient {

    private final BillingApi billingApi;

    public BillingReadinessClient(BillingApi billingApi) {
        this.billingApi = billingApi;
    }

    @Retry(name = class="str">"billing-readiness", fallbackMethod = class="str">"fallback")
    public BillingReadiness checkReadiness() {
        return billingApi.readiness();
    }

    public BillingReadiness fallback(Exception ex) {
        return new BillingReadiness(false, class="str">"Billing API not reachable: " + ex.getClass().getSimpleName());
    }
}

Dazu gehört eine Konfiguration:

yaml
resilience4j:
  retry:
    instances:
      billing-readiness:
        max-attempts: 3
        wait-duration: 300ms
        retry-exceptions:
          - java.net.SocketTimeoutException
          - java.io.IOException
        ignore-exceptions:
          - com.seb4u.demo.spring.order.domain.BusinessRuleViolationException

Der wichtigste Punkt ist ignore-exceptions. Fachliche Fehler dürfen nicht wie technische Störungen behandelt werden.

5. Circuit Breaker gegen Kaskadeneffekte

Ein Circuit Breaker schützt ein System davor, eine kaputte Abhängigkeit immer wieder anzusprechen. Er funktioniert ähnlich wie ein Sicherungsautomat. Wenn zu viele Fehler auftreten, öffnet er. Dann werden weitere Aufrufe schnell abgewiesen oder über einen Fallback beantwortet. Nach einer Wartezeit prüft er vorsichtig, ob die Abhängigkeit wieder verfügbar ist.

Im Lab ist das besonders relevant für synchrone Abhängigkeiten wie Customer API, Payment Provider, externe Partner-API oder interne Legacy-VM-Dienste.

yaml
resilience4j:
  circuitbreaker:
    instances:
      partner-api:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 50
        minimum-number-of-calls: 20
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30s
        permitted-number-of-calls-in-half-open-state: 5
        record-exceptions:
          - java.io.IOException
          - java.net.SocketTimeoutException

Ein Circuit Breaker darf aber nicht missverstanden werden. Er repariert die Abhängigkeit nicht. Er begrenzt Schaden. Deshalb muss jeder geöffnete Circuit Breaker in Monitoring und Alerting sichtbar sein.

6. Bulkheads: Fehlerbereiche trennen

Bulkhead bedeutet Schottwand. In Schiffen verhindern Schottwände, dass ein Leck das ganze Schiff flutet. In Software bedeutet Bulkhead: Ein langsamer Bereich soll nicht alle Ressourcen verbrauchen.

Beispiel: Der Order Service ruft eine interne Customer API, eine Promotion API und eine externe Partner API auf. Wenn alle Aufrufe denselben Threadpool verwenden, kann eine langsame Partner API alle Threads blockieren. Dann funktionieren auch interne Aufrufe nicht mehr.

Ein Bulkhead trennt Ressourcen:

yaml
resilience4j:
  bulkhead:
    instances:
      partner-api:
        max-concurrent-calls: 10
        max-wait-duration: 100ms
      customer-api:
        max-concurrent-calls: 50
        max-wait-duration: 200ms

Damit wird die externe Partner API begrenzt. Sie kann langsamer werden, ohne den gesamten Service zu blockieren.

7. Backpressure bei asynchroner Verarbeitung

Asynchrone Verarbeitung verschiebt Arbeit in Queues, Topics oder Outbox-Tabellen. Das macht Systeme entkoppelter, aber nicht automatisch stabil. Wenn Producer schneller schreiben als Consumer verarbeiten, wächst ein Backlog. Das ist nicht sofort ein Fehler. Es wird aber gefährlich, wenn Backlog, Speicherverbrauch, Latenz und Fehlerraten unkontrolliert steigen.

Backpressure bedeutet: Das System erkennt Überlast und reduziert den Zufluss oder verlangsamt Verarbeitung kontrolliert.

Im Lab betrifft das besonders:

Vorlage / Textauszug
  • Outbox Events im Order Service
  • Kafka Topics für Billing
  • Dead Letter Queues
  • File Processing Worker
  • Reporting Jobs
  • Object Storage Uploads

Ein Outbox Publisher darf nicht unbegrenzt viele Events gleichzeitig senden. Er braucht Batch-Größe, Polling-Intervall, Retry-Regeln und Metriken.

java
package com.seb4u.demo.spring.order.infrastructure.outbox;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class OutboxPublisherJob {

    private final OutboxRepository outboxRepository;
    private final EventBrokerPublisher brokerPublisher;
    private final Counter publishedCounter;
    private final Counter failedCounter;

    public OutboxPublisherJob(
            OutboxRepository outboxRepository,
            EventBrokerPublisher brokerPublisher,
            MeterRegistry registry
    ) {
        this.outboxRepository = outboxRepository;
        this.brokerPublisher = brokerPublisher;
        this.publishedCounter = registry.counter(class="str">"outbox_events_published_total");
        this.failedCounter = registry.counter(class="str">"outbox_events_failed_total");
        Gauge.builder(class="str">"outbox_events_unpublished", outboxRepository, OutboxRepository::countUnpublished)
                .description(class="str">"Number of unpublished outbox events")
                .register(registry);
    }

    @Scheduled(fixedDelayString = class="str">"${outbox.publisher.fixed-delay:2000}")
    public void publishBatch() {
        List<OutboxEventEntity> events = outboxRepository.lockNextBatch(100);

        for (OutboxEventEntity event : events) {
            try {
                brokerPublisher.publish(event.eventType(), event.payload());
                outboxRepository.markPublished(event.id());
                publishedCounter.increment();
            } catch (Exception ex) {
                outboxRepository.markFailed(event.id(), ex.getMessage());
                failedCounter.increment();
            }
        }
    }
}

Die Metriken sind hier genauso wichtig wie das Publishing selbst. Ohne Metriken erkennt niemand, ob die Outbox gesund ist.

8. Rate Limiting am API Gateway

Rate Limiting begrenzt Anfragen. Es schützt nicht nur vor Angriffen, sondern auch vor Fehlkonfigurationen und fehlerhaften Clients. Ein Partner, der versehentlich eine Schleife baut, darf nicht die gesamte Plattform überlasten.

Im Zielbild kann Rate Limiting an mehreren Stellen erfolgen:

Vorlage / Textauszug
  • Web Application Firewall
  • API Gateway
  • OpenShift Route / Ingress Layer
  • Anwendung selbst
  • externe Partner-Schnittstelle

Ein fachliches Beispiel:

Skizze / Textauszug
Customer Portal:
  normale Benutzer: 100 Requests pro Minute
  Admin Portal: strengere Regeln je Rolle
  Partner Upload API: größere Dateien, aber begrenzte Parallelität
  Reporting Export API: wenige parallele Exporte pro Benutzer

Rate Limits müssen dokumentiert werden. Sonst wirken sie später wie zufällige Fehler.

9. Lasttest-Strategie

Lasttests sollen nicht beweisen, dass ein System unendlich viel Last trägt. Sie sollen zeigen, wo Grenzen liegen und wie sich das System vor der Grenze verhält.

Das Lab nutzt mehrere Testarten:

Skizze / Textauszug
Smoke Load Test:
  kleine Last, schnelle Prüfung nach Deployment.

Baseline Load Test:
  typische Tageslast.

Stress Test:
  Last wird erhöht, bis Sättigung sichtbar wird.

Spike Test:
  plötzliche Lastspitze.

Soak Test:
  längere Laufzeit, um Speicherlecks und Backlog zu erkennen.

Failure Load Test:
  Last bei gestörter Abhängigkeit.

Ein guter Lasttest betrachtet nicht nur Antwortzeiten. Er betrachtet auch CPU, RAM, Datenbankverbindungen, Broker-Lag, Outbox-Länge, Garbage Collection, Fehlerquote und Autoscaling-Verhalten.

10. k6-Beispiel für Order API

javascript
import http from class="str">'k6/http';
import { check, sleep } from class="str">'k6';
import { Trend, Rate } from class="str">'k6/metrics';

export const options = {
  stages: [
    { duration: class="str">'2m', target: 20 },
    { duration: class="str">'5m', target: 50 },
    { duration: class="str">'2m', target: 100 },
    { duration: class="str">'3m', target: 0 }
  ],
  thresholds: {
    http_req_duration: [class="str">'p(95)<600', class="str">'p(99)<1200'],
    http_req_failed: [class="str">'rate<0.02']
  }
};

const orderLatency = new Trend(class="str">'order_create_latency');
const businessErrors = new Rate(class="str">'business_errors');

export default function () {
  const payload = JSON.stringify({
    customerId: `customer-${__VU}`,
    items: [
      { productId: class="str">'sku-1000', quantity: 1, unitPrice: 19.90 },
      { productId: class="str">'sku-2000', quantity: 2, unitPrice: 4.50 }
    ]
  });

  const params = {
    headers: {
      class="str">'Content-Type': class="str">'application/json',
      class="str">'X-Correlation-ID': `loadtest-${__VU}-${__ITER}`
    }
  };

  const res = http.post(class="str">'https://api.alpine.example.com/orders', payload, params);
  orderLatency.add(res.timings.duration);

  const ok = check(res, {
    class="str">'status is 201 or accepted': r => r.status === 201 || r.status === 202,
    class="str">'response has order id': r => String(r.body).includes(class="str">'orderId')
  });

  businessErrors.add(!ok);
  sleep(1);
}

Dieser Test ist bewusst nicht nur ein curl in Schleife. Er setzt Korrelation, prüft Antwortform und definiert Schwellenwerte.

11. Lasttest-Auswertung

Nach einem Lasttest reicht es nicht zu sagen: „Der Test war grün.“ Man muss erklären, was beobachtet wurde.

Ein sinnvoller Bericht enthält:

Vorlage / Textauszug
  • Testziel
  • Umgebung
  • Version der Anwendung
  • Testdaten
  • Lastprofil
  • p50/p95/p99 Antwortzeiten
  • Fehlerquote
  • CPU/RAM der Pods
  • Datenbankverbindungen
  • Broker Lag
  • Outbox Backlog
  • relevante Logs
  • auffällige Traces
  • Entscheidung: bestanden, bedingt bestanden oder nicht bestanden

Beispielauswertung:

Vorlage / Textauszug

Lasttest-Bericht: Order Service Baseline

Ziel

Prüfung, ob der Order Service 50 gleichzeitige Benutzer bei typischer Warenkorbgröße stabil verarbeitet.

Ergebnis

Der Test wurde technisch bestanden. p95 lag bei 420 ms, p99 bei 870 ms. Die Fehlerquote lag bei 0,3 Prozent. Die Fehler entstanden ausschließlich durch absichtlich ungültige Testdaten.

Auffälligkeiten

Während der Last stieg die Outbox-Länge kurzfristig auf 1.800 Events, wurde aber innerhalb von vier Minuten wieder abgebaut. Kafka zeigte keinen kritischen Lag. PostgreSQL erreichte maximal 68 Prozent CPU.

Entscheidung

Baseline bestanden. Für Go-Live muss zusätzlich ein Spike Test mit 200 gleichzeitigen Benutzern durchgeführt werden.

12. Autoscaling und seine Grenzen

Horizontal Pod Autoscaling kann helfen, wenn ein Service mehr Last bekommt. Es löst aber nicht jedes Problem. Wenn die Datenbank der Engpass ist, erzeugen mehr Pods mehr Datenbankverbindungen und verschlimmern das Problem. Wenn Kafka Consumer falsch partitioniert sind, bringen zusätzliche Pods eventuell keine höhere Verarbeitung. Wenn eine externe API langsam ist, erzeugen zusätzliche Pods mehr gleichzeitige externe Aufrufe.

Ein HPA muss deshalb mit Ressourcen, Connection Pools und abhängigen Systemen abgestimmt werden.

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
  namespace: order-prod
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: http_server_requests_seconds_p95
        target:
          type: AverageValue
          averageValue: class="str">"600m"

Dieses Beispiel zeigt ein Prinzip. In der Realität braucht custom metrics sorgfältige Einrichtung. Wichtig ist: Autoscaling darf nicht blind konfiguriert werden.

13. Connection Pools richtig begrenzen

Ein typischer Fehler ist ein zu großer Connection Pool. Wenn acht Pods jeweils 50 Datenbankverbindungen öffnen, entstehen 400 mögliche Verbindungen. Die Datenbank kann dadurch überlastet werden.

Für das Lab wird die maximale Verbindungszahl pro Umgebung geplant:

Vorlage / Textauszug

PostgreSQL max_connections: 300

Reserve für DBA, Monitoring und Wartung: 50

Verfügbar für Anwendungen: 250

Order Service: 8 Pods × 10 Connections = 80

Billing Service: 6 Pods × 10 Connections = 60

Reporting: 4 Jobs × 15 Connections = 60

Reserve für andere Dienste: 50

Spring Boot Beispiel:

yaml
spring:
  datasource:
    hikari:
      maximum-pool-size: 10
      minimum-idle: 2
      connection-timeout: 2000
      validation-timeout: 1000
      idle-timeout: 300000
      max-lifetime: 1800000

Diese Zahlen müssen zur Datenbank passen. Sie sind keine Dekoration.

14. Readiness ist kein allgemeiner Gesundheitstest

Readiness beantwortet die Frage: Soll dieser Pod Traffic erhalten? Liveness beantwortet die Frage: Muss dieser Pod neu gestartet werden? Diese Unterscheidung ist wichtig.

Ein häufiger Fehler ist, in der Liveness Probe externe Abhängigkeiten zu prüfen. Wenn die Datenbank kurz ausfällt, starten dann alle Pods neu. Das verschlimmert die Lage.

Besser:

Skizze / Textauszug
Liveness:
  prüft, ob die Anwendung selbst noch lebt.

Readiness:
  prüft, ob die Anwendung Traffic sicher verarbeiten kann.

Startup Probe:
  schützt langsame Startphasen.

OpenShift-Beispiel:

yaml
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  initialDelaySeconds: 40
  periodSeconds: 20
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 10
  failureThreshold: 2

startupProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  periodSeconds: 10
  failureThreshold: 18

15. Produktionsreife von Jobs und CronJobs

Nicht nur dauerhafte Services brauchen Resilienz. Jobs und CronJobs sind oft kritischer, weil sie nachts laufen und Fehler später auffallen.

Für Reporting Jobs gilt:

Vorlage / Textauszug
  • eindeutige Job-ID
  • Startzeit und Endzeit in Logs
  • fachliche Ergebnisanzahl
  • Fehleranzahl
  • Wiederholbarkeit
  • keine doppelte Auslieferung
  • klare Sperre gegen parallele Ausführung
  • Alarm bei Ausfall oder Laufzeitüberschreitung

Kubernetes CronJob Beispiel:

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-reporting-export
  namespace: reporting-prod
spec:
  schedule: class="str">"0 2 * * *"
  concurrencyPolicy: Forbid
  failedJobsHistoryLimit: 5
  successfulJobsHistoryLimit: 3
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 7200
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: reporting-export
              image: registry.example.com/alpine/reporting-export:1.0.0
              env:
                - name: JOB_MODE
                  value: nightly-export
              resources:
                requests:
                  cpu: class="str">"500m"
                  memory: class="str">"1Gi"
                limits:
                  cpu: class="str">"2000m"
                  memory: class="str">"4Gi"

concurrencyPolicy: Forbid ist hier wichtig. Es verhindert, dass ein verspäteter Job parallel mit dem nächsten Lauf kollidiert.

16. Chaos-Übungen im kleinen Rahmen

Chaos Engineering bedeutet nicht, zufällig Produktion kaputt zu machen. Im Lab bedeutet es kontrollierte Störungsübungen.

Beispiele:

Vorlage / Textauszug
  • Billing-Datenbank für zwei Minuten nicht erreichbar
  • Kafka Consumer gestoppt
  • falsches Secret in Testumgebung
  • künstliche Latenz zur Partner API
  • Pod wird während Verarbeitung beendet
  • Object Storage liefert temporär Fehler

Jede Übung braucht Hypothese, Durchführung, Beobachtung und Ergebnis.

Vorlage / Textauszug

Chaos-Übung: Kafka Consumer gestoppt

Hypothese

Wenn der Billing Consumer zehn Minuten gestoppt wird, wächst der Kafka Lag, aber der Order Service bleibt verfügbar. Nach Start des Consumers wird der Lag innerhalb von 20 Minuten abgebaut.

Durchführung

Billing Deployment in Test auf 0 Replicas skalieren. Währenddessen 5.000 Bestellungen erzeugen. Danach Deployment wieder auf 3 Replicas skalieren.

Erwartete Beobachtung

Order Service liefert weiterhin 201. Kafka Lag steigt sichtbar. Billing-Dashboard zeigt verzögerte Verarbeitung. Alert wird nach fünf Minuten ausgelöst.

Ergebnis

Bestanden, wenn keine Events verloren gehen und keine Rechnung doppelt erzeugt wird.

17. Go-Live-Readiness-Check

Vor einem produktionsnahen Release braucht das Team eine klare Abnahme. Diese Abnahme darf nicht nur aus „Tests grün“ bestehen.

Go-Live-Checkliste:

Vorlage / Textauszug

Fachlich:

  • Kernprozess getestet
  • Fehlerfälle getestet
  • fachliche Korrekturen beschrieben
  • Datenmigration geprüft

Technisch:

  • CI Pipeline grün
  • Image signiert oder nachvollziehbar gebaut
  • Manifeste geprüft
  • Resource Limits gesetzt
  • Probes gesetzt
  • NetworkPolicies gesetzt

Security:

  • Rollen geprüft
  • Secrets nicht im Repository
  • Token-Konfiguration geprüft
  • Audit Logging aktiv

Betrieb:

  • Dashboard vorhanden
  • Alerts vorhanden
  • Runbook vorhanden
  • Backup/Restore geprüft
  • Rollback beschrieben

Last und Resilienz:

  • Baseline Load Test bestanden
  • Timeout-Strategie dokumentiert
  • Retry-Regeln dokumentiert
  • Circuit Breaker sichtbar
  • Outbox/DLQ überwacht

18. Produktionsreife im Quality Gate

Das Quality Gate im Pull Request prüft nicht alles automatisch. Aber es kann verhindern, dass wichtige Artefakte fehlen.

Beispiel einer Repository-Prüfung:

bash
#!/usr/bin/env bash
set -euo pipefail

required_files=(
  class="str">"README.md"
  class="str">"docs/adr/ADR-001-service-boundary.md"
  class="str">"docs/runbooks/order-service.md"
  class="str">"deploy/overlays/prod/kustomization.yaml"
  class="str">"deploy/base/deployment.yaml"
  class="str">"deploy/base/service.yaml"
  class="str">"deploy/base/networkpolicy.yaml"
  class="str">"observability/prometheus-rule.yaml"
)

for file in class="str">"${required_files[@]}"; do
  if [[ ! -f class="str">"$file" ]]; then
    echo class="str">"Missing required file: $file"
    exit 1
  fi
done

echo class="str">"Operational readiness files are present."

Diese Prüfung ersetzt keinen Review, aber sie setzt eine Mindestgrenze.

19. Runbook: Lastspitze im Customer Portal

Vorlage / Textauszug

Runbook: Lastspitze im Customer Portal

Symptom

Benutzer melden langsame Seitenladezeiten. Grafana zeigt erhöhte p95-Latenz im Customer Portal und im Order Service.

Sofort prüfen
  • Ist nur das Frontend betroffen oder auch APIs?
  • Steigt die Fehlerrate?
  • Sind Pods CPU- oder Memory-limitiert?
  • Gibt es Datenbank-Wartezeiten?
  • Gibt es Broker-Lag oder Outbox-Backlog?
Kommandos

oc get hpa -n portal-prod

oc get pods -n portal-prod

oc top pods -n portal-prod

oc get pods -n order-prod

oc top pods -n order-prod

Vorlage / Textauszug
Entscheidungen

Wenn nur Frontend-CPU hoch ist: HPA und CDN/Cache prüfen.

Wenn Order API langsam ist: Traces und Datenbank prüfen.

Wenn Datenbank an Grenze ist: keine blinde Pod-Erhöhung.

Wenn externe Partner API langsam ist: Circuit Breaker Status prüfen.

Eskalation

Bei Fehlerquote über 5 Prozent länger als 10 Minuten: Incident Lead aktivieren.

20. Typische Fehler bei Resilienz

Ein häufiger Fehler ist blindes Retry. Dadurch wird aus einer kurzen Störung ein Lastproblem. Ein zweiter Fehler ist fehlender Timeout. Dadurch werden Threads blockiert und Pods wirken zufällig langsam. Ein dritter Fehler ist falsche Liveness-Konfiguration. Dadurch starten Pods wegen externer Probleme neu. Ein vierter Fehler ist Autoscaling ohne Blick auf Datenbank und Broker. Ein fünfter Fehler ist fehlende Lasttest-Auswertung. Ein sechster Fehler ist ein Runbook, das nur sagt „Logs prüfen“, aber keine Entscheidungen beschreibt.

Produktionsreife entsteht nicht durch ein einzelnes Tool. Sie entsteht durch konsequente Grenzen, Messbarkeit, Übung und klare Zuständigkeiten.

21. Übung

Erstelle für den Order Service eine Produktionsreife-Abnahme. Sie soll folgende Punkte enthalten:

Vorlage / Textauszug
  • drei Timeouts
  • zwei Retry-Regeln
  • einen Circuit Breaker
  • eine Backpressure-Regel
  • drei Metriken
  • zwei Alerts
  • einen Lasttest
  • ein Runbook-Symptom
  • eine Go-Live-Entscheidung

22. Musterlösung

Vorlage / Textauszug

Timeouts:

  • Verbindung zur Customer API: 1,5 Sekunden
  • Antwortzeit Customer API: 3 Sekunden
  • Datenbankverbindung aus Pool: 2 Sekunden

Retry-Regeln:

  • Customer API technische Timeouts maximal 3 Versuche mit Backoff
  • Outbox Publishing maximal 5 Versuche, danach Fehlerstatus und Alert

Circuit Breaker:

  • Partner API öffnet bei 50 Prozent Fehlern in 50 Aufrufen

Backpressure:

  • Outbox Publisher verarbeitet maximal 100 Events pro Batch

Metriken:

  • outbox_events_unpublished
  • http_server_requests_seconds p95/p99
  • database_connection_pool_active

Alerts:

  • Outbox älter als 10 Minuten
  • p95 Order API über 800 ms für 5 Minuten

Lasttest:

  • k6 Baseline mit 50 gleichzeitigen Benutzern und p95 unter 600 ms

Runbook-Symptom:

  • Customer Portal langsam, Order API p95 steigt, Outbox bleibt normal

Go-Live-Entscheidung:

  • Freigabe nur, wenn Baseline Load Test bestanden, Alerts aktiv und Runbook reviewed sind.

23. Merksätze

Vorlage / Textauszug

Resilienz bedeutet nicht, jeden Fehler zu verstecken.

Timeouts schützen Ressourcen.

Retries brauchen Grenzen.

Circuit Breaker begrenzen Kaskadeneffekte.

Backpressure ist Pflicht bei asynchroner Verarbeitung.

Autoscaling ohne Kapazitätsplanung kann Schaden vergrößern.

Produktionsreife muss gemessen, dokumentiert und geübt werden.

Teil F: DevOps, GitOps, Release Management und AI-Unterstützung

Teil F: DevOps, GitOps, Release Management und AI-Unterstützung

PlanZielbildBuildUmsetzungRunBetriebTeil F: DevOps, GitOps, Release Management und AI-Unterstützung: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 11, Teil 12.

DevOps, GitOps und Release Management

Release-Fluss kompakt
Kompakte Themenübersicht
GitCIRegistryImageGitOps RepoOverlayArgo CDProdpushbuildtagsyncdeploy

1. Warum DevOps in diesem Lab mehr ist als eine Pipeline

DevOps wird in vielen Projekten auf CI/CD reduziert. Das ist zu wenig. Eine Pipeline kann Code bauen, Tests ausführen und ein Container Image veröffentlichen. Aber eine Enterprise-Plattform braucht mehr: klare Verantwortlichkeiten, nachvollziehbare Änderungen, reproduzierbare Umgebungen, sichere Freigaben, kontrollierte Releases, Rollback-Wege und eine Verbindung zwischen Entwicklung, Betrieb, Security und Architektur.

In diesem Lab bedeutet DevOps nicht, dass jeder alles darf. DevOps bedeutet, dass Teams so arbeiten, dass Änderungen schnell, sicher und nachvollziehbar in produktionsnahe Umgebungen gelangen. Dazu gehören Git-Strategie, Pull Requests, automatisierte Checks, Artefaktversionierung, Container Registry, GitOps, Release Notes, Environment Promotion, Change-Nachweise und Runbooks.

Ein moderner Service ist erst dann wirklich lieferbar, wenn folgende Fragen beantwortet sind:

Vorlage / Textauszug

Wer hat die Änderung gemacht?

Welche Tests wurden ausgeführt?

Welche Artefakte wurden gebaut?

Welche Image-Version wurde deployt?

Welche Konfiguration wurde verwendet?

Welche Umgebung ist betroffen?

Wie kann die Änderung zurückgenommen werden?

Welche fachliche Auswirkung hat die Änderung?

Das Ziel dieses Kapitels ist, aus einzelnen Build-Schritten einen belastbaren Enterprise-Lieferprozess zu machen.

---

2. Git als zentrale Wahrheit

Git ist im Lab nicht nur ein Ort für Quellcode. Git wird zur zentralen Wahrheit für Anwendungen, Infrastruktur, Plattformkonfiguration und Dokumentation.

Das bedeutet: Eine Änderung, die nicht in Git nachvollziehbar ist, gilt nicht als sauber umgesetzt. Das betrifft Java-Code genauso wie OpenShift-Manifeste, Terraform-Dateien, Ansible-Playbooks, ADRs, Runbooks und Dashboards.

Eine gute Git-Struktur macht sichtbar, welche Verantwortung ein Bereich hat. Der Order Service liegt unter apps/order-service. Die OpenShift-Manifeste liegen unter platform/openshift oder platform/kustomize. ADRs liegen unter docs/adr. Runbooks liegen unter docs/runbooks.

Dadurch kann ein Reviewer erkennen, ob eine Änderung vollständig ist. Wenn ein Entwickler eine neue Datenbankverbindung einführt, sollte nicht nur Java-Code geändert werden. Wahrscheinlich braucht es auch eine ConfigMap, ein Secret, eine NetworkPolicy, eine README-Ergänzung und vielleicht ein Runbook.

---

3. Branching-Modell für das Lab

Für das Lab verwenden wir ein einfaches, aber enterprise-taugliches Modell.

Vorlage / Textauszug

main

feature/order-outbox

feature/billing-consumer

fix/order-db-timeout

release/2026.07

hotfix/prod-token-validation

main enthält den integrierten Stand. Feature-Branches dienen der Entwicklung einzelner Änderungen. Release-Branches können genutzt werden, wenn ein stabiler Release-Kandidat vorbereitet wird. Hotfix-Branches sind für dringende Produktionskorrekturen gedacht.

Wichtig ist: Das Branching-Modell darf nicht komplizierter sein als nötig. Viele alte Enterprise-Projekte leiden unter schwer verständlichen Branching-Regeln. Dann wissen Teams nicht mehr, wohin eine Änderung gemerged werden soll. Für dieses Lab gilt daher:

Vorlage / Textauszug

Feature klein halten.

Pull Request früh öffnen.

Automatische Checks verpflichtend machen.

Release-Zustand taggen.

Produktionsänderungen nie direkt im Cluster klicken.

---

4. Pull Request als Qualitätsgrenze

Ein Pull Request ist mehr als eine Bitte um Code Review. Er ist eine Qualitätsgrenze. Hier wird geprüft, ob eine Änderung fachlich, technisch, sicherheitsbezogen und betrieblich vollständig ist.

Ein Pull Request für den Order Service sollte zum Beispiel enthalten:

Vorlage / Textauszug
  • fachliche Beschreibung der Änderung
  • Link zum Ticket oder zur Story
  • betroffene API-Endpunkte
  • Datenbankmigration, falls nötig
  • Tests
  • Auswirkungen auf Konfiguration
  • Auswirkungen auf Security
  • Auswirkungen auf Monitoring
  • Rollback-Hinweis

Ein schwacher Pull Request sagt nur:

Vorlage / Textauszug

Added new endpoint.

Ein guter Pull Request sagt:

Vorlage / Textauszug

Dieser PR ergänzt den Endpunkt zum Stornieren einer Bestellung. Der Endpunkt ist nur für Benutzer mit der Rolle ORDER_MANAGER erlaubt. Die Änderung ergänzt einen Integrationstest mit Keycloak-Testtoken, eine Flyway-Migration für den Stornogrund, eine neue Metrik order_cancel_total und ein Runbook-Hinweis für gehäufte Stornofehler.

Dadurch wird der Review deutlich besser.

---

5. Pull-Request-Checkliste

Diese Checkliste ist kein Ersatz für Denken. Sie verhindert aber typische Lücken.

Vorlage / Textauszug

Pull Request Checkliste

Fachlichkeit
  • [ ] Zweck der Änderung ist beschrieben
  • [ ] betroffene Use Cases sind genannt
  • [ ] fachliche Fehlerfälle sind berücksichtigt
Code
  • [ ] Unit Tests vorhanden
  • [ ] Integrationstests vorhanden, wenn Infrastruktur beteiligt ist
  • [ ] keine Secrets im Code
  • [ ] keine harten Umgebungswerte
Daten
  • [ ] Migration vorhanden, falls Schema geändert wird
  • [ ] Rollback- oder Korrekturstrategie beschrieben
  • [ ] Datenklassifizierung geprüft
Security
  • [ ] Rollen/Berechtigungen geprüft
  • [ ] Token-/OIDC-Auswirkung geprüft
  • [ ] NetworkPolicy-Auswirkung geprüft
Betrieb
  • [ ] Logs sinnvoll
  • [ ] Metriken ergänzt, falls nötig
  • [ ] Alert oder Dashboard angepasst, falls nötig
  • [ ] Runbook angepasst, falls nötig
Deployment
  • [ ] Manifest/Kustomize/Helm angepasst
  • [ ] Ressourcenlimits geprüft
  • [ ] Readiness/Liveness nicht gebrochen
  • [ ] Rollback-Hinweis vorhanden

Diese Checkliste wird direkt in das Repository gelegt, zum Beispiel unter .github/pull_request_template.md oder .gitruntime-lab/merge_request_templates/default.md.

---

6. CI: Continuous Integration

Continuous Integration bedeutet, dass Änderungen regelmäßig integriert und automatisch geprüft werden. Für Java-Services im Lab umfasst CI typischerweise:

Vorlage / Textauszug

Checkout

Java Setup

Dependency Cache

Build

Unit Tests

Integration Tests

Static Analysis

Container Build

Image Scan

Push zur Registry

CI beantwortet die Frage: Ist dieser Code technisch und qualitativ in einem Zustand, der als Artefakt gebaut werden kann?

CI deployt nicht automatisch nach Produktion. Das ist eine bewusste Trennung. Deployment erfolgt später kontrolliert über GitOps.

---

7. Beispiel: GitHub Actions für Order Service

yaml
name: order-service-ci

on:
  pull_request:
    paths:
      - class="str">'apps/order-service/**'
      - class="str">'.github/workflows/order-service-ci.yml'
  push:
    branches:
      - main
    paths:
      - class="str">'apps/order-service/**'

jobs:
  build-test-image:
    runs-on: ubuntu-latest

    permissions:
      contents: read
      packages: write
      security-events: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">'21'
          cache: maven

      - name: Build and test
        working-directory: apps/order-service
        run: ./mvnw clean verify

      - name: Build container image
        working-directory: apps/order-service
        run: |
          IMAGE=ghcr.io/example/order-service:${{ github.sha }}
          docker build -t class="str">"$IMAGE" .
          echo class="str">"IMAGE=$IMAGE" >> class="str">"$GITHUB_ENV"

      - name: Scan image
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.IMAGE }}
          format: table
          exit-code: class="str">'1'
          severity: CRITICAL,HIGH

      - name: Push image
        if: github.ref == class="str">'refs/heads/main'
        run: docker push class="str">"$IMAGE"

Dieses Beispiel zeigt mehrere wichtige Punkte. Die Pipeline reagiert nur auf relevante Pfade. Java 21 wird festgelegt. Tests laufen vor dem Image-Build. Das Image bekommt den Git SHA als Version. Security Scans können den Build stoppen.

In einem echten Projekt würde zusätzlich Login zur Registry, SBOM-Erzeugung und Signierung ergänzt.

---

8. Artefaktversionierung

Ein häufiger Fehler ist die Nutzung von latest als produktive Image-Version. Das ist gefährlich, weil später nicht mehr klar ist, welcher Code tatsächlich läuft.

Im Lab verwenden wir nachvollziehbare Versionen:

Vorlage / Textauszug

registry.example.com/alpine/order-service:git-8f3a91c

registry.example.com/alpine/order-service:1.4.0

registry.example.com/alpine/order-service:2026.07.05-8f3a91c

Für produktionsnahe Releases sollte die Version mit Git-Tag, Commit SHA und Build-Zeit nachvollziehbar sein.

Eine gute Image-Metadatenstrategie ergänzt Labels:

dockerfile
LABEL org.opencontainers.image.title=class="str">"order-service"
LABEL org.opencontainers.image.source=class="str">"https://git.example.com/alpine/order-service"
LABEL org.opencontainers.image.revision=class="str">"${GIT_COMMIT}"
LABEL org.opencontainers.image.version=class="str">"${APP_VERSION}"

Diese Labels helfen später bei Audits und Incident-Analysen.

---

9. Container Build: sauber statt zufällig

Ein Dockerfile für Enterprise-Services sollte klein, reproduzierbar und sicher sein.

dockerfile
FROM eclipse-temurin:21-jre-alpine

RUN addgroup -S app && adduser -S app -G app

WORKDIR /app

COPY target/order-service.jar /app/order-service.jar

USER app

EXPOSE 8080

ENTRYPOINT [class="str">"java", class="str">"-XX:MaxRAMPercentage=75", class="str">"-jar", class="str">"/app/order-service.jar"]

Dieses Dockerfile ist bewusst schlicht. Es nutzt ein JRE-Image statt eines kompletten JDK-Images, legt einen nicht-root Benutzer an und startet die Anwendung ohne Shell-Tricks.

In OpenShift ist zusätzlich wichtig, dass Container nicht auf feste User-IDs angewiesen sind. OpenShift vergibt häufig dynamische UIDs. Deshalb muss das Image so gebaut sein, dass es nicht zwingend einen bestimmten Linux-Benutzer erwartet, wenn die Plattform andere Security-Kontexte erzwingt.

---

10. GitOps-Grundidee

GitOps-Releasefluss
Kompakte Lernhilfe
GitCIRegistryImageGitOps RepoOverlayArgo CDSync → Cluster

GitOps bedeutet: Der gewünschte Zustand der Plattform liegt in Git. Ein GitOps-Controller wie Argo CD vergleicht diesen Zustand mit dem tatsächlichen Zustand im Cluster und synchronisiert Abweichungen.

Nicht der Entwickler klickt direkt im Cluster herum. Stattdessen ändert er Git. Argo CD bringt den Cluster in den gewünschten Zustand.

Skizze / Textauszug
Git Repository
↓
Argo CD
↓
OpenShift Cluster
↓
laufende Anwendung

Der Vorteil ist Nachvollziehbarkeit. Jede Deployment-Änderung hat einen Commit. Jede Änderung kann reviewed werden. Rollbacks erfolgen über Git-Zustände.

Der Nachteil ist: Teams müssen sauber zwischen Build und Deployment unterscheiden. Die CI-Pipeline baut ein Image. GitOps deployt dieses Image.

---

11. Trennung von App-Repo und GitOps-Repo

In vielen Enterprise-Projekten ist es sinnvoll, Quellcode und Deployment-Zustand zu trennen.

Beispiel:

Vorlage / Textauszug

app repository:

apps/order-service

platform repository:

platform-gitops/environments/dev/order-service

platform-gitops/environments/test/order-service

platform-gitops/environments/prod/order-service

Das App-Repo enthält Code, Tests und Dockerfile. Das GitOps-Repo enthält Manifest-Versionen für Umgebungen.

Der Vorteil: Produktionsdeployment kann stärker geschützt werden. Ein Entwickler kann ein Image bauen, aber nicht automatisch Produktion ändern. Die Änderung im GitOps-Repo braucht einen eigenen Review.

---

12. Kustomize für Umgebungstrennung

Kustomize eignet sich gut, um gemeinsame Basis-Manifeste und umgebungsspezifische Anpassungen zu trennen.

Skizze / Textauszug
platform/kustomize/order-service/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── route.yaml
│   ├── service-monitor.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── patch-replicas.yaml
    ├── test/
    │   ├── kustomization.yaml
    │   └── patch-resources.yaml
    └── prod/
        ├── kustomization.yaml
        ├── patch-replicas.yaml
        └── patch-resources.yaml

Die Basis beschreibt, was der Service grundsätzlich braucht. Overlays beschreiben Unterschiede zwischen Umgebungen.

---

13. Beispiel: Kustomize Overlay für Produktion

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: order-prod

resources:
  - ../../base

images:
  - name: registry.example.com/alpine/order-service
    newTag: 1.4.0

patches:
  - path: patch-replicas.yaml
  - path: patch-resources.yaml

configMapGenerator:
  - name: order-service-config
    literals:
      - LOG_LEVEL=INFO
      - FEATURE_CANCEL_ORDER=true

Hier wird das Image für Produktion festgelegt. Genau diese Datei ist später Gegenstand eines Reviews. Ein Release nach Produktion ist dadurch nicht unsichtbar, sondern ein Git-Commit.

---

14. Argo CD Application

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: openshift-gitops
spec:
  project: alpine-prod
  source:
    repoURL: https://git.example.com/alpine/platform-gitops.git
    targetRevision: main
    path: platform/kustomize/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=false

Diese Application beschreibt, wo Argo CD den gewünschten Zustand findet und wohin er deployt wird.

Für Produktion wird prune: false in manchen Organisationen bewusst gewählt, damit Ressourcen nicht automatisch gelöscht werden. Andere Organisationen erlauben prune, wenn Review und Schutzmechanismen stark genug sind. Das ist eine Governance-Entscheidung.

---

15. Environment Promotion

Environment Promotion bedeutet: Eine Änderung wandert kontrolliert von Dev nach Test und dann nach Prod.

Skizze / Textauszug
Dev
↓
automatische Tests
↓
Test
↓
fachliche Abnahme
↓
Staging
↓
Release-Freigabe
↓
Prod

Die schlechte Variante ist, dieselbe Änderung manuell in jeder Umgebung anders einzutragen. Dann weiß niemand, ob Test wirklich wie Prod ist.

Die bessere Variante ist, dass dasselbe Image durch die Umgebungen wandert. Nicht jedes Mal wird neu gebaut. Ein Image, das in Test geprüft wurde, wird später in Prod verwendet.

Skizze / Textauszug
Build einmal
↓
Image versionieren
↓
Dev deployen
↓
Test deployen
↓
Prod deployen

Dadurch wird verhindert, dass in Produktion ein anderes Artefakt landet als getestet wurde.

---

16. Release Notes

Release Notes sind nicht nur für Endbenutzer wichtig. Sie helfen Betrieb, Support und Security.

Gute Release Notes enthalten:

Vorlage / Textauszug

Version

Datum

betroffene Services

neue Funktionen

geänderte APIs

Datenbankmigrationen

Security-Auswirkungen

Betriebsänderungen

bekannte Risiken

Rollback-Hinweis

Beispiel:

Vorlage / Textauszug

Release 2026.07.0

Betroffene Services
  • order-service 1.4.0
  • billing-service 1.2.3
Änderungen
  • Order Service unterstützt Stornierung von Bestellungen.
  • Billing Service ignoriert stornierte Bestellungen bei Rechnungserstellung.
Datenbank
  • Neue Spalte orders.cancel_reason.
  • Neue Tabelle order_status_history.
Security
  • Neuer Scope order:cancel erforderlich.
Betrieb
  • Neue Metrik order_cancel_total.
  • Dashboard Order Overview erweitert.
Rollback

Rollback der Anwendung ist möglich. Datenbankmigration ist vorwärtskompatibel, Spalten bleiben bestehen.

Das ist deutlich hilfreicher als eine automatische Commit-Liste.

---

17. Rollback und Rollforward

Rollback bedeutet, auf einen vorherigen Stand zurückzugehen. Rollforward bedeutet, mit einer neuen Korrektur weiterzugehen.

In Enterprise-Systemen ist Rollback nicht immer einfach. Besonders Datenbankänderungen können problematisch sein. Wenn eine Migration Daten löscht oder Spalten umbenennt, kann ein alter Service möglicherweise nicht mehr starten.

Deshalb gilt im Lab:

Vorlage / Textauszug

Datenbankmigrationen möglichst vorwärtskompatibel bauen.

Neue Spalten zuerst hinzufügen, später nutzen.

Alte Spalten erst entfernen, wenn kein Service sie braucht.

Feature Flags für riskante Funktionen verwenden.

Rollback-Hinweis im Pull Request verlangen.

Ein Rollback ohne Datenstrategie ist kein echter Rollback.

---

18. Blue-Green und Canary Releases

Nicht jedes Release muss sofort 100 Prozent des Traffics erhalten.

Bei Blue-Green gibt es zwei Umgebungen oder zwei Versionen. Eine Version ist aktiv, die andere wird vorbereitet. Nach Prüfung wird Traffic umgeschaltet.

Bei Canary erhält eine neue Version zunächst nur einen kleinen Teil des Traffics. Wenn Metriken stabil bleiben, wird der Anteil erhöht.

In OpenShift können solche Strategien über Routes, Service Mesh oder progressive Delivery-Werkzeuge umgesetzt werden. Für das Lab reicht zunächst das Verständnis:

Vorlage / Textauszug

Rolling Update: Standard, einfach, meist ausreichend.

Blue-Green: nützlich bei schnellen Umschaltungen.

Canary: nützlich bei risikoreichen Änderungen.

Für den Order Service kann ein Rolling Update genügen. Für ein Customer Portal mit vielen Nutzern kann Canary sinnvoll sein.

---

19. Feature Flags

Feature Flags erlauben, eine Funktion im Code zu haben, aber kontrolliert zu aktivieren.

Beispiel:

yaml
order:
  features:
    cancel-order: true
    new-price-calculation: false

Im Code:

java
package com.seb4u.demo.spring.order.application;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = class="str">"order.features")
public record OrderFeatureProperties(
        boolean cancelOrder,
        boolean newPriceCalculation
) {
}

Feature Flags sind hilfreich, aber gefährlich, wenn sie nicht gepflegt werden. Alte Flags müssen entfernt werden. Sonst entsteht versteckte Komplexität.

---

20. Jenkinsfile als Enterprise-Alternative

Viele Unternehmen nutzen weiterhin Jenkins. Auch das ist realistisch. Ein gutes Lab sollte nicht nur GitHub Actions kennen.

groovy
pipeline {
    agent any

    environment {
        APP_NAME = class="str">'order-service'
        IMAGE = class="str">"registry.example.com/alpine/order-service:${env.GIT_COMMIT}"
    }

    stages {
        stage(class="str">'Checkout') {
            steps {
                checkout scm
            }
        }

        stage(class="str">'Build and Test') {
            steps {
                dir(class="str">'apps/order-service') {
                    sh class="str">'./mvnw clean verify'
                }
            }
        }

        stage(class="str">'Build Image') {
            steps {
                dir(class="str">'apps/order-service') {
                    sh class="str">"docker build -t ${IMAGE} ."
                }
            }
        }

        stage(class="str">'Scan Image') {
            steps {
                sh class="str">"trivy image --exit-code 1 --severity HIGH,CRITICAL ${IMAGE}"
            }
        }

        stage(class="str">'Push Image') {
            when {
                branch class="str">'main'
            }
            steps {
                sh class="str">"docker push ${IMAGE}"
            }
        }
    }
}

Der wichtige Punkt ist nicht das konkrete Tool. Wichtig ist der Prozess: Build, Test, Scan, Versionierung, Push und später GitOps-Deployment.

---

21. Supply Chain Security

Moderne Angriffe betreffen nicht nur den eigenen Code. Sie betreffen Abhängigkeiten, Build-Systeme, Container Images und Registries.

Supply Chain Security fragt:

Vorlage / Textauszug

Welche Abhängigkeiten verwenden wir?

Woher kommen Base Images?

Wer darf Pipelines ändern?

Wer darf Images pushen?

Wurden Images gescannt?

Gibt es eine SBOM?

Sind Artefakte signiert?

SBOM bedeutet Software Bill of Materials. Sie beschreibt, welche Komponenten in einer Software enthalten sind.

Ein Enterprise-Projekt sollte mindestens folgende Regeln haben:

Vorlage / Textauszug

Base Images aus vertrauenswürdiger Registry.

Dependency Scans in CI.

Image Scans vor Veröffentlichung.

Keine Secrets in Build Logs.

Registry-Zugriff über Service Accounts.

Pipeline-Dateien reviewpflichtig.

---

22. Beispiel: Maven Dependency Check im Prozess

bash
./mvnw org.owasp:dependency-check-maven:check

Oder als Maven Plugin:

xml
<plugin>
    <groupId>org.owasp</groupId>
    <artifactId>dependency-check-maven</artifactId>
    <version>10.0.4</version>
    <configuration>
        <failBuildOnCVSS>8</failBuildOnCVSS>
    </configuration>
</plugin>

Die genaue Version muss im echten Projekt gepflegt werden. Wichtig ist die Regel: Kritische Abhängigkeiten dürfen nicht unbemerkt in Produktion gelangen.

---

23. Secrets in CI/CD

Secrets sind in Pipelines besonders gefährlich. Ein falsch gesetztes echo kann ein Token in Logs schreiben. Ein zu breit berechtigter Registry-Token kann Images überschreiben. Ein Cloud-Key in einer Pipeline kann Infrastruktur kompromittieren.

Regeln für das Lab:

Vorlage / Textauszug

Secrets nie im Repository speichern.

Secrets nie in Klartext in Pipeline-Dateien schreiben.

Secrets mit minimalen Rechten ausstellen.

Secrets regelmäßig rotieren.

Pipeline-Logs auf versehentliche Ausgabe prüfen.

Produktions-Secrets getrennt von Dev-Secrets halten.

In GitOps wird zusätzlich beachtet: Kubernetes Secrets sollten nicht unverschlüsselt im GitOps-Repo liegen. Dafür nutzt man zum Beispiel External Secrets, Sealed Secrets oder SOPS.

---

24. Change Management ohne Schwerfälligkeit

Enterprise bedeutet nicht, jede Änderung durch wochenlange Gremien zu schicken. Aber produktionsrelevante Änderungen brauchen Nachvollziehbarkeit.

Ein schlanker Change-Prozess kann so aussehen:

Vorlage / Textauszug

Pull Request beschreibt Änderung.

CI prüft Build, Tests und Scans.

Reviewer prüft Code, Betrieb und Security.

GitOps-PR aktualisiert Image-Version.

Release Notes werden ergänzt.

Argo CD synchronisiert Zielumgebung.

Monitoring wird nach Deployment beobachtet.

Damit ist die Änderung kontrolliert, aber nicht unnötig blockiert.

---

25. Release-Freigabe für Produktion

Für Produktion sollten zusätzliche Bedingungen gelten.

Vorlage / Textauszug

Alle Tests grün.

Image Scan ohne kritische Funde.

Datenbankmigration geprüft.

Rollback- oder Rollforward-Strategie beschrieben.

Runbook aktuell.

Monitoring nach Deployment definiert.

Fachliche Freigabe vorhanden, wenn notwendig.

Die Freigabe kann über einen Pull Request im GitOps-Repo erfolgen. Dieser PR ändert zum Beispiel nur die Image-Version von 1.3.2 auf 1.4.0. Trotzdem ist er wichtig, weil er Produktion verändert.

---

26. Beispiel: GitOps Pull Request für Release

diff
 images:
   - name: registry.example.com/alpine/order-service
-    newTag: 1.3.2
+    newTag: 1.4.0

Dieser kleine Diff ist fachlich groß. Er bedeutet: In Produktion soll eine neue Version laufen. Deshalb gehört in den PR-Text eine Release-Zusammenfassung.

Vorlage / Textauszug

Production Release: order-service 1.4.0

Zweck

Aktiviert Stornierung von Bestellungen für interne Mitarbeiter.

Checks
  • CI grün
  • Integrationstests grün
  • Image Scan ohne HIGH/CRITICAL
  • Staging 24 Stunden stabil
Beobachtung nach Deployment
  • order_cancel_total
  • http_server_requests_seconds
  • outbox_unpublished_events
  • error_rate
Rollback

Image-Tag zurück auf 1.3.2 setzen. Datenbankmigration ist vorwärtskompatibel.

---

27. Betrieb nach dem Deployment

Ein Deployment ist nicht fertig, wenn Argo CD grün zeigt. Danach beginnt die Beobachtung.

Nach einem Release prüft das Team:

Vorlage / Textauszug

Sind Pods ready?

Steigt die Fehlerrate?

Steigt die Latenz?

Wachsen Queues oder Outbox?

Gibt es neue Logfehler?

Funktionieren fachliche Kernprozesse?

Für den Order Service könnten konkrete Checks sein:

bash
oc get pods -n order-prod -l app=order-service
oc rollout status deployment/order-service -n order-prod
oc logs -n order-prod deployment/order-service --tail=100

Zusätzlich prüft man Grafana-Dashboards und Prometheus Alerts.

---

28. Runbook: Fehlgeschlagenes Deployment

Vorlage / Textauszug

Runbook: Deployment order-service fehlgeschlagen

Symptom

Argo CD zeigt OutOfSync oder Degraded. Pods starten nicht oder Readiness schlägt fehl.

Sofortprüfung

argocd app get order-service-prod

oc get pods -n order-prod -l app=order-service

oc describe pod -n order-prod -l app=order-service

oc logs -n order-prod deployment/order-service --tail=200

Vorlage / Textauszug
Häufige Ursachen
  • Image-Tag existiert nicht in Registry
  • Secret fehlt oder hat falschen Key
  • ConfigMap enthält ungültigen Wert
  • Datenbankmigration fehlt
  • Readiness Endpoint falsch
  • Resource Limit zu niedrig
  • NetworkPolicy blockiert Abhängigkeit
Entscheidung

Wenn die Ursache schnell korrigierbar ist, GitOps-Repo korrigieren und Argo CD erneut synchronisieren.

Wenn Produktion beeinträchtigt ist, Image-Version im GitOps-Repo auf vorherigen stabilen Stand zurücksetzen.

Nacharbeit
  • Incident dokumentieren
  • fehlenden Pipeline-Check ergänzen
  • Runbook aktualisieren

Dieses Runbook verbindet GitOps, OpenShift und Anwendung. Es verhindert hektisches Klicken im Cluster.

---

29. Typische Fehler im DevOps-Bereich

Ein häufiger Fehler ist, CI/CD als reine Tool-Frage zu behandeln. Dann wird viel über Jenkins, GitHub Actions oder GitLab CI diskutiert, aber wenig über Release-Qualität.

Ein zweiter Fehler ist, Deployment und Build zu vermischen. Wenn eine Pipeline direkt in Produktion deployt, ohne GitOps-Zustand, fehlt später oft die saubere Nachvollziehbarkeit.

Ein dritter Fehler ist latest als Image-Tag. Damit wird Auditing unnötig schwer.

Ein vierter Fehler sind fehlende Rollback-Strategien. Besonders Datenbankänderungen werden unterschätzt.

Ein fünfter Fehler ist, Security Scans nur einzuführen, aber nie zu pflegen. Wenn Scans ständig ignoriert werden, verlieren sie ihren Wert.

Ein sechster Fehler ist, Pipeline-Secrets zu breit zu berechtigen. Ein CI-Token sollte nicht mehr dürfen als nötig.

---

30. Übung: Release-Prozess für Billing Service entwerfen

Entwirf einen Release-Prozess für den Billing Service.

Beschreibe:

Vorlage / Textauszug
  1. Welche Branches verwendet werden.
  2. Welche CI-Schritte laufen.
  3. Wie das Container Image versioniert wird.
  4. Wie Dev, Test und Prod aktualisiert werden.
  5. Welche Checks vor Produktion Pflicht sind.
  6. Welche Monitoring-Werte nach Deployment geprüft werden.
  7. Wie ein Rollback funktioniert.

Erstelle zusätzlich einen kurzen Pull-Request-Text für die Produktionsfreigabe.

---

31. Musterlösung zur Übung

Vorlage / Textauszug

Der Billing Service wird über Feature-Branches entwickelt. Jeder Feature-Branch öffnet einen Pull Request gegen main. Die CI führt Maven Verify, Unit Tests, Integrationstests mit Testcontainers, Dependency Scan, Container Build und Image Scan aus.

Bei Merge nach main wird ein Image mit Git SHA und semantischer Version gebaut. Das Image wird in die Registry gepusht.

Dev wird automatisch über GitOps aktualisiert. Test wird über einen Pull Request im GitOps-Repo auf dieselbe Image-Version gehoben. Produktion wird erst nach fachlicher Abnahme und stabiler Testphase aktualisiert.

Vor Produktion müssen CI, Image Scan, Datenbankmigration, Runbook, Release Notes und Monitoring-Checks abgeschlossen sein.

Nach Deployment werden Fehlerrate, Latenz, Consumer Lag, Dead Letter Queue, Rechnungsfehler und Datenbankverbindungen geprüft.

Rollback erfolgt über Änderung des Image-Tags im GitOps-Repo auf die vorherige stabile Version. Datenbankmigrationen werden vorwärtskompatibel gebaut, damit der alte Service weiterhin mit dem Schema arbeiten kann.

Beispiel für den Produktions-PR:

Vorlage / Textauszug

Production Release: billing-service 1.2.3

Zweck

Verbessert Verarbeitung von OrderCreatedEvents und ergänzt idempotente Rechnungsprüfung.

Nachweise
  • CI grün
  • Integrationstests mit Kafka und PostgreSQL grün
  • Image Scan ohne kritische Findings
  • Staging seit 24 Stunden stabil
  • Runbook aktualisiert
Monitoring nach Deployment
  • billing_event_processing_total
  • billing_event_failed_total
  • kafka_consumer_lag
  • dead_letter_queue_size
  • http_server_requests_seconds
Rollback

Image-Tag zurück auf 1.2.2 setzen. Migration ist vorwärtskompatibel.

---

32. Merksätze

Vorlage / Textauszug

DevOps ist ein Betriebsmodell, nicht nur eine Pipeline.

Git ist die zentrale Wahrheit für Code, Infrastruktur und Dokumentation.

CI baut und prüft Artefakte. GitOps deployt gewünschte Zustände.

Ein Image muss eindeutig versioniert sein. latest ist kein Release-Konzept.

Ein Pull Request ist eine Qualitätsgrenze.

Ein Deployment ist erst abgeschlossen, wenn es beobachtet wurde.

Rollback ohne Datenstrategie ist gefährlich.

Release Management verbindet Entwicklung, Betrieb, Security und Fachlichkeit.

Codex und Claude Code im Enterprise-Projekt

1. Warum AI-Werkzeuge im Enterprise-Projekt anders geführt werden müssen

Codex und Claude Code können in einem Enterprise-Projekt sehr hilfreich sein, aber sie ersetzen keine Architekturverantwortung. Sie sind Werkzeuge, die Code erklären, Varianten erzeugen, Refactorings vorbereiten, Tests schreiben, Buildfehler analysieren und Dokumentation strukturieren können. Sie wissen aber nicht automatisch, welche Sicherheitsregeln, Betriebsprozesse, Compliance-Vorgaben, Namenskonventionen und Plattformstandards im Unternehmen gelten.

Der wichtigste Unterschied zu einer kleinen Demo ist der Kontext. In einem einfachen Projekt reicht oft ein Prompt wie: „Erstelle mir einen Spring Boot Service.“ In einem Enterprise-Projekt ist das zu ungenau. Ein Service muss zur Zielarchitektur passen, die Paketstruktur einhalten, Authentifizierung berücksichtigen, Metriken liefern, Fehler sauber behandeln, Deployment-Artefakte mitbringen und in GitOps integrierbar sein.

AI-Werkzeuge müssen deshalb wie ein sehr schneller Junior- bis Mid-Level-Mitarbeiter behandelt werden: nützlich, produktiv, aber kontrollbedürftig. Sie brauchen klare Aufgaben, klare Grenzen, konkrete Dateien, Qualitätsregeln und Review.

Vorlage / Textauszug

AI im Enterprise-Projekt ist kein Autopilot.

AI ist ein Beschleuniger für klar geführte Arbeit.

Wenn die Vorgaben schlecht sind, erzeugt das Werkzeug oft scheinbar professionelle Ergebnisse, die später teuer werden: falsche Paketstruktur, fehlende Tests, unsichere Defaults, zu große Klassen, unklare Transaktionen, generische Dokumentation oder Deployment-YAML ohne echte Betriebsreife.

---

2. Rolle von Codex im Lab

Codex eignet sich besonders für Arbeit direkt am Code. Im Lab kann Codex genutzt werden, um konkrete Dateien zu erstellen oder zu ändern, Tests zu ergänzen, Buildfehler zu analysieren und Refactorings Schritt für Schritt vorzubereiten.

Typische Aufgaben für Codex:

Vorlage / Textauszug
  • Java-Klassen erstellen
  • Spring Boot Controller ergänzen
  • Tests schreiben
  • Maven-Fehler analysieren
  • DTOs und Mapper erzeugen
  • kleine Refactorings durchführen
  • Dockerfile prüfen
  • YAML-Manifeste syntaktisch korrigieren
  • Repository-Struktur aufräumen
  • README aus tatsächlichem Projektstand aktualisieren

Codex sollte jedoch nicht ohne Kontrolle ganze Architekturen entscheiden. Die Entscheidung, ob ein Service Spring Boot, Quarkus oder Micronaut nutzt, ob Kafka oder RabbitMQ verwendet wird, ob ein Workload auf VM oder OpenShift läuft und wie Datenbesitz geschnitten wird, bleibt eine Architekturentscheidung.

Codex ist stark, wenn die Aufgabe nah an konkreten Dateien liegt:

Vorlage / Textauszug

Ändere diese Klasse.

Ergänze diesen Test.

Passe diese Pipeline an.

Erkläre diesen Buildfehler.

Füge diese Validierung ein.

Codex ist schwächer, wenn die Aufgabe zu offen ist:

Vorlage / Textauszug

Baue mir eine komplette Enterprise-Plattform.

Mach alles sicher.

Mach es production-ready.

Solche Prompts sind zu breit. Sie erzeugen oft generische Ergebnisse.

---

3. Rolle von Claude Code im Lab

Claude Code eignet sich besonders für größere Codebasis-Navigation, Architekturverständnis, mehrstufige Änderungen und erklärende Aufgaben. Es kann hilfreich sein, wenn mehrere Dateien zusammen betrachtet werden müssen oder wenn ein Refactoring über mehrere Module vorbereitet wird.

Typische Aufgaben für Claude Code:

Vorlage / Textauszug
  • vorhandene Projektstruktur analysieren
  • Architekturprobleme in mehreren Modulen erkennen
  • Refactoring-Plan schreiben
  • große Pull Requests vorbereiten
  • technische Schulden dokumentieren
  • Modulgrenzen erklären
  • ADRs aus Codeänderungen ableiten
  • Migrationspfade beschreiben
  • Testszenarien aus Use Cases ableiten

Claude Code kann besonders nützlich sein, wenn nicht nur eine Datei geändert wird, sondern ein Zusammenhang verstanden werden muss. Beispiel: Der Order Service speichert Bestellungen, schreibt Outbox Events, ein Publisher liest diese Events und ein Billing Consumer verarbeitet sie. Wenn eine Änderung am Event Payload nötig ist, betrifft das mehrere Stellen. Hier hilft ein Werkzeug, das die Zusammenhänge systematisch betrachten kann.

Trotzdem gilt auch hier: Claude Code entscheidet nicht selbst, welche Fachlogik richtig ist. Die fachliche Wahrheit kommt aus Anforderungen, ADRs, Tests und Reviews.

---

4. Gemeinsames Arbeitsmodell

Im Lab verwenden wir Codex und Claude Code nicht konkurrierend, sondern ergänzend.

Skizze / Textauszug
Architekturfrage
↓
Claude Code oder ChatGPT für Analyse, Struktur, ADR und Plan
↓
Codex für konkrete Codeänderung
↓
Tests und Build lokal/CI ausführen
↓
Review durch Mensch
↓
GitOps-Deployment
↓
Monitoring und Betrieb prüfen

Das Ziel ist nicht, möglichst viel automatisch zu erzeugen. Das Ziel ist, Arbeitsschritte nachvollziehbar zu machen.

Ein gutes Arbeitsmodell sieht so aus:

Vorlage / Textauszug
  1. Mensch beschreibt Ziel und Grenzen.
  2. AI schlägt Plan vor.
  3. Mensch prüft Plan.
  4. AI ändert kleine, kontrollierbare Einheiten.
  5. Tests laufen.
  6. Mensch reviewed.
  7. Dokumentation wird aktualisiert.
  8. Änderung wird gemergt.

Ein schlechtes Arbeitsmodell sieht so aus:

Vorlage / Textauszug
  1. AI soll alles bauen.
  2. Ergebnis wird nicht geprüft.
  3. Fehler fallen erst beim Deployment auf.
  4. Dokumentation passt nicht zum Code.
  5. Betrieb findet keine Ursache.

---

5. Prompt-Regeln für Enterprise-Code

Ein guter Prompt beschreibt nicht nur das gewünschte Ergebnis, sondern auch den Kontext und die Qualitätsgrenzen.

Schlechter Prompt:

Vorlage / Textauszug

Erstelle einen Order Service.

Besserer Prompt:

Vorlage / Textauszug

Erstelle im bestehenden Maven-Modul order-service einen Spring Boot REST Controller für das Anlegen einer Bestellung.

Rahmen:

  • Java 21
  • Package: com.seb4u.demo.spring.order
  • Controller liegt in api
  • Use Case liegt in application
  • Domänenobjekte liegen in domain
  • keine Datenbanklogik im Controller
  • Validierung über DTO und Anwendungsschicht
  • Fehler als ProblemDetail zurückgeben
  • Tests für gültige und ungültige Requests ergänzen
  • bestehende Paketstruktur nicht verändern

Dieser Prompt führt das Werkzeug. Er verhindert, dass zufällige Struktur entsteht.

---

6. Kontextdateien für AI-Arbeit

Ein Enterprise-Projekt sollte AI-Werkzeugen nicht jedes Mal neu erklärt werden. Stattdessen gibt es Kontextdateien.

Sinnvolle Dateien:

Vorlage / Textauszug

AI_PROJECT_RULES.md

ARCHITECTURE.md

CODING_STANDARDS.md

SECURITY_RULES.md

TESTING_RULES.md

DEPLOYMENT_RULES.md

OBSERVABILITY_RULES.md

Diese Dateien enthalten nicht Marketingtext, sondern konkrete Regeln.

Beispiel AI_PROJECT_RULES.md:

Vorlage / Textauszug

AI Project Rules

Sprache und Stil

Antworten, Kommentare und Dokumentation im Projekt sind auf Deutsch, außer technische Bezeichner, Code und externe API-Namen.

Java Packages

Alle Java-Packages beginnen mit:

com.seb4u.demo

Vorlage / Textauszug

Spring-Services verwenden:

com.seb4u.demo.spring.<bounded-context>

Vorlage / Textauszug

Quarkus-Services verwenden:

com.seb4u.demo.quarkus.<bounded-context>

Vorlage / Textauszug
Architektur

Controller enthalten keine Fachlogik.

Application Services koordinieren Use Cases.

Domain-Klassen enthalten fachliche Regeln.

Infrastructure-Klassen enthalten Datenbank-, Messaging- und Plattformdetails.

Tests

Neue Fachlogik braucht Unit Tests.

Neue Datenbankintegration braucht Integrationstests mit Testcontainers.

Neue REST-Endpunkte brauchen Controller- oder API-Tests.

Nicht erlaubt
  • Secrets in Code
  • direkte Datenbankzugriffe aus Controller-Klassen
  • generische catch(Exception)-Blöcke ohne Behandlung
  • neue Frameworks ohne ADR
  • Copy/Paste-Dokumentation ohne Bezug zum Code

Mit solchen Regeln arbeitet AI deutlich stabiler.

---

7. Beispiel: Codex-Aufgabe für einen Controller

Eine präzise Codex-Aufgabe könnte so aussehen:

Vorlage / Textauszug

Aufgabe:

Ergänze im Modul apps/order-service einen REST-Endpunkt POST /api/orders.

Kontext:

  • Java 21
  • Spring Boot
  • Package: com.seb4u.demo.spring.order
  • vorhandener Use Case: CreateOrderUseCase
  • Request DTO: CreateOrderRequest neu erstellen
  • Response DTO: CreateOrderResponse neu erstellen

Regeln:

  • Controller darf keine Datenbanklogik enthalten
  • Controller ruft nur den Use Case auf
  • Bean Validation verwenden
  • Fehler nicht mit stack trace an Client geben
  • Tests ergänzen

Akzeptanzkriterien:

  • mvn test läuft erfolgreich
  • ungültiger Request liefert HTTP 400
  • gültiger Request liefert HTTP 201
  • Location Header wird gesetzt

Daraus kann Codex zielgerichtet arbeiten.

Beispielcode für das gewünschte Ergebnis:

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderResult;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    public ResponseEntity<CreateOrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request
    ) {
        CreateOrderCommand command = request.toCommand();
        CreateOrderResult result = createOrderUseCase.createOrder(command);

        URI location = URI.create(class="str">"/api/orders/" + result.orderId());

        return ResponseEntity
                .created(location)
                .body(new CreateOrderResponse(result.orderId(), result.status()));
    }
}

Wichtig: Der Prompt enthält nicht nur „schreibe Code“, sondern auch Struktur, Regeln und Akzeptanzkriterien.

---

8. Beispiel: Claude-Code-Aufgabe für Refactoring

Wenn eine Klasse zu groß geworden ist, sollte Claude Code nicht einfach „mach schöner“ bekommen.

Schlechter Prompt:

Vorlage / Textauszug

Refactore diese Monsterklasse.

Besserer Prompt:

Vorlage / Textauszug

Analysiere die Klasse LegacyOrderImportService.

Ziel:

Wir wollen fachliche Validierung, Dateiparsing, Fehlerprotokollierung und Persistenz trennen.

Bitte liefere zuerst nur einen Refactoring-Plan:

  • aktuelle Verantwortlichkeiten
  • erkannte Code Smells
  • vorgeschlagene Zielklassen
  • Reihenfolge der sicheren Änderungen
  • benötigte Tests
  • Risiken

Ändere noch keinen Code.

Erst nach dem Plan folgt die Codeänderung.

Mögliche Zielstruktur:

Skizze / Textauszug
file-processing-service/
├── application/
│   ├── ImportPartnerFileUseCase.java
│   └── PartnerFileImportService.java
├── domain/
│   ├── PartnerRecord.java
│   ├── PartnerRecordValidator.java
│   └── ImportError.java
├── infrastructure/
│   ├── csv/PartnerCsvParser.java
│   ├── persistence/PartnerImportRepository.java
│   └── storage/ObjectStorageClient.java
└── observability/
    └── ImportMetrics.java

So wird Refactoring kontrollierbar.

---

9. AI bei Architekturentscheidungen

AI kann ADRs vorbereiten, aber nicht allein entscheiden.

Ein guter ADR-Prompt:

Vorlage / Textauszug

Erstelle einen ADR-Entwurf für die Entscheidung, das Outbox Pattern im Order Service zu verwenden.

Kontext:

  • Order Service speichert Bestellungen in PostgreSQL
  • Billing Service verarbeitet OrderCreated Events
  • Broker kann zeitweise nicht erreichbar sein
  • Datenbanktransaktion und Event-Publishing dürfen nicht auseinanderlaufen

Vergleiche:

  • direktes Kafka-Publishing in derselben Methode
  • synchroner REST-Aufruf an Billing
  • Outbox Pattern

ADR-Struktur:

  • Status
  • Kontext
  • Entscheidung
  • Alternativen
  • Konsequenzen
  • Betriebsaspekte
  • Testnachweise

Der Mensch prüft danach:

Vorlage / Textauszug

Ist der Kontext richtig?

Sind Alternativen fair bewertet?

Sind Konsequenzen ehrlich?

Sind Betriebsaspekte konkret?

Passt die Entscheidung zur Zielarchitektur?

Ein ADR darf nicht nur gut klingen. Er muss später die Umsetzung erklären.

---

10. AI bei Tests

AI ist sehr nützlich beim Schreiben von Tests, aber auch hier braucht sie klare Grenzen.

Schlechter Prompt:

Vorlage / Textauszug

Schreibe Tests.

Besser:

Vorlage / Textauszug

Ergänze Tests für OrderApplicationService.

Zu testen:

  • gültige Bestellung wird gespeichert
  • Outbox Event wird erzeugt
  • leere Item-Liste wird abgelehnt
  • fehlende customerId wird abgelehnt
  • Repository-Fehler führt zu Rollback

Regeln:

  • JUnit 5
  • AssertJ
  • Mockito für Unit Test
  • keine Spring Context Tests für reine Anwendungsschicht
  • sprechende Testnamen

Beispiel:

java
package com.seb4u.demo.spring.order.application;

import com.seb4u.demo.spring.order.domain.OrderRepository;
import com.seb4u.demo.spring.order.infrastructure.messaging.OutboxPublisher;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
class OrderApplicationServiceTest {

    @Mock
    OrderRepository orderRepository;

    @Mock
    OutboxPublisher outboxPublisher;

    Clock clock = Clock.fixed(Instant.parse(class="str">"2026-01-01T10:00:00Z"), ZoneOffset.UTC);

    @Test
    void rejectsOrderWithoutItems() {
        OrderApplicationService service = new OrderApplicationService(
                orderRepository,
                outboxPublisher,
                clock
        );

        CreateOrderCommand command = new CreateOrderCommand(class="str">"customer-100", List.of());

        assertThatThrownBy(() -> service.createOrder(command))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessageContaining(class="str">"at least one item");
    }

    @Test
    void storesOutboxEventWhenOrderIsCreated() {
        // Dieser Test wird im echten Projekt mit Repository-Stub oder Integrationstest vervollständigt.
        // Wichtig ist die Testabsicht: Bestellung und Outbox gehören fachlich zusammen.
    }
}

AI kann solche Tests schnell vorbereiten. Der Mensch muss prüfen, ob die Tests wirklich relevante Risiken abdecken.

---

11. AI bei Kubernetes und OpenShift

AI kann YAML erzeugen, aber Kubernetes-YAML ist gefährlich, wenn Defaults fehlen.

Ein Deployment ohne Resource Limits, Probes, SecurityContext und Labels ist für ein Enterprise-Lab unvollständig.

Guter Prompt:

Vorlage / Textauszug

Erstelle Kubernetes/OpenShift Manifeste für order-service.

Erforderlich:

  • Namespace wird nicht erstellt, existiert bereits
  • Deployment mit 2 Replicas
  • Service
  • Route für OpenShift
  • ConfigMap für nicht-sensitive Konfiguration
  • Secret nur als Platzhalter, keine echten Werte
  • Resource Requests und Limits
  • Readiness und Liveness Probes
  • SecurityContext ohne root
  • Labels: app, component, part-of, owner
  • keine ClusterRole
  • NetworkPolicy separat vorschlagen

Beispiel für Sicherheitskontext:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        component: backend
        part-of: order-platform
        owner: team-order
    spec:
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.2.0
          ports:
            - containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
          resources:
            requests:
              cpu: class="str">"250m"
              memory: class="str">"512Mi"
            limits:
              cpu: class="str">"1000m"
              memory: class="str">"1024Mi"

AI-generiertes YAML muss immer gegen Plattformregeln geprüft werden.

---

12. AI bei Dokumentation

AI kann Dokumentation schnell schreiben, aber sie neigt zu generischen Formulierungen. Genau das war im bisherigen Projekt problematisch: Absätze klangen ordentlich, waren aber wiederholt und austauschbar.

Deshalb gilt:

Vorlage / Textauszug

Dokumentation darf nicht nur erklären, was allgemein richtig ist.

Sie muss erklären, was in diesem Projekt konkret gilt.

Schlechter Dokumentationssatz:

Vorlage / Textauszug

Monitoring ist wichtig, um Systeme zu überwachen und Fehler zu erkennen.

Besser:

Vorlage / Textauszug

Der Order Service exportiert Metriken über /actuator/prometheus. Prometheus sammelt diese Metriken über einen ServiceMonitor im Namespace order-prod. Ein Alert wird ausgelöst, wenn die Fehlerrate über fünf Minuten mehr als zwei Prozent beträgt oder wenn die Outbox länger als zehn Minuten unveröffentlichte Events enthält.

Der zweite Satz ist prüfbar. Er nennt Service, Endpoint, Namespace, Metrikidee und konkretes Fehlerbild.

---

13. Qualitätsprüfung gegen AI-Fülltext

Damit kein künstlicher Inhalt entsteht, braucht das Projekt eine Prüfung gegen Wiederholungsmuster.

Nicht erlaubt:

Vorlage / Textauszug
  • gleiche Absätze mit ausgetauschtem Schwerpunkt
  • nummerierte Fülltabellen ohne echte Unterschiede
  • Kapitel mit fast identischem Aufbau und gleichem Text
  • generische Listen ohne Projektbezug
  • Glossar als Ersatz für Erklärung
  • Praxisdurchläufe als angehängte Serienblöcke

Erlaubt:

Vorlage / Textauszug
  • wiederkehrende Kapitelstruktur
  • unterschiedliche fachliche Inhalte
  • konkrete Beispiele
  • projektbezogene Codeblöcke
  • echte Runbooks
  • spezifische Fehlerbilder
  • klare Akzeptanzkriterien

Eine technische Prüfung kann einfache Muster finden.

Beispiel:

bash
#!/usr/bin/env bash
set -euo pipefail

TARGET_DIR=class="str">"docs"

for pattern in \
  class="str">"integrierte Vertiefung" \
  class="str">"Praxisdurchlauf" \
  class="str">"Entscheidung 2" \
  class="str">"Risiko 2" \
  class="str">"Warum das im Lab wichtig ist"; do
  if grep -R class="str">"$pattern" class="str">"$TARGET_DIR"; then
    echo class="str">"Unzulässiges Muster gefunden: $pattern"
    exit 1
  fi
done

echo class="str">"Keine bekannten Füllmuster gefunden."

Diese Prüfung ersetzt kein Review, aber sie verhindert bekannte Fehler.

---

14. AI und Security-Grenzen

AI darf keine echten Secrets, Tokens, Passwörter oder internen Zugangsdaten verarbeiten. Für das Lab werden immer Platzhalter verwendet.

Richtig:

yaml
apiVersion: v1
kind: Secret
metadata:
  name: order-db-secret
stringData:
  username: CHANGE_ME
  password: CHANGE_ME

Besser in echten Plattformen:

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: order-db-secret
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-store
    kind: ClusterSecretStore
  target:
    name: order-db-secret
  data:
    - secretKey: username
      remoteRef:
        key: prod/order/db
        property: username
    - secretKey: password
      remoteRef:
        key: prod/order/db
        property: password

AI darf beim Schreiben helfen, aber echte Werte gehören nicht in Prompts.

---

15. AI im Pull-Request-Prozess

AI kann Pull Requests vorbereiten und Reviews unterstützen.

Ein sinnvoller PR-Prozess:

Vorlage / Textauszug
  1. Entwickler erstellt Aufgabe mit Akzeptanzkriterien.
  2. AI unterstützt bei Code oder Tests.
  3. Entwickler prüft lokal.
  4. CI führt Build, Tests und Scans aus.
  5. AI kann eine PR-Zusammenfassung vorschlagen.
  6. Menschliche Reviewer prüfen Architektur, Fachlichkeit und Sicherheit.
  7. Merge erfolgt erst nach grünen Checks.

AI-Zusammenfassung für PRs sollte konkret sein:

Vorlage / Textauszug
Änderung

Dieser Pull Request ergänzt den Endpunkt POST /api/orders im Order Service.

Technische Änderungen
  • CreateOrderRequest ergänzt
  • OrderController ergänzt
  • Bean Validation aktiviert
  • Controller-Test für HTTP 201 und HTTP 400 ergänzt
Nicht enthalten
  • keine Änderung am Billing Service
  • keine Änderung am Event Payload
  • keine neue Datenbankmigration
Nachweise
  • mvn test erfolgreich
  • Controller-Test deckt gültige und ungültige Requests ab

Das ist besser als eine generische Zusammenfassung wie „This PR improves order handling“.

---

16. AI bei Fehlersuche

AI kann Logs und Fehlermeldungen erklären, wenn man ihr ausreichend Kontext gibt. Wichtig ist, keine sensiblen Daten zu kopieren.

Guter Prompt:

Vorlage / Textauszug

Analysiere diesen Buildfehler.

Kontext:

  • Modul: order-service
  • Java 21
  • Maven Multi Module Projekt
  • Fehler tritt seit Änderung an OrderControllerTest auf
  • relevante pom.xml und Fehlermeldung folgen

Bitte:

  • Ursache erklären
  • wahrscheinlichste Datei nennen
  • minimalen Fix vorschlagen
  • keine komplette Projektstruktur neu erfinden

Bei Kubernetes-Fehlern:

Vorlage / Textauszug

Analysiere diesen OpenShift-Fehler.

Kontext:

  • Namespace: order-prod
  • Deployment: order-service
  • Pod startet, wird aber nicht ready
  • Readiness Endpoint /actuator/health/readiness
  • Logs und oc describe Ausschnitt folgen

Bitte:

  • mögliche Ursachen priorisieren
  • Prüfkommandos nennen
  • keine Secrets ausgeben

So wird AI Teil der Fehlersuche, aber nicht Ersatz für Betriebswissen.

---

17. AI und Refactoring alter Enterprise-Anwendungen

Bei Legacy-Code darf AI nicht einfach modernisieren, ohne Verhalten zu sichern. Zuerst braucht es Charakterisierungstests.

Sicheres Vorgehen:

Vorlage / Textauszug
  1. bestehendes Verhalten beschreiben
  2. Tests für aktuelles Verhalten ergänzen
  3. kleine Extraktionen durchführen
  4. nach jedem Schritt Tests ausführen
  5. Fachlogik nicht gleichzeitig ändern
  6. technische Verbesserung und fachliche Änderung trennen

Prompt:

Vorlage / Textauszug

Analysiere diese Legacy-Methode.

Ziel:

Noch kein Refactoring durchführen.

Bitte liefere:

  • Verantwortlichkeiten der Methode
  • Seiteneffekte
  • externe Abhängigkeiten
  • mögliche Testfälle
  • sichere Extraktionsschritte
  • Risiken bei Änderung

Erst danach wird geändert.

---

18. AI bei Migration von VM nach OpenShift

Eine Migration ist mehr als Container bauen.

AI kann helfen, eine Migrationscheckliste zu erstellen:

Vorlage / Textauszug
  • Welche Ports nutzt die Anwendung?
  • Welche Dateien schreibt sie lokal?
  • Welche Umgebungsvariablen braucht sie?
  • Welche Secrets verwendet sie?
  • Welche CronJobs oder Scheduler gibt es?
  • Welche Logs werden geschrieben?
  • Welche Health Checks existieren?
  • Welche Datenbankverbindungen gibt es?
  • Welche Firewall-Regeln werden benötigt?
  • Welche Betriebsprozesse ändern sich?

Prompt:

Vorlage / Textauszug

Erstelle einen Migrationsplan für reporting-service von VM nach OpenShift.

Kontext:

  • Anwendung läuft aktuell als systemd-Service
  • schreibt Reports nach /data/reports
  • liest PostgreSQL Orders und Billing
  • läuft täglich nachts
  • benötigt 2 GB RAM
  • Logs liegen aktuell unter /var/log/reporting-service

Ziel:

  • Containerisierung prüfen
  • Alternative als Kubernetes CronJob bewerten
  • Storage-Optionen vergleichen
  • Risiken nennen
  • schrittweisen Plan erstellen

Das Ergebnis muss danach mit Betrieb, Security und Fachbereich geprüft werden.

---

19. AI und Lernprojekt-Dokumentation

Für dieses Lernprojekt ist wichtig: AI darf beim Schreiben helfen, aber der Inhalt muss wie ein echtes Fachbuch aufgebaut sein.

Regel:

Vorlage / Textauszug

Ein Abschnitt ist nur gültig, wenn er fachlich etwas Eigenes erklärt.

Beispiel Security:

Vorlage / Textauszug

RBAC erklärt Rollen und Rechte.

Hardening erklärt Angriffsfläche und sichere Laufzeit.

Audit erklärt Nachvollziehbarkeit.

Incident Response erklärt Reaktion auf Störungen.

Zero Trust erklärt Misstrauen als Standardprinzip.

Diese Themen dürfen nicht denselben Absatz mit ausgetauschtem Begriff bekommen. Jedes Thema braucht eigene Beispiele, eigene Fehlerbilder und eigene Artefakte.

---

20. AI-Runbook: Falsch generierter Inhalt

Wenn AI wieder künstliche Wiederholungen erzeugt, wird nicht weiter darauf aufgebaut.

Vorlage / Textauszug

Runbook: AI hat Fülltext erzeugt

Symptom

Mehrere Absätze sind fast identisch und unterscheiden sich nur durch einzelne Begriffe.

Sofortmaßnahme

Nicht reparieren durch Suchen/Ersetzen.

Nicht erweitern.

Abschnitt verwerfen.

Analyse

Prüfen:

  • War der Prompt zu generisch?
  • Wurde Umfang statt Qualität gefordert?
  • Wurden konkrete Kapitelziele genannt?
  • Gab es Akzeptanzkriterien?
  • Gab es Beispiele für unerlaubte Muster?
Korrektur

Kapitel neu schreiben mit:

  • eigenem Ziel
  • eigenem Szenario
  • eigenem Code
  • eigenem Fehlerbild
  • eigener Übung
  • eigener Musterlösung
Nachweis

Vor Aufnahme ins ZIP prüfen:

  • keine bekannten Füllmuster
  • keine Copy/Paste-Absätze
  • mindestens ein konkreter Projektbezug

Dieses Runbook ist bewusst Teil des Lernprojekts. Es schützt die Qualität.

---

21. Übung: AI-Aufgabe richtig formulieren

Formuliere einen guten Prompt für Codex oder Claude Code.

Aufgabe:

Vorlage / Textauszug

Der Billing Service soll ein Event OrderCreated konsumieren und daraus einen Rechnungsentwurf erzeugen.

Der Prompt soll enthalten:

Vorlage / Textauszug
  • Projektkontext
  • Technologie
  • Paketstruktur
  • gewünschte Dateien
  • fachliche Regeln
  • Fehlerbehandlung
  • Tests
  • Akzeptanzkriterien
  • Grenzen, was nicht geändert werden darf

---

22. Musterlösung zur Übung

Vorlage / Textauszug

Aufgabe für Codex:

Ergänze im Modul apps/billing-service einen Kafka Consumer für das Event OrderCreated.

Kontext:

  • Java 21
  • Spring Boot
  • Package: com.seb4u.demo.spring.billing
  • Billing Service erzeugt Rechnungsentwürfe, aber versendet keine Rechnungen
  • Event kommt vom Order Service
  • Event enthält orderId, customerId, totalAmount und createdAt

Gewünschte Struktur:

  • api bleibt unverändert
  • Consumer liegt in infrastructure.messaging
  • Application Use Case liegt in application
  • Domänenobjekt InvoiceDraft liegt in domain
  • Repository Interface liegt in domain

Fachliche Regeln:

  • Für jedes OrderCreated Event wird maximal ein InvoiceDraft erzeugt
  • Verarbeitung muss idempotent sein
  • doppelte Events dürfen keine doppelte Rechnung erzeugen
  • bei fachlich ungültigem Event wird ein Fehler geloggt und das Event nicht verarbeitet

Fehlerbehandlung:

  • technische Fehler sollen Retry ermöglichen
  • dauerhaft ungültige Events sollen später in eine Dead Letter Queue gehen
  • keine Secrets oder personenbezogenen Daten loggen

Tests:

  • Unit Test für Use Case
  • Test für doppelte Event-Verarbeitung
  • Test für ungültiges Event
  • Integrationstest für Consumer kann als TODO dokumentiert werden, falls Testcontainer-Setup fehlt

Nicht ändern:

  • keine Änderung am Order Service
  • keine Änderung am Event-Namen
  • keine Änderung an bestehenden Datenbanktabellen ohne Migration

Akzeptanzkriterien:

  • mvn test erfolgreich
  • neuer Use Case ist getestet
  • Consumer delegiert an Anwendungsschicht
  • keine Fachlogik im Consumer

Das ist ein brauchbarer Enterprise-Prompt. Er führt das Werkzeug, ohne die Kontrolle abzugeben.

---

23. Merksätze

Vorlage / Textauszug

AI ersetzt keine Architekturverantwortung.

Codex ist stark bei konkreten Codeänderungen.

Claude Code ist stark bei Analyse, Refactoring-Planung und größeren Zusammenhängen.

Je konkreter Kontext, Grenzen und Akzeptanzkriterien sind, desto besser wird das Ergebnis.

AI-generierte Dokumentation muss projektbezogen, prüfbar und nicht generisch sein.

Secrets gehören niemals in Prompts.

Jede AI-Änderung braucht Tests, Review und Nachweis.

Wenn AI Fülltext erzeugt, wird der Abschnitt verworfen und neu geschrieben.

Teil G: Komplettes Beispielprojekt und große Codebasis

Teil G: Komplettes Beispielprojekt und große Codebasis

PlanZielbildBuildUmsetzungRunBetriebTeil G: Komplettes Beispielprojekt und große Codebasis: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 13, Teil 36.

Komplettes Beispielprojekt

Order-to-Billing-End-to-End
Kompakte Themenübersicht
PortalOrder APIPostgresOutboxKafkaBillingRechnungReportingCSV/PDFwritepublishconsumeexport

1. Ziel dieses Teils

In den bisherigen Teilen wurden Zielbild, Infrastruktur, Cloud, VM/Bare Metal, OpenShift, Security, moderne Anwendungen, Datenintegration, Observability, Backup, DevOps und AI-Unterstützung getrennt erklärt. In diesem Teil werden diese Bausteine zu einem zusammenhängenden Beispielprojekt verbunden.

Das Ziel ist nicht, eine winzige Demo mit einem Controller und einer Datenbank zu bauen. Das Ziel ist ein realistisches Enterprise-Skelett, das zeigt, wie mehrere Anwendungen, Plattformdefinitionen, Infrastrukturartefakte, Dokumentation und Betriebsunterlagen zusammengehören.

Das Beispielprojekt heißt:

Vorlage / Textauszug

modern-hybrid-enterprise-platform-lab

Es bildet eine hybride Plattform für die Alpine Retail & Services Group ab. Neue Anwendungen laufen auf OpenShift. Datenbanken, File-Verarbeitung und bestimmte Spezialprozesse können auf VM oder Bare Metal bleiben. Cloud-Dienste werden gezielt für Object Storage, Testumgebungen oder externe Integrationen verwendet.

Der wichtigste Gedanke dieses Teils lautet:

Vorlage / Textauszug

Ein Enterprise-Projekt besteht nicht aus einer Anwendung.

Es besteht aus Code, Infrastruktur, Security, Deployment, Betrieb, Dokumentation, Tests und Entscheidungen.

---

2. Gesamtstruktur des Repositories

Das Repository wird bewusst so aufgebaut, dass Verantwortungen sichtbar bleiben. Anwendungen liegen nicht neben zufälligen Skripten. Plattformdefinitionen werden nicht in App-Ordnern versteckt. Betriebsdokumentation liegt nicht irgendwo in einem Wiki, das niemand versioniert.

Skizze / Textauszug
modern-hybrid-enterprise-platform-runtime-lab/
├── README.md
├── docs/
│   ├── architecture/
│   │   ├── target-architecture.md
│   │   ├── system-context.md
│   │   ├── data-flow.md
│   │   └── network-zones.md
│   ├── adr/
│   │   ├── 0001-hybrid-platform.md
│   │   ├── 0002-openshift-for-modern-services.md
│   │   ├── 0003-outbox-for-domain-events.md
│   │   ├── 0004-keycloak-for-identity.md
│   │   └── 0005-gitops-as-deployment-source.md
│   ├── runbooks/
│   │   ├── order-service-unavailable.md
│   │   ├── outbox-backlog.md
│   │   ├── database-restore-orders.md
│   │   ├── keycloak-login-failure.md
│   │   └── failed-release-rollback.md
│   ├── security/
│   │   ├── threat-model.md
│   │   ├── rbac-model.md
│   │   ├── secret-handling.md
│   │   └── audit-events.md
│   └── operations/
│       ├── slo-overview.md
│       ├── backup-plan.md
│       ├── incident-process.md
│       └── release-calendar.md
├── apps/
│   ├── customer-portal/
│   ├── admin-portal/
│   ├── order-service/
│   ├── billing-service/
│   ├── reporting-service/
│   └── file-processing-service/
├── platform/
│   ├── namespaces/
│   ├── network-policies/
│   ├── argocd/
│   ├── kustomize/
│   ├── helm/
│   └── observability/
├── infrastructure/
│   ├── cloud/
│   │   ├── object-storage/
│   │   └── network/
│   ├── vm/
│   │   ├── reporting/
│   │   └── file-processing/
│   ├── bare-metal/
│   │   ├── postgres/
│   │   └── backup-repository/
│   └── ansible/
├── security/
│   ├── keycloak/
│   ├── external-secrets/
│   ├── rbac/
│   └── policies/
├── ci-cd/
│   ├── github-actions/
│   ├── gitlab-ci/
│   └── jenkins/
└── examples/
    ├── local-compose/
    ├── api-requests/
    └── test-data/

Diese Struktur ist absichtlich umfangreich. Sie zwingt dazu, jede Verantwortung an einen klaren Ort zu legen.

---

3. Fachlicher Ablauf des Beispielprojekts

Das Beispielprojekt bildet einen typischen Bestell- und Abrechnungsprozess ab.

Ein Kunde meldet sich im Customer Portal an. Die Anmeldung erfolgt über Keycloak. Das Portal ruft den Order Service auf. Der Order Service prüft die Bestellung, speichert sie in PostgreSQL und schreibt ein fachliches Event in eine Outbox-Tabelle. Ein Outbox Publisher veröffentlicht dieses Event an Kafka oder RabbitMQ. Der Billing Service konsumiert das Event und erzeugt eine Rechnung.

Parallel dazu können Partnerunternehmen Dateien liefern. Diese Dateien landen in Object Storage oder auf einem kontrollierten Übergabeverzeichnis. Der File Processing Service validiert die Dateien, schreibt Fehlerberichte und erzeugt Events für gültige Datensätze.

Das Reporting liest nicht direkt unkontrolliert aus allen Produktivtabellen. Stattdessen erhält es freigegebene Datenkopien, Exporte oder spezielle Reporting-Views. Dadurch wird verhindert, dass operative Systeme durch schwere Reports blockiert werden.

Der Ablauf sieht logisch so aus:

Skizze / Textauszug
Kunde
↓
Customer Portal
↓
Order Service
↓
PostgreSQL Orders
↓
Outbox Event
↓
Kafka oder RabbitMQ
↓
Billing Service
↓
PostgreSQL Billing
↓
Rechnung / Audit / Reporting

Der wichtigste technische Punkt ist die Trennung von fachlichen Verantwortungen. Der Order Service erstellt keine Rechnung. Der Billing Service nimmt keine Bestellung entgegen. Das Customer Portal greift nicht direkt auf Datenbanken zu. Reporting wird nicht zum versteckten Integrationsmonster.

---

4. Lokale Entwicklungsumgebung

Für die lokale Entwicklung wird nicht sofort ein vollständiger OpenShift Cluster benötigt. Entwickler sollen Order Service, Billing Service, PostgreSQL, Keycloak und Broker lokal starten können. Dafür gibt es ein Docker-Compose-Lab.

yaml
version: class="str">"3.9"

services:
  postgres-orders:
    image: postgres:16
    container_name: postgres-orders
    environment:
      POSTGRES_DB: orders
      POSTGRES_USER: orders_user
      POSTGRES_PASSWORD: orders_pw
    ports:
      - class="str">"5433:5432"
    volumes:
      - postgres_orders_data:/var/lib/postgresql/data

  postgres-billing:
    image: postgres:16
    container_name: postgres-billing
    environment:
      POSTGRES_DB: billing
      POSTGRES_USER: billing_user
      POSTGRES_PASSWORD: billing_pw
    ports:
      - class="str">"5434:5432"
    volumes:
      - postgres_billing_data:/var/lib/postgresql/data

  rabbitmq:
    image: rabbitmq:3.13-management
    container_name: rabbitmq
    ports:
      - class="str">"5672:5672"
      - class="str">"15672:15672"

  keycloak:
    image: quay.io/keycloak/keycloak:25.0
    container_name: keycloak
    command: start-dev
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - class="str">"8081:8080"

  minio:
    image: minio/minio:RELEASE.2024-06-29T01-20-47Z
    container_name: minio
    command: server /data --console-address class="str">":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    ports:
      - class="str">"9000:9000"
      - class="str">"9001:9001"
    volumes:
      - minio_data:/data

volumes:
  postgres_orders_data:
  postgres_billing_data:
  minio_data:

Diese Umgebung ist nicht Produktion. Sie ist eine kontrollierte Lern- und Entwicklungsumgebung. Sie zeigt Abhängigkeiten, ohne dass sofort alle Enterprise-Sicherheitsmechanismen aktiv sein müssen.

---

5. Order Service als Kernbeispiel

Der Order Service ist das zentrale Backend-Beispiel. Er nimmt Bestellungen entgegen, validiert Eingaben, speichert Daten und erzeugt Events.

Die Paketstruktur folgt einer klaren Schichtung:

Skizze / Textauszug
apps/order-service/source-material/main/java/com/seb4u/demo/spring/order/
├── OrderServiceApplication.java
├── api/
│   ├── OrderController.java
│   ├── CreateOrderRequest.java
│   ├── OrderResponse.java
│   └── ApiErrorResponse.java
├── application/
│   ├── CreateOrderCommand.java
│   ├── CreateOrderResult.java
│   ├── CreateOrderUseCase.java
│   └── OrderApplicationService.java
├── domain/
│   ├── Order.java
│   ├── OrderId.java
│   ├── CustomerId.java
│   ├── OrderItem.java
│   ├── Money.java
│   ├── OrderStatus.java
│   ├── OrderRepository.java
│   └── events/
│       └── OrderCreatedEvent.java
├── infrastructure/
│   ├── persistence/
│   ├── messaging/
│   └── security/
└── observability/
    └── OrderMetrics.java

Diese Struktur verhindert, dass Controller, Datenbanklogik und fachliche Regeln in einer einzigen Klasse landen.

---

6. REST Controller des Order Service

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderResult;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    @PreAuthorize(class="str">"hasAuthority('ROLE_CUSTOMER') or hasAuthority('ROLE_ORDER_MANAGER')")
    public OrderResponse createOrder(@Valid @RequestBody CreateOrderRequest request) {
        CreateOrderCommand command = request.toCommand();
        CreateOrderResult result = createOrderUseCase.createOrder(command);
        return new OrderResponse(result.orderId(), result.status());
    }
}

Der Controller bleibt dünn. Er kennt HTTP, Validierung und Security-Annotation. Er entscheidet aber nicht selbst, wie eine Bestellung fachlich erzeugt wird.

---

7. Fachliches Domain-Modell

java
package com.seb4u.demo.spring.order.domain;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.UUID;

public class Order {

    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private final OrderStatus status;
    private final Instant createdAt;

    private Order(OrderId id, CustomerId customerId, List<OrderItem> items, OrderStatus status, Instant createdAt) {
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException(class="str">"order must contain at least one item");
        }
        this.id = id;
        this.customerId = customerId;
        this.items = List.copyOf(items);
        this.status = status;
        this.createdAt = createdAt;
    }

    public static Order create(CustomerId customerId, List<OrderItem> items, Instant now) {
        return new Order(new OrderId(UUID.randomUUID()), customerId, items, OrderStatus.CREATED, now);
    }

    public BigDecimal totalAmount() {
        return items.stream()
                .map(OrderItem::lineTotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public OrderId id() {
        return id;
    }

    public CustomerId customerId() {
        return customerId;
    }

    public List<OrderItem> items() {
        return items;
    }

    public OrderStatus status() {
        return status;
    }

    public Instant createdAt() {
        return createdAt;
    }
}

Das Domain-Modell enthält fachliche Regeln. Es verlässt sich nicht darauf, dass die Datenbank oder der Controller schon alles richtig macht.

---

8. Persistenz mit JPA als Infrastrukturdetail

Die Domäne muss nicht wissen, ob JPA, JDBC oder ein anderer Mechanismus verwendet wird. Die Infrastruktur implementiert das Repository.

java
package com.seb4u.demo.spring.order.infrastructure.persistence;

import com.seb4u.demo.spring.order.domain.Order;
import com.seb4u.demo.spring.order.domain.OrderRepository;
import org.springframework.stereotype.Repository;

@Repository
public class JpaOrderRepositoryAdapter implements OrderRepository {

    private final SpringDataOrderJpaRepository jpaRepository;
    private final OrderMapper mapper;

    public JpaOrderRepositoryAdapter(SpringDataOrderJpaRepository jpaRepository, OrderMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.mapper = mapper;
    }

    @Override
    public Order save(Order order) {
        OrderEntity entity = mapper.toEntity(order);
        OrderEntity saved = jpaRepository.save(entity);
        return mapper.toDomain(saved);
    }
}

Dieser Adapter schützt die fachliche Logik vor technischen Details. Wenn später ein anderer Persistenzmechanismus genutzt wird, muss nicht die gesamte Anwendungslogik umgebaut werden.

---

9. Outbox-Tabelle und Publisher

Der Outbox Publisher veröffentlicht Events nicht direkt aus der Transaktion heraus. Er speichert zuerst Events in der Datenbank. Danach verarbeitet ein separater Scheduler unveröffentlichte Events.

java
package com.seb4u.demo.spring.order.infrastructure.messaging;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.UUID;

@Component
public class OutboxWriter {

    private final JdbcTemplate jdbcTemplate;
    private final ObjectMapper objectMapper;

    public OutboxWriter(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
        this.jdbcTemplate = jdbcTemplate;
        this.objectMapper = objectMapper;
    }

    public void store(String aggregateType, String aggregateId, String eventType, Object payload) {
        try {
            jdbcTemplate.update(
                    class="str">"""
                    insert into outbox_events
                    (id, aggregate_type, aggregate_id, event_type, payload, created_at, publish_attempts)
                    values (?, ?, ?, ?, ?::jsonb, ?, 0)
                    class="str">""",
                    UUID.randomUUID(),
                    aggregateType,
                    aggregateId,
                    eventType,
                    objectMapper.writeValueAsString(payload),
                    Instant.now()
            );
        } catch (JsonProcessingException ex) {
            throw new IllegalStateException(class="str">"could not serialize outbox payload", ex);
        }
    }
}

Der Vorteil ist nicht nur technische Zuverlässigkeit. Der Vorteil ist auch Betriebssichtbarkeit. Wenn Events hängen bleiben, kann man sie in der Outbox-Tabelle sehen.

---

10. Billing Service als Event Consumer

Der Billing Service reagiert auf OrderCreatedEvent. Er erstellt eine Rechnung, aber er verändert keine Bestellung. Damit bleibt die fachliche Verantwortung getrennt.

java
package com.seb4u.demo.spring.billing.messaging;

import com.seb4u.demo.spring.billing.application.CreateInvoiceFromOrderUseCase;
import com.seb4u.demo.spring.billing.application.InvoiceFromOrderCommand;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class OrderCreatedEventListener {

    private final CreateInvoiceFromOrderUseCase useCase;

    public OrderCreatedEventListener(CreateInvoiceFromOrderUseCase useCase) {
        this.useCase = useCase;
    }

    @RabbitListener(queues = class="str">"billing.order-created")
    public void onOrderCreated(OrderCreatedMessage message) {
        useCase.createInvoice(
                new InvoiceFromOrderCommand(
                        message.eventId(),
                        message.orderId(),
                        message.customerId(),
                        message.totalAmount(),
                        message.createdAt()
                )
        );
    }
}

Der Consumer muss idempotent sein. Wenn dieselbe Nachricht zweimal verarbeitet wird, darf keine doppelte Rechnung entstehen.

---

11. Idempotenz im Billing Service

java
package com.seb4u.demo.spring.billing.application;

import com.seb4u.demo.spring.billing.domain.Invoice;
import com.seb4u.demo.spring.billing.domain.InvoiceRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BillingApplicationService implements CreateInvoiceFromOrderUseCase {

    private final InvoiceRepository invoiceRepository;

    public BillingApplicationService(InvoiceRepository invoiceRepository) {
        this.invoiceRepository = invoiceRepository;
    }

    @Override
    @Transactional
    public void createInvoice(InvoiceFromOrderCommand command) {
        if (invoiceRepository.existsBySourceEventId(command.sourceEventId())) {
            return;
        }

        Invoice invoice = Invoice.fromOrder(
                command.sourceEventId(),
                command.orderId(),
                command.customerId(),
                command.totalAmount(),
                command.createdAt()
        );

        invoiceRepository.save(invoice);
    }
}

Idempotenz ist ein typisches Enterprise-Thema. Ohne Idempotenz werden Wiederholungen gefährlich. Mit Idempotenz werden Wiederholungen kontrollierbar.

---

12. Frontend: Customer Portal

Das Customer Portal ist eine moderne Webanwendung. Es zeigt Bestellungen an und ermöglicht das Erstellen neuer Bestellungen. Es speichert keine Secrets und ruft keine Datenbank direkt auf.

Ein API-Client kann so aussehen:

typescript
export type CreateOrderRequest = {
  customerId: string;
  items: Array<{
    productId: string;
    quantity: number;
    unitPrice: number;
  }>;
};

export type OrderResponse = {
  orderId: string;
  status: string;
};

export async function createOrder(
  accessToken: string,
  request: CreateOrderRequest
): Promise<OrderResponse> {
  const response = await fetch(class="str">'/api/orders', {
    method: class="str">'POST',
    headers: {
      class="str">'Content-Type': class="str">'application/json',
      class="str">'Authorization': `Bearer ${accessToken}`
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Order creation failed: ${response.status} ${errorBody}`);
  }

  return response.json();
}

Das Frontend kennt nur die API. Es kennt nicht PostgreSQL, RabbitMQ oder interne Deployment-Details.

---

13. OpenShift Deployment für Order Service

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
  labels:
    app: order-service
    team: order
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        team: order
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.0.0
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: order-service-config
            - secretRef:
                name: order-service-secret
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 60
            periodSeconds: 20
          resources:
            requests:
              cpu: class="str">"250m"
              memory: class="str">"512Mi"
            limits:
              cpu: class="str">"1000m"
              memory: class="str">"1024Mi"
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL

Dieses Deployment zeigt wichtige Betriebs- und Security-Entscheidungen: mehrere Replikate, Probes, Resource Limits und eingeschränkter SecurityContext.

---

14. Service, Route und NetworkPolicy

yaml
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: order-prod
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 8080
      targetPort: http
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: order-service
  namespace: order-prod
spec:
  host: api.alpine.example.com
  path: /orders
  to:
    kind: Service
    name: order-service
  tls:
    termination: edge
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-egress
  namespace: order-prod
spec:
  podSelector:
    matchLabels:
      app: order-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              zone: data
          podSelector:
            matchLabels:
              app: postgres-orders
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector:
            matchLabels:
              zone: messaging
          podSelector:
            matchLabels:
              app: rabbitmq
      ports:
        - protocol: TCP
          port: 5672

Die NetworkPolicy ist hier kein Zusatz. Sie ist Teil der Zielarchitektur. Ohne sie wäre nicht klar, welche Kommunikation wirklich erlaubt ist.

---

15. Argo CD Application

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: openshift-gitops
spec:
  project: alpine-prod
  source:
    repoURL: https://git.example.com/alpine/platform-gitops.git
    targetRevision: main
    path: apps/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=false

GitOps bedeutet: Der gewünschte Zustand steht in Git. Argo CD vergleicht diesen Zustand mit dem Cluster und synchronisiert kontrolliert.

---

16. Kustomize Overlays

Die Umgebungen unterscheiden sich nicht durch Copy/Paste-Manifeste, sondern durch Overlays.

Skizze / Textauszug
platform/kustomize/order-service/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── route.yaml
│   ├── configmap.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── replica-patch.yaml
    ├── test/
    │   ├── kustomization.yaml
    │   └── config-patch.yaml
    └── prod/
        ├── kustomization.yaml
        ├── replica-patch.yaml
        └── resource-patch.yaml

Beispiel für Produktion:

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: order-prod

images:
  - name: registry.example.com/alpine/order-service
    newTag: 1.0.0

patchesStrategicMerge:
  - replica-patch.yaml
  - resource-patch.yaml

So bleibt die Struktur nachvollziehbar. Dev, Test und Prod verwenden dieselbe Basis, aber unterscheiden sich gezielt.

---

17. CI Pipeline für den Order Service

yaml
name: order-service-ci

on:
  pull_request:
    paths:
      - class="str">'apps/order-service/**'
      - class="str">'.github/workflows/order-service-ci.yml'

jobs:
  build-test-scan:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">'21'
          cache: maven

      - name: Run unit and integration tests
        working-directory: apps/order-service
        run: ./mvnw clean verify

      - name: Build container image
        working-directory: apps/order-service
        run: |
          docker build -t registry.example.com/alpine/order-service:${{ github.sha }} .

      - name: Security scan
        run: |
          trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/alpine/order-service:${{ github.sha }}

Diese Pipeline ist eine Qualitätsgrenze. Code wird nicht einfach gebaut und deployt. Er wird getestet, gebaut und gescannt.

---

18. Testcontainers Integrationstest

java
package com.seb4u.demo.spring.order;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(class="str">"postgres:16")
            .withDatabaseName(class="str">"orders")
            .withUsername(class="str">"orders_user")
            .withPassword(class="str">"orders_pw");

    @Test
    void contextStartsWithRealPostgres() {
        // Der Test prüft, dass Anwendung, Migrationen und Datenbankverbindung zusammen starten.
    }
}

Testcontainers verhindert, dass Integrationstests nur gegen Mocks laufen. Dadurch werden Datenbankmigrationen und echte Verbindungsdetails früher sichtbar.

---

19. Observability im Beispielprojekt

Jeder produktionsnahe Service braucht Logs, Metriken und Health Checks.

Für Spring Boot wird Actuator aktiviert:

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  endpoint:
    health:
      probes:
        enabled: true
  metrics:
    tags:
      application: order-service
      environment: prod

Dazu kommt ein ServiceMonitor:

yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: order-service
  namespace: order-prod
spec:
  selector:
    matchLabels:
      app: order-service
  endpoints:
    - port: http
      path: /actuator/prometheus
      interval: 30s

Monitoring ist nicht optional. Ohne Metriken kann der Betrieb nicht erkennen, ob ein Service langsam wird, Fehler häuft oder Ressourcen knapp werden.

---

20. Runbook: Outbox wächst an

Vorlage / Textauszug

Runbook: Outbox Backlog im Order Service

Symptom

Die Metrik `order_outbox_unpublished_events` steigt kontinuierlich.

Mögliche Auswirkungen

Billing erhält Bestellungen verspätet. Kunden sehen Bestellungen, aber Rechnungen werden nicht zeitnah erzeugt.

Erste Prüfung

oc logs -n order-prod deployment/order-service --tail=200

oc exec -n order-prod deployment/order-service -- curl -s localhost:8080/actuator/health

Vorlage / Textauszug
Datenbank prüfen

select count(*)

from outbox_events

where published_at is null;

select event_type, publish_attempts, count(*)

from outbox_events

where published_at is null

group by event_type, publish_attempts

order by publish_attempts desc;

Vorlage / Textauszug
Broker prüfen

oc get pods -n messaging

oc logs -n messaging statefulset/rabbitmq --tail=100

Vorlage / Textauszug
Häufige Ursachen
  • Broker nicht erreichbar
  • falsche Credentials
  • Queue oder Exchange fehlt
  • NetworkPolicy blockiert Egress
  • Publisher Scheduler deaktiviert
  • Payload kann nicht serialisiert werden
Sofortmaßnahmen
  1. Broker-Verfügbarkeit prüfen.
  2. Secrets und Konfiguration prüfen.
  3. Letzte Deployment-Änderung prüfen.
  4. Wenn Fehler durch neues Release: Rollback oder Hotfix einleiten.
  5. Nicht manuell Events löschen, bevor fachliche Auswirkung geklärt ist.

Dieses Runbook verbindet Anwendung, Datenbank, Broker, OpenShift und fachliche Auswirkung.

---

21. Security im Beispielprojekt

Security besteht nicht nur aus Login. Das Projekt verwendet mehrere Sicherheitsschichten:

Vorlage / Textauszug
  • Keycloak für Authentifizierung
  • Rollen im Token für fachliche Autorisierung
  • Kubernetes RBAC für Plattformzugriffe
  • ServiceAccounts für Workload-Identität
  • Secrets oder External Secrets für vertrauliche Konfiguration
  • NetworkPolicies für Netzwerkbegrenzung
  • SecurityContext für Container-Härtung
  • Audit Logs für Nachvollziehbarkeit
  • Image Scans für Supply Chain Security

Beispiel für Spring Security:

java
package com.seb4u.demo.spring.order.infrastructure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(class="str">"/actuator/health/**").permitAll()
                        .requestMatchers(class="str">"/actuator/prometheus").hasAuthority(class="str">"ROLE_PLATFORM_MONITORING")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt())
                .build();
    }
}

Die Health-Endpunkte bleiben erreichbar, damit Kubernetes Probes funktionieren. Der Prometheus-Endpunkt wird eingeschränkt. Fachliche APIs verlangen Authentifizierung.

---

22. File Processing als hybrider Workload

Der File Processing Service ist bewusst nicht automatisch ein OpenShift-Service. Er kann auf VM oder Bare Metal laufen, wenn die Dateien sehr groß sind oder bestehende Storage-Prozesse genutzt werden.

Ein systemd-Service könnte so aussehen:

ini
[Unit]
Description=Alpine File Processing Worker
After=network-online.target
Wants=network-online.target

[Service]
User=fileworker
Group=fileworker
WorkingDirectory=/opt/alpine/file-processing
EnvironmentFile=/etc/alpine/file-processing.env
ExecStart=/usr/bin/java -jar /opt/alpine/file-processing/file-processing-service.jar
Restart=on-failure
RestartSec=15
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/alpine/file-processing /var/log/alpine/file-processing

[Install]
WantedBy=multi-user.target

Das zeigt: Auch ein VM-Workload kann gehärtet und sauber betrieben werden. Modernisierung bedeutet nicht immer sofort Containerisierung.

---

23. Cloud Object Storage für Partnerdateien

hcl
resource class="str">"aws_s3_bucket" class="str">"partner_incoming" {
  bucket = class="str">"alpine-partner-incoming-prod"

  tags = {
    owner       = class="str">"team-integration"
    environment = class="str">"prod"
    data_class  = class="str">"confidential"
  }
}

resource class="str">"aws_s3_bucket_versioning" class="str">"partner_incoming" {
  bucket = aws_s3_bucket.partner_incoming.id

  versioning_configuration {
    status = class="str">"Enabled"
  }
}

resource class="str">"aws_s3_bucket_server_side_encryption_configuration" class="str">"partner_incoming" {
  bucket = aws_s3_bucket.partner_incoming.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = class="str">"AES256"
    }
  }
}

Dieser Ausschnitt zeigt, dass Cloud-Ressourcen nicht nur erstellt, sondern auch klassifiziert, verschlüsselt und versioniert werden.

---

24. Reporting Service

Reporting ist ein häufiger Architektur-Stolperstein. Wenn Reports direkt auf operative Tabellen zugreifen, können sie Bestellprozesse verlangsamen. Deshalb muss Reporting bewusst entkoppelt werden.

Mögliche Strategien:

Vorlage / Textauszug
  1. Reporting liest aus freigegebenen Views.
  2. Reporting nutzt replizierte Daten.
  3. Reporting erhält Events und baut eigene Projektionen.
  4. Reporting läuft zeitgesteuert außerhalb der Hauptlastzeiten.
  5. Reporting wird für große Auswertungen auf VM oder Bare Metal betrieben.

Ein Kubernetes CronJob für einen kleineren Report:

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-order-summary
  namespace: reporting-prod
spec:
  schedule: class="str">"0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: reporting-job
              image: registry.example.com/alpine/reporting-service:1.0.0
              args:
                - class="str">"daily-order-summary"
              envFrom:
                - secretRef:
                    name: reporting-db-secret

Für große Reports kann eine VM sinnvoller sein. Wichtig ist die fachliche Begründung.

---

25. ADR: Outbox für Domain Events

Vorlage / Textauszug

ADR 0003: Outbox Pattern für Domain Events

Status

Akzeptiert

Kontext

Der Order Service speichert Bestellungen in PostgreSQL und muss Billing über neue Bestellungen informieren. Eine direkte Veröffentlichung an den Broker innerhalb derselben fachlichen Aktion kann inkonsistent werden, wenn Datenbank oder Broker unterschiedlich reagieren.

Entscheidung

Der Order Service verwendet das Outbox Pattern. Fachliche Events werden in derselben Transaktion wie die Bestellung in einer Outbox-Tabelle gespeichert. Ein separater Publisher veröffentlicht unveröffentlichte Events an den Broker.

Konsequenzen

Der Service benötigt eine Outbox-Tabelle, einen Publisher, Monitoring für unveröffentlichte Events und ein Runbook für Backlogs. Billing Consumer müssen idempotent sein.

Dieser ADR ist kurz, aber wichtig. Er erklärt nicht nur die Lösung, sondern auch die Konsequenzen für Betrieb und Entwicklung.

---

26. End-to-End Testidee

Ein End-to-End-Test für das Lab prüft nicht jedes Detail, sondern den wichtigsten fachlichen Fluss.

Vorlage / Textauszug
  1. Keycloak Test-Token für Customer erzeugen.
  2. Bestellung über Customer Portal oder API anlegen.
  3. Order Service speichert Bestellung.
  4. Outbox Event entsteht.
  5. Publisher veröffentlicht Event.
  6. Billing Service erzeugt Rechnung.
  7. Metriken zeigen erfolgreiche Verarbeitung.
  8. Logs enthalten eine Correlation-ID.

Ein vereinfachter API-Test:

bash
TOKEN=class="str">"$(./scripts/get-test-token.sh customer)"

curl -s -X POST class="str">"https://api.alpine.example.com/orders" \
  -H class="str">"Authorization: Bearer $TOKEN" \
  -H class="str">"Content-Type: application/json" \
  -d '{
    class="str">"customerId": class="str">"C-10001",
    class="str">"items": [
      { class="str">"productId": class="str">"P-100", class="str">"quantity": 2, class="str">"unitPrice": 19.90 }
    ]
  }'

Dieser Test zeigt die Verbindung zwischen Identity, API, Anwendung, Datenbank und Messaging.

---

27. Typische Fehler im Gesamtprojekt

Ein häufiger Fehler ist, alle Artefakte nach Technologie statt nach Verantwortung zu sortieren. Dann liegen wichtige Plattformdefinitionen verstreut, und niemand weiß, welcher Service welche Ressourcen braucht.

Ein zweiter Fehler ist fehlende Trennung zwischen App-Repository und GitOps-Repository. Wenn Anwendungscode automatisch direkt Cluster-Manifeste verändert, entstehen unkontrollierte Deployments.

Ein dritter Fehler ist, Security nur im Code zu betrachten. In Wahrheit entsteht Security auch durch NetworkPolicies, RBAC, Secrets, Image Scans, Audit Logs und Betriebsprozesse.

Ein vierter Fehler ist, VM- und Bare-Metal-Systeme als Sonderfälle ohne Automatisierung zu behandeln. Auch diese Systeme brauchen Baselines, Monitoring, Backups und Runbooks.

Ein fünfter Fehler ist, End-to-End-Abläufe nicht zu testen. Einzelne Services können funktionieren, während der Gesamtprozess trotzdem scheitert.

---

28. Übung: eigenes Mini-Enterprise-Projekt ableiten

Erstelle aus diesem Beispiel eine kleinere Variante mit nur drei Anwendungen:

Vorlage / Textauszug
  1. Customer Portal
  2. Order Service
  3. Billing Service

Lege fest:

Vorlage / Textauszug
  • welche Repositories oder Ordner du brauchst
  • welche Datenbanken existieren
  • welcher Broker verwendet wird
  • welche OpenShift Namespaces entstehen
  • welche Secrets benötigt werden
  • welche CI-Schritte verpflichtend sind
  • welches Runbook zuerst geschrieben wird

Die Übung ist bestanden, wenn du nicht nur Code nennst, sondern auch Betrieb, Security und Deployment berücksichtigst.

---

29. Musterlösung zur Übung

Eine mögliche Lösung:

Vorlage / Textauszug

Repository-Struktur:

  • apps/customer-portal
  • apps/order-service
  • apps/billing-service
  • platform/kustomize
  • platform/argocd
  • docs/adr
  • docs/runbooks

Datenbanken:

  • PostgreSQL orders
  • PostgreSQL billing

Broker:

  • RabbitMQ für lokalen Einstieg oder Kafka für größere Event-Streams

Namespaces:

  • customer-prod
  • order-prod
  • billing-prod
  • messaging-prod
  • platform-monitoring

Secrets:

  • order-db-secret
  • billing-db-secret
  • broker-credentials
  • keycloak-client-secret

CI-Schritte:

  • Build
  • Unit Tests
  • Integration Tests
  • Container Build
  • Image Scan
  • Manifest Linting

Erstes Runbook:

  • Outbox Backlog im Order Service

Diese Musterlösung ist bewusst klein, aber vollständig gedacht. Sie betrachtet Anwendungen nicht isoliert, sondern als Teil einer Plattform.

---

30. Merksätze

Vorlage / Textauszug

Ein gutes Enterprise-Repository zeigt Verantwortung, nicht nur Dateien.

Ein moderner Service besteht aus Code, Tests, Deployment, Security, Observability und Runbook.

GitOps macht den Zielzustand sichtbar.

Outbox schützt vor inkonsistenter Kommunikation zwischen Datenbank und Broker.

Idempotenz ist Pflicht, wenn Nachrichten wiederholt verarbeitet werden können.

VMs und Bare Metal sind nicht automatisch Legacy, wenn sie bewusst betrieben werden.

Cloud-Ressourcen brauchen dieselbe Disziplin wie Anwendungscode: Versionierung, Review, Security und Dokumentation.

Große Code- und Konfigurationsbeispiele

Ziel dieses Schritts

Dieser Schritt ergänzt keine allgemeinen Erklärungen, sondern konkrete technische Artefakte. Das Projekt soll dadurch näher an ein echtes Enterprise-Repository heranrücken. Der Schwerpunkt liegt auf zusammenhängendem Code: Ein Order Flow wird über REST, Use Case, Domäne, Persistenz, Outbox, Messaging, Billing Consumer, Tests, OpenShift Deployment, GitOps, Terraform/OpenTofu und Ansible beschrieben.

Die Beispiele sind bewusst nicht nur kleine Schnipsel. Sie zeigen, wie verschiedene Schichten zusammenspielen. Genau dadurch wird die spätere HTML größer, aber nicht künstlich. Der Umfang entsteht durch technische Substanz.

1. Order Service: REST Controller

Der Controller bleibt dünn. Er nimmt HTTP entgegen, validiert einfache Request-Struktur und übergibt die Arbeit an den Use Case. Fachlogik gehört nicht in den Controller.

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderResult;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.List;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    public ResponseEntity<CreateOrderResponse> createOrder(
            @Valid @RequestBody CreateOrderRequest request,
            Authentication authentication
    ) {
        String userId = authentication.getName();

        CreateOrderResult result = createOrderUseCase.create(
                new CreateOrderCommand(
                        userId,
                        request.customerId(),
                        request.items().stream()
                                .map(item -> new CreateOrderCommand.Item(
                                        item.productId(),
                                        item.quantity(),
                                        item.unitPrice()
                                ))
                                .toList()
                )
        );

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(new CreateOrderResponse(result.orderId(), result.status()));
    }

    public record CreateOrderRequest(
            @NotBlank String customerId,
            @NotEmpty List<ItemRequest> items
    ) {}

    public record ItemRequest(
            @NotBlank String productId,
            @Positive int quantity,
            @Positive BigDecimal unitPrice
    ) {}

    public record CreateOrderResponse(String orderId, String status) {}
}

Wichtig ist die klare Grenze: Der Controller weiß nichts über JPA, Outbox, Kafka oder Datenbanktransaktionen. Er übersetzt nur HTTP in einen Anwendungsbefehl.

2. Use Case mit Transaktionsgrenze

Die Transaktion liegt im Anwendungsservice. Dadurch werden Bestellung und Outbox-Event gemeinsam gespeichert. Wenn einer der beiden Schritte fehlschlägt, wird alles zurückgerollt.

java
package com.seb4u.demo.spring.order.application;

import com.seb4u.demo.spring.order.domain.CustomerId;
import com.seb4u.demo.spring.order.domain.Money;
import com.seb4u.demo.spring.order.domain.Order;
import com.seb4u.demo.spring.order.domain.OrderItem;
import com.seb4u.demo.spring.order.domain.OrderRepository;
import com.seb4u.demo.spring.order.domain.events.OrderCreatedEvent;
import com.seb4u.demo.spring.order.infrastructure.outbox.OutboxWriter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.Instant;
import java.util.List;
import java.util.UUID;

@Service
public class CreateOrderService implements CreateOrderUseCase {

    private final OrderRepository orderRepository;
    private final OutboxWriter outboxWriter;
    private final Clock clock;

    public CreateOrderService(OrderRepository orderRepository, OutboxWriter outboxWriter, Clock clock) {
        this.orderRepository = orderRepository;
        this.outboxWriter = outboxWriter;
        this.clock = clock;
    }

    @Override
    @Transactional
    public CreateOrderResult create(CreateOrderCommand command) {
        List<OrderItem> items = command.items().stream()
                .map(item -> new OrderItem(
                        item.productId(),
                        item.quantity(),
                        Money.euro(item.unitPrice())
                ))
                .toList();

        Order order = Order.create(
                UUID.randomUUID(),
                new CustomerId(command.customerId()),
                items,
                Instant.now(clock)
        );

        Order saved = orderRepository.save(order);

        outboxWriter.write(new OrderCreatedEvent(
                saved.id().toString(),
                saved.customerId().value(),
                saved.total().amount(),
                saved.createdAt()
        ));

        return new CreateOrderResult(saved.id().toString(), saved.status().name());
    }
}

Diese Struktur erlaubt später Integrationstests mit Testcontainers. Die fachliche Logik bleibt testbar, obwohl Persistenz und Outbox real angebunden werden können.

3. Domänenmodell für Order

Die Domäne schützt fachliche Regeln. Eine Bestellung ohne Positionen darf nicht entstehen. Negative Mengen oder negative Preise sind ebenfalls unzulässig.

java
package com.seb4u.demo.spring.order.domain;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

public class Order {

    private final UUID id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private final Instant createdAt;
    private OrderStatus status;

    private Order(UUID id, CustomerId customerId, List<OrderItem> items, OrderStatus status, Instant createdAt) {
        this.id = Objects.requireNonNull(id);
        this.customerId = Objects.requireNonNull(customerId);
        this.items = List.copyOf(validateItems(items));
        this.status = Objects.requireNonNull(status);
        this.createdAt = Objects.requireNonNull(createdAt);
    }

    public static Order create(UUID id, CustomerId customerId, List<OrderItem> items, Instant createdAt) {
        return new Order(id, customerId, items, OrderStatus.CREATED, createdAt);
    }

    public Money total() {
        BigDecimal sum = items.stream()
                .map(item -> item.unitPrice().amount().multiply(BigDecimal.valueOf(item.quantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return new Money(sum, class="str">"EUR");
    }

    public void markSubmitted() {
        if (status != OrderStatus.CREATED) {
            throw new IllegalStateException(class="str">"Only CREATED orders can be submitted");
        }
        this.status = OrderStatus.SUBMITTED;
    }

    private static List<OrderItem> validateItems(List<OrderItem> items) {
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException(class="str">"Order must contain at least one item");
        }
        return items;
    }

    public UUID id() { return id; }
    public CustomerId customerId() { return customerId; }
    public List<OrderItem> items() { return items; }
    public OrderStatus status() { return status; }
    public Instant createdAt() { return createdAt; }
}

4. JPA-Adapter als Infrastruktur

Die Domäne soll nicht von JPA-Annotationen abhängig sein. Deshalb übernimmt ein Adapter die Übersetzung zwischen Domänenobjekt und Datenbankmodell.

java
package com.seb4u.demo.spring.order.infrastructure.persistence;

import com.seb4u.demo.spring.order.domain.CustomerId;
import com.seb4u.demo.spring.order.domain.Money;
import com.seb4u.demo.spring.order.domain.Order;
import com.seb4u.demo.spring.order.domain.OrderItem;
import com.seb4u.demo.spring.order.domain.OrderRepository;
import org.springframework.stereotype.Repository;

import java.util.UUID;

@Repository
public class JpaOrderRepositoryAdapter implements OrderRepository {

    private final SpringDataOrderJpaRepository jpaRepository;

    public JpaOrderRepositoryAdapter(SpringDataOrderJpaRepository jpaRepository) {
        this.jpaRepository = jpaRepository;
    }

    @Override
    public Order save(Order order) {
        OrderEntity entity = new OrderEntity();
        entity.setId(order.id());
        entity.setCustomerId(order.customerId().value());
        entity.setStatus(order.status().name());
        entity.setCreatedAt(order.createdAt());
        entity.setTotalAmount(order.total().amount());

        order.items().forEach(item -> {
            OrderItemEntity itemEntity = new OrderItemEntity();
            itemEntity.setId(UUID.randomUUID());
            itemEntity.setOrder(entity);
            itemEntity.setProductId(item.productId());
            itemEntity.setQuantity(item.quantity());
            itemEntity.setUnitPrice(item.unitPrice().amount());
            entity.getItems().add(itemEntity);
        });

        OrderEntity saved = jpaRepository.save(entity);
        return Order.create(
                saved.getId(),
                new CustomerId(saved.getCustomerId()),
                saved.getItems().stream()
                        .map(i -> new OrderItem(i.getProductId(), i.getQuantity(), Money.euro(i.getUnitPrice())))
                        .toList(),
                saved.getCreatedAt()
        );
    }
}

Der Adapter wirkt länger als ein direkt annotiertes Domänenobjekt, bringt aber saubere Grenzen. Für größere Enterprise-Systeme ist diese Trennung oft wertvoll.

5. Outbox Writer

Der Outbox Writer serialisiert fachliche Events und speichert sie in einer Tabelle. Er veröffentlicht nicht direkt an Kafka. Diese Trennung verhindert Transaktionsprobleme.

java
package com.seb4u.demo.spring.order.infrastructure.outbox;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.seb4u.demo.spring.order.domain.events.DomainEvent;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.UUID;

@Component
public class OutboxWriter {

    private final OutboxEventJpaRepository repository;
    private final ObjectMapper objectMapper;

    public OutboxWriter(OutboxEventJpaRepository repository, ObjectMapper objectMapper) {
        this.repository = repository;
        this.objectMapper = objectMapper;
    }

    public void write(DomainEvent event) {
        OutboxEventEntity entity = new OutboxEventEntity();
        entity.setId(UUID.randomUUID());
        entity.setAggregateType(event.aggregateType());
        entity.setAggregateId(event.aggregateId());
        entity.setEventType(event.eventType());
        entity.setPayload(toJson(event));
        entity.setCreatedAt(Instant.now());
        entity.setPublishAttempts(0);
        repository.save(entity);
    }

    private String toJson(DomainEvent event) {
        try {
            return objectMapper.writeValueAsString(event);
        } catch (JsonProcessingException ex) {
            throw new IllegalStateException(class="str">"Could not serialize domain event " + event.eventType(), ex);
        }
    }
}

6. Outbox Publisher mit Retry-Logik

Der Publisher läuft regelmäßig. Er liest unveröffentlichte Events, sendet sie an den Broker und markiert sie danach als veröffentlicht. Fehler werden gezählt und dokumentiert.

java
package com.seb4u.demo.spring.order.infrastructure.outbox;

import org.springframework.data.domain.PageRequest;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;

@Component
public class OutboxPublisherJob {

    private final OutboxEventJpaRepository repository;
    private final KafkaTemplate<String, String> kafkaTemplate;

    public OutboxPublisherJob(OutboxEventJpaRepository repository, KafkaTemplate<String, String> kafkaTemplate) {
        this.repository = repository;
        this.kafkaTemplate = kafkaTemplate;
    }

    @Scheduled(fixedDelayString = class="str">"${outbox.publisher.fixed-delay-ms:5000}")
    @Transactional
    public void publishBatch() {
        var events = repository.findUnpublished(PageRequest.of(0, 50));

        for (OutboxEventEntity event : events) {
            try {
                kafkaTemplate.send(class="str">"order.events", event.getAggregateId(), event.getPayload()).get();
                event.setPublishedAt(Instant.now());
                event.setLastError(null);
            } catch (Exception ex) {
                event.setPublishAttempts(event.getPublishAttempts() + 1);
                event.setLastError(ex.getClass().getSimpleName() + class="str">": " + ex.getMessage());
            }
        }
    }
}

In Produktion würde man zusätzlich Locking, Backoff, Metriken und Alerting ergänzen. Das Grundprinzip bleibt aber gleich.

7. Billing Consumer mit Idempotenz

Der Billing Service muss Events mehrfach erhalten können, ohne doppelte Rechnungen zu erzeugen. Das ist Idempotenz.

java
package com.seb4u.demo.spring.billing.messaging;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.seb4u.demo.spring.billing.application.CreateInvoiceFromOrderUseCase;
import com.seb4u.demo.spring.billing.application.InvoiceCommand;
import com.seb4u.demo.spring.billing.infrastructure.ProcessedEventRepository;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class OrderCreatedConsumer {

    private final ObjectMapper objectMapper;
    private final ProcessedEventRepository processedEventRepository;
    private final CreateInvoiceFromOrderUseCase useCase;

    public OrderCreatedConsumer(
            ObjectMapper objectMapper,
            ProcessedEventRepository processedEventRepository,
            CreateInvoiceFromOrderUseCase useCase
    ) {
        this.objectMapper = objectMapper;
        this.processedEventRepository = processedEventRepository;
        this.useCase = useCase;
    }

    @KafkaListener(topics = class="str">"order.events", groupId = class="str">"billing-service")
    @Transactional
    public void consume(String payload) throws Exception {
        OrderCreatedMessage message = objectMapper.readValue(payload, OrderCreatedMessage.class);

        if (processedEventRepository.existsByEventId(message.eventId())) {
            return;
        }

        useCase.createInvoice(new InvoiceCommand(
                message.orderId(),
                message.customerId(),
                message.totalAmount()
        ));

        processedEventRepository.markProcessed(message.eventId());
    }
}

Diese Logik ist im Enterprise-Betrieb entscheidend. Broker garantieren nicht immer exakt einmalige fachliche Verarbeitung. Die Anwendung muss Wiederholungen aushalten.

8. Datenbankmigration für Idempotenz

sql
CREATE TABLE processed_events (
    event_id VARCHAR(120) PRIMARY KEY,
    processed_at TIMESTAMP WITH TIME ZONE NOT NULL,
    handler_name VARCHAR(160) NOT NULL
);

CREATE TABLE invoices (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL,
    customer_id VARCHAR(120) NOT NULL,
    amount NUMERIC(12, 2) NOT NULL,
    currency VARCHAR(3) NOT NULL,
    status VARCHAR(40) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL
);

CREATE UNIQUE INDEX ux_invoices_order_id ON invoices(order_id);

Die eindeutige Einschränkung auf order_id ist eine zweite Schutzlinie. Selbst wenn Idempotenz im Consumer fehlerhaft wäre, verhindert die Datenbank doppelte Rechnungen für dieselbe Bestellung.

9. Testcontainers-End-to-End-Test

Ein guter Integrationstest startet echte Infrastruktur in Containern. Dadurch werden Datenbankmigrationen, Repository, REST API und Outbox gemeinsam geprüft.

java
package com.seb4u.demo.spring.order;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles(class="str">"test")
class CreateOrderEndToEndTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(class="str">"postgres:16-alpine");

    private final TestRestTemplate restTemplate;

    CreateOrderEndToEndTest(TestRestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Test
    void createsOrderAndStoresOutboxEvent() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setBearerAuth(class="str">"test-token-with-order-write-role");

        Map<String, Object> body = Map.of(
                class="str">"customerId", class="str">"customer-1000",
                class="str">"items", List.of(Map.of(
                        class="str">"productId", class="str">"product-42",
                        class="str">"quantity", 2,
                        class="str">"unitPrice", new BigDecimal(class="str">"19.90")
                ))
        );

        var response = restTemplate.postForEntity(
                class="str">"/api/orders",
                new HttpEntity<>(body, headers),
                Map.class
        );

        assertThat(response.getStatusCode().value()).isEqualTo(201);
        assertThat(response.getBody()).containsKey(class="str">"orderId");
        assertThat(response.getBody()).containsEntry(class="str">"status", class="str">"CREATED");
    }
}

Der Test ist noch nicht vollständig, aber er zeigt die Richtung: echte Datenbank, echter HTTP-Aufruf, echte Migrationen. In einem nächsten Schritt würde man Outbox-Einträge direkt prüfen.

10. Spring Security Resource Server

java
package com.seb4u.demo.spring.order.infrastructure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(class="str">"/actuator/health/**").permitAll()
                        .requestMatchers(class="str">"/actuator/prometheus").hasAuthority(class="str">"ROLE_platform_observer")
                        .requestMatchers(class="str">"/api/orders/**").hasAuthority(class="str">"ROLE_order_write")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter())))
                .build();
    }

    @Bean
    Converter<Jwt, AbstractAuthenticationToken> jwtConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
        return converter;
    }
}

Der Zugriff auf Prometheus-Metriken ist hier bewusst nicht komplett offen. In produktionsnahen Umgebungen muss entschieden werden, ob Metriken intern frei lesbar sind oder ebenfalls geschützt werden.

11. React API Client mit OIDC Token

typescript
import { UserManager } from class="str">"oidc-client-ts";

export type CreateOrderRequest = {
  customerId: string;
  items: Array<{
    productId: string;
    quantity: number;
    unitPrice: number;
  }>;
};

export class OrderApiClient {
  constructor(
    private readonly baseUrl: string,
    private readonly userManager: UserManager
  ) {}

  async createOrder(request: CreateOrderRequest): Promise<{ orderId: string; status: string }> {
    const user = await this.userManager.getUser();
    if (!user || user.expired) {
      throw new Error(class="str">"User is not authenticated or token is expired");
    }

    const response = await fetch(`${this.baseUrl}/api/orders`, {
      method: class="str">"POST",
      headers: {
        class="str">"Content-Type": class="str">"application/json",
        class="str">"Authorization": `Bearer ${user.access_token}`,
        class="str">"X-Correlation-ID": crypto.randomUUID()
      },
      body: JSON.stringify(request)
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`Order API failed with ${response.status}: ${text}`);
    }

    return await response.json();
  }
}

Das Frontend enthält keine Secrets. Es nutzt Tokens des angemeldeten Benutzers und sendet eine Correlation-ID, damit Logs über Frontend und Backend verbunden werden können.

12. Dockerfile für Spring Boot Service

dockerfile
FROM eclipse-temurin:21-jre-alpine

RUN addgroup -S app && adduser -S app -G app
WORKDIR /app

COPY target/order-service.jar /app/order-service.jar

USER app
EXPOSE 8080

ENV JAVA_OPTS=class="str">"-XX:MaxRAMPercentage=75 -XX:+UseContainerSupport"

ENTRYPOINT [class="str">"sh", class="str">"-c", class="str">"java $JAVA_OPTS -jar /app/order-service.jar"]

Das Image läuft nicht als Root. JVM-Speicher wird containerfreundlich begrenzt. In einem gehärteten Build kämen zusätzlich SBOM, Image-Scan und Signatur dazu.

13. OpenShift Deployment, Service und Route

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: order-prod
  labels:
    app: order-service
    team: order
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
        team: order
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.8.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: order-service-config
            - secretRef:
                name: order-service-secret
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            periodSeconds: 10
            failureThreshold: 6
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            periodSeconds: 20
            failureThreshold: 3
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: class="str">"1"
              memory: 1024Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
  namespace: order-prod
spec:
  selector:
    app: order-service
  ports:
    - name: http
      port: 8080
      targetPort: http
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  name: order-service
  namespace: order-prod
spec:
  host: api.alpine.example.com
  path: /api/orders
  to:
    kind: Service
    name: order-service
  tls:
    termination: edge
    insecureEdgeTerminationPolicy: Redirect

14. Kustomize Overlay für Produktion

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: order-prod

resources:
  - ../../base
  - network-policy.yaml
  - service-monitor.yaml
  - prometheus-rule.yaml

images:
  - name: registry.example.com/alpine/order-service
    newTag: 1.8.0

configMapGenerator:
  - name: order-service-config
    literals:
      - SPRING_PROFILES_ACTIVE=openshift
      - OUTBOX_PUBLISHER_FIXED_DELAY_MS=5000
      - LOGGING_LEVEL_ROOT=INFO

patches:
  - target:
      kind: Deployment
      name: order-service
    patch: |-
      - op: replace
        path: /spec/replicas
        value: 3

Kustomize trennt Basisdefinition und Umgebungsanpassung. Dadurch muss Produktion nicht durch Kopieren kompletter YAML-Dateien gepflegt werden.

15. Argo CD Application

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: openshift-gitops
spec:
  project: alpine-prod
  source:
    repoURL: https://git.example.com/alpine/platform-gitops.git
    targetRevision: main
    path: apps/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=false
      - ApplyOutOfSyncOnly=true

Für Produktion ist prune: false bewusst konservativ gewählt. Automatisches Löschen von Ressourcen kann sinnvoll sein, sollte aber kontrolliert eingeführt werden.

16. PrometheusRule für Outbox Backlog

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: order-service-alerts
  namespace: order-prod
spec:
  groups:
    - name: order-service.rules
      rules:
        - alert: OrderOutboxBacklogHigh
          expr: order_outbox_unpublished_events > 100
          for: 10m
          labels:
            severity: warning
            team: order
          annotations:
            summary: class="str">"Order Outbox backlog is high"
            description: class="str">"More than 100 unpublished events for over 10 minutes. Check broker, publisher job and database locks."
        - alert: OrderServiceReadinessFailing
          expr: kube_pod_container_status_ready{namespace=class="str">"order-prod",container=class="str">"order-service"} == 0
          for: 5m
          labels:
            severity: critical
            team: order
          annotations:
            summary: class="str">"Order Service pod is not ready"
            description: class="str">"At least one Order Service pod is not ready for more than five minutes."

Alerts müssen konkrete Handlungen auslösen. Ein Alert ohne Runbook ist oft nur Lärm.

17. Terraform/OpenTofu: Object Storage für Partnerdateien

hcl
variable class="str">"environment" {
  type        = string
  description = class="str">"Environment name such as dev, test or prod"
}

resource class="str">"aws_s3_bucket" class="str">"partner_files" {
  bucket = class="str">"alpine-partner-files-${var.environment}"

  tags = {
    owner          = class="str">"team-integration"
    environment    = var.environment
    data_class     = class="str">"internal"
    operational_use = class="str">"file-processing"
  }
}

resource class="str">"aws_s3_bucket_versioning" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  versioning_configuration {
    status = class="str">"Enabled"
  }
}

resource class="str">"aws_s3_bucket_lifecycle_configuration" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  rule {
    id     = class="str">"expire-rejected-after-90-days"
    status = class="str">"Enabled"

    filter {
      prefix = class="str">"rejected/"
    }

    expiration {
      days = 90
    }
  }
}

Dieses Beispiel zeigt Kosten- und Betriebsdenken. Abgelehnte Dateien bleiben nicht unbegrenzt liegen.

18. Ansible: File Processing Worker als VM-Service

yaml
---
- name: Deploy file processing worker on VM
  hosts: file_processing_workers
  become: true

  vars:
    worker_user: fileworker
    app_dir: /opt/alpine/file-processing-worker

  tasks:
    - name: Create service user
      ansible.builtin.user:
        name: class="str">"{{ worker_user }}"
        system: true
        shell: /usr/sbin/nologin

    - name: Create application directory
      ansible.builtin.file:
        path: class="str">"{{ app_dir }}"
        state: directory
        owner: class="str">"{{ worker_user }}"
        group: class="str">"{{ worker_user }}"
        mode: class="str">"0750"

    - name: Copy worker jar
      ansible.builtin.copy:
        src: files/file-processing-worker.jar
        dest: class="str">"{{ app_dir }}/file-processing-worker.jar"
        owner: class="str">"{{ worker_user }}"
        group: class="str">"{{ worker_user }}"
        mode: class="str">"0640"
      notify: Restart file worker

    - name: Install systemd unit
      ansible.builtin.template:
        src: templates/file-processing-worker.service.j2
        dest: /etc/systemd/system/file-processing-worker.service
        mode: class="str">"0644"
      notify:
        - Reload systemd
        - Restart file worker

    - name: Ensure service is enabled
      ansible.builtin.service:
        name: file-processing-worker
        enabled: true
        state: started

  handlers:
    - name: Reload systemd
      ansible.builtin.systemd:
        daemon_reload: true

    - name: Restart file worker
      ansible.builtin.service:
        name: file-processing-worker
        state: restarted

Dieses Beispiel zeigt, dass VM-Betrieb ebenfalls automatisiert wird. Hybrid bedeutet nicht manuell.

19. systemd Unit für den Worker

ini
[Unit]
Description=Alpine File Processing Worker
After=network-online.target
Wants=network-online.target

[Service]
User=fileworker
Group=fileworker
WorkingDirectory=/opt/alpine/file-processing-worker
EnvironmentFile=/etc/alpine/file-processing-worker.env
ExecStart=/usr/bin/java -XX:MaxRAMPercentage=70 -jar /opt/alpine/file-processing-worker/file-processing-worker.jar
Restart=on-failure
RestartSec=10
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/alpine/file-processing

[Install]
WantedBy=multi-user.target

Die Unit enthält Härtung. Der Worker bekommt nur den Schreibpfad, den er benötigt.

20. CI Pipeline für Maven und Container Build

yaml
name: order-service-ci

on:
  pull_request:
    paths:
      - class="str">"apps/order-service/**"
  push:
    branches:
      - main

jobs:
  build-test-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">"21"
          cache: maven

      - name: Maven verify
        working-directory: apps/order-service
        run: ./mvnw -B verify

      - name: Build container image
        working-directory: apps/order-service
        run: |
          docker build -t registry.example.com/alpine/order-service:${{ github.sha }} .

      - name: Scan image
        run: |
          trivy image --exit-code 1 --severity HIGH,CRITICAL registry.example.com/alpine/order-service:${{ github.sha }}

Diese Pipeline bricht bei kritischen Schwachstellen ab. In echten Projekten braucht es zusätzlich Ausnahmen mit Ablaufdatum, damit Sicherheitswarnungen nicht dauerhaft ignoriert werden.

21. Runbook: Outbox Backlog

Vorlage / Textauszug

Runbook: Order Outbox Backlog

Symptom

Alert `OrderOutboxBacklogHigh` ist aktiv. Die Metrik `order_outbox_unpublished_events` liegt länger als zehn Minuten über 100.

Erste Prüfung

oc get pods -n order-prod -l app=order-service

oc logs -n order-prod deployment/order-service --tail=200 | grep -i outbox

oc exec -n order-prod deployment/order-service -- curl -s localhost:8080/actuator/health

Vorlage / Textauszug
Broker prüfen

oc get pods -n messaging

oc logs -n messaging statefulset/kafka --tail=200

Vorlage / Textauszug
Datenbank prüfen

SELECT count(*) FROM outbox_events WHERE published_at IS NULL;

SELECT event_type, count(*) FROM outbox_events WHERE published_at IS NULL GROUP BY event_type;

SELECT id, publish_attempts, last_error FROM outbox_events WHERE published_at IS NULL ORDER BY created_at LIMIT 20;

Vorlage / Textauszug
Entscheidung

Wenn Kafka nicht erreichbar ist, Messaging-Team informieren und Publisher nicht manuell umgehen.

Wenn nur einzelne Events fehlschlagen, Payload prüfen und gegebenenfalls in Dead Letter Prozess überführen.

Wenn Datenbanklocks sichtbar sind, DBA-Team einbinden.

Abschluss

Nach Behebung prüfen:

SELECT count(*) FROM outbox_events WHERE published_at IS NULL;

Vorlage / Textauszug

Ein Runbook ist dann gut, wenn es nicht nur Diagnosebefehle enthält, sondern auch Entscheidungspunkte.

22. ADR: Outbox statt direkter Broker-Veröffentlichung

Vorlage / Textauszug

ADR 012: Outbox Pattern für Order Events

Status

Akzeptiert

Kontext

Der Order Service speichert Bestellungen in PostgreSQL und muss fachliche Events an Billing und Reporting veröffentlichen. Eine direkte Veröffentlichung an Kafka innerhalb des Use Case wäre riskant, weil Datenbanktransaktion und Broker-Veröffentlichung nicht atomar sind.

Entscheidung

Der Order Service schreibt fachliche Events in eine Outbox-Tabelle innerhalb derselben Transaktion wie die Bestellung. Ein separater Publisher veröffentlicht die Events an Kafka und markiert sie danach als veröffentlicht.

Konsequenzen

Das System wird robuster gegenüber temporären Broker-Ausfällen. Dafür entsteht ein zusätzlicher Betriebsaspekt: Die Outbox muss überwacht werden. Es braucht Metriken, Alerts und ein Runbook für Backlog-Situationen.

23. Abschluss dieses Schritts

Dieser Teil ergänzt zusammenhängende technische Substanz. Die Beispiele bilden eine Brücke zwischen Fachbuch und echtem Repository. Sie zeigen, wie Architekturentscheidungen in Code, YAML, Tests, Infrastruktur und Runbooks sichtbar werden.

Für die spätere finale HTML-Version ist dieser Schritt wichtig, weil die Datei durch echten Code wächst. Die Größe entsteht nicht durch wiederholte Absätze, sondern durch konkrete Artefakte.

Teil H: Praxisfälle, Betriebsstörungen und End-to-End-Szenarien

Teil H: Praxisfälle, Betriebsstörungen und End-to-End-Szenarien

PlanZielbildBuildUmsetzungRunBetriebTeil H: Praxisfälle, Betriebsstörungen und End-to-End-Szenarien: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 31, Teil 34, Teil 37.

Finaler End-to-End-Praxissprint und HTML-Masterbuild

Ziel dieses großen Schritts

Dieser Teil führt das bisherige Lernbuch aus der reinen Planung in einen zusammenhängenden Praxissprint. Der Schwerpunkt liegt nicht auf einer weiteren Theorie-Erweiterung, sondern auf einem End-to-End-Durchlauf, der alle bisherigen Themen verbindet: Fachprozess, Anwendungscode, Datenbank, Eventing, Security, OpenShift, GitOps, Observability, Backup, Runbook und Qualitätsprüfung. Dadurch entsteht ein konkreter roter Faden für die spätere HTML-Version.

Der Sprint beantwortet eine einfache, aber realistische Enterprise-Frage: Was passiert technisch und organisatorisch, wenn ein Kunde im Customer Portal eine Bestellung anlegt, diese Bestellung gespeichert wird, ein Event erzeugt wird, Billing daraus eine Rechnung vorbereitet, der Betrieb den Ablauf überwacht und ein Fehlerfall über Runbook und GitOps nachvollziehbar behandelt wird?

Wichtig ist: Dieser Schritt ist kein künstlicher Zusatzblock. Er fasst die Fachkapitel praktisch zusammen und zeigt, wie die Architektur im Alltag funktioniert.

Ausgangsszenario

Die Alpine Retail & Services Group möchte einen produktionsnahen Durchlauf für das neue hybride Plattformmodell durchführen. Der Durchlauf soll klein genug sein, um in einem Lab umgesetzt zu werden, aber vollständig genug, um typische Enterprise-Fragen zu beantworten. Deshalb wird kein isolierter Demo-Service gebaut, sondern ein zusammenhängender Ablauf.

Der fachliche Ablauf lautet:

Vorlage / Textauszug

Kunde meldet sich im Customer Portal an.

Kunde legt eine Bestellung an.

Order Service validiert die Bestellung.

Order Service speichert Bestellung und Outbox Event in PostgreSQL.

Outbox Publisher veröffentlicht OrderCreated an Kafka oder RabbitMQ.

Billing Service konsumiert das Event idempotent.

Billing Service erzeugt einen Rechnungsentwurf.

Reporting Service kann den Status auswerten.

Observability zeigt Logs, Metriken und Trace.

Runbook beschreibt Fehleranalyse bei Backlog oder fehlgeschlagenem Billing.

GitOps stellt sicher, dass Deployment und Infrastruktur nachvollziehbar bleiben.

Dieser Ablauf verbindet moderne Anwendung, Daten, Messaging, Security, Betrieb und Plattform.

End-to-End-Architektur

mermaid
flowchart LR
    Browser[Kunde im Browser] --> Portal[Customer Portal]
    Portal --> Gateway[Route / API Gateway]
    Gateway --> OrderAPI[Order Service API]
    OrderAPI --> OrderDB[(Orders PostgreSQL)]
    OrderAPI --> Outbox[(Outbox Tabelle)]
    Outbox --> Publisher[Outbox Publisher]
    Publisher --> Broker[Kafka oder RabbitMQ]
    Broker --> Billing[Billing Service]
    Billing --> BillingDB[(Billing PostgreSQL)]
    Billing --> Audit[(Audit Log)]
    Reporting[Reporting Service] --> OrderDB
    Reporting --> BillingDB
    Keycloak[Keycloak / OIDC] --> Portal
    Keycloak --> OrderAPI
    Prometheus[Prometheus] --> OrderAPI
    Prometheus --> Billing
    Loki[Loki] --> OrderAPI
    Loki --> Billing
    Grafana[Grafana] --> Prometheus

Die Architektur zeigt zwei wichtige Prinzipien. Erstens: Das Portal greift nicht direkt auf Datenbanken zu. Zweitens: Billing hängt nicht synchron am Order Service, sondern verarbeitet ein fachliches Event. Dadurch bleibt der Bestellprozess stabiler, auch wenn Billing kurzzeitig nicht verfügbar ist.

Fachliche Schnittstelle des Order Service

Die REST-Schnittstelle soll bewusst klein und klar sein. Der Order Service nimmt eine Bestellung entgegen und gibt eine technische sowie fachliche Bestätigung zurück. Die API soll keine internen Datenbankdetails offenlegen.

yaml
openapi: 3.0.3
info:
  title: Order Service API
  version: 1.0.0
paths:
  /api/orders:
    post:
      summary: Erstellt eine neue Bestellung
      operationId: createOrder
      security:
        - oidc: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: class="str">'#/components/schemas/CreateOrderRequest'
      responses:
        class="str">'201':
          description: Bestellung wurde angenommen
          content:
            application/json:
              schema:
                $ref: class="str">'#/components/schemas/CreateOrderResponse'
        class="str">'400':
          description: Fachlich ungültige Bestellung
        class="str">'401':
          description: Nicht authentifiziert
        class="str">'403':
          description: Keine Berechtigung
components:
  securitySchemes:
    oidc:
      type: openIdConnect
      openIdConnectUrl: https://keycloak.alpine.example.com/realms/alpine/.well-known/openid-configuration
  schemas:
    CreateOrderRequest:
      type: object
      required: [customerId, items]
      properties:
        customerId:
          type: string
        items:
          type: array
          minItems: 1
          items:
            $ref: class="str">'#/components/schemas/OrderItemRequest'
    OrderItemRequest:
      type: object
      required: [productId, quantity, unitPrice]
      properties:
        productId:
          type: string
        quantity:
          type: integer
          minimum: 1
        unitPrice:
          type: number
          format: decimal
    CreateOrderResponse:
      type: object
      properties:
        orderId:
          type: string
        status:
          type: string
        traceId:
          type: string

Die API ist bewusst nicht überladen. Ein späteres Admin-Portal kann zusätzliche Endpunkte bekommen, aber das Kundenportal braucht nur einen klaren Bestell-Endpunkt.

Spring Boot Controller

Der Controller bleibt dünn. Er übersetzt HTTP in einen Use Case und kümmert sich nicht um Datenbankdetails oder Event-Veröffentlichung.

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import org.slf4j.MDC;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    @PreAuthorize(class="str">"hasAuthority('SCOPE_order:create') or hasRole('CUSTOMER')")
    public ResponseEntity<CreateOrderResponse> create(@Valid @RequestBody CreateOrderRequest request) {
        var result = createOrderUseCase.createOrder(new CreateOrderCommand(
                request.customerId(),
                request.items().stream()
                        .map(item -> new CreateOrderCommand.Item(
                                item.productId(),
                                item.quantity(),
                                item.unitPrice()))
                        .toList()
        ));

        var response = new CreateOrderResponse(
                result.orderId(),
                result.status(),
                MDC.get(class="str">"traceId")
        );

        return ResponseEntity
                .created(URI.create(class="str">"/api/orders/" + result.orderId()))
                .body(response);
    }
}

Der Controller enthält absichtlich keine Speicherung. Dadurch bleibt die Grenze zwischen Webschicht und Anwendungsschicht sichtbar.

Domänenmodell für Bestellung

Die Domäne beschreibt fachliche Regeln. Eine Bestellung ohne Positionen ist ungültig. Eine Position mit Menge null ist ebenfalls ungültig. Solche Regeln gehören nicht nur in das Frontend, sondern in die Backend-Domäne.

java
package com.seb4u.demo.spring.order.domain;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.UUID;

public class Order {

    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderItem> items;
    private final OrderStatus status;
    private final Instant createdAt;

    private Order(OrderId id, CustomerId customerId, List<OrderItem> items, OrderStatus status, Instant createdAt) {
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException(class="str">"order must contain at least one item");
        }
        this.id = id;
        this.customerId = customerId;
        this.items = List.copyOf(items);
        this.status = status;
        this.createdAt = createdAt;
    }

    public static Order create(CustomerId customerId, List<OrderItem> items, Instant createdAt) {
        return new Order(new OrderId(UUID.randomUUID()), customerId, items, OrderStatus.CREATED, createdAt);
    }

    public BigDecimal totalAmount() {
        return items.stream()
                .map(item -> item.unitPrice().multiply(BigDecimal.valueOf(item.quantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public OrderId id() { return id; }
    public CustomerId customerId() { return customerId; }
    public List<OrderItem> items() { return items; }
    public OrderStatus status() { return status; }
    public Instant createdAt() { return createdAt; }
}

Diese Klasse bleibt frei von JPA-Annotationen. Das ist eine bewusste Entscheidung. Die Domäne soll nicht direkt vom Persistenzframework abhängig sein.

Outbox Event als fachlicher Vertrag

Das Event ist ein Vertrag zwischen Order und Billing. Es darf keine zufällige Kopie einer Datenbankzeile sein. Es beschreibt ein fachliches Ereignis.

java
package com.seb4u.demo.spring.order.domain.events;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;

public record OrderCreatedEvent(
        String eventId,
        String orderId,
        String customerId,
        BigDecimal totalAmount,
        List<Item> items,
        Instant occurredAt
) {
    public record Item(String productId, int quantity, BigDecimal unitPrice) {}
}

Ein gutes Event enthält genug Information für den Consumer, aber nicht beliebig viele interne Details. Billing muss wissen, welche Bestellung entstanden ist und welcher Betrag relevant ist. Billing muss nicht wissen, welche interne JPA-Entity verwendet wurde.

Outbox Writer

Der Outbox Writer speichert das Event in derselben Transaktion wie die Bestellung. Genau dadurch wird verhindert, dass Datenbankzustand und Eventstrom auseinanderlaufen.

java
package com.seb4u.demo.spring.order.infrastructure.messaging;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.seb4u.demo.spring.order.domain.events.OrderCreatedEvent;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.UUID;

@Component
public class JdbcOutboxWriter implements OutboxWriter {

    private final JdbcTemplate jdbcTemplate;
    private final ObjectMapper objectMapper;

    public JdbcOutboxWriter(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) {
        this.jdbcTemplate = jdbcTemplate;
        this.objectMapper = objectMapper;
    }

    @Override
    public void store(OrderCreatedEvent event) {
        try {
            String payload = objectMapper.writeValueAsString(event);
            jdbcTemplate.update(class="str">"""
                insert into outbox_events(
                    id, aggregate_type, aggregate_id, event_type, payload, created_at, publish_attempts
                ) values (?, ?, ?, ?, cast(? as jsonb), ?, 0)
                class="str">""",
                    UUID.fromString(event.eventId()),
                    class="str">"Order",
                    event.orderId(),
                    class="str">"OrderCreated",
                    payload,
                    Instant.now()
            );
        } catch (Exception ex) {
            throw new IllegalStateException(class="str">"Could not store outbox event", ex);
        }
    }
}

Der Writer kommuniziert nicht direkt mit Kafka oder RabbitMQ. Dadurch bleibt die Transaktion kurz und kontrollierbar.

Outbox Publisher

Der Publisher läuft separat. Er liest unveröffentlichte Events und sendet sie an den Broker. Wenn der Broker nicht erreichbar ist, bleibt das Event in der Outbox und kann später erneut verarbeitet werden.

java
package com.seb4u.demo.spring.order.infrastructure.messaging;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.List;
import java.util.UUID;

@Component
public class OutboxPublisher {

    private final JdbcTemplate jdbcTemplate;
    private final EventBrokerClient brokerClient;
    private final Counter publishedCounter;
    private final Counter failedCounter;

    public OutboxPublisher(JdbcTemplate jdbcTemplate, EventBrokerClient brokerClient, MeterRegistry registry) {
        this.jdbcTemplate = jdbcTemplate;
        this.brokerClient = brokerClient;
        this.publishedCounter = Counter.builder(class="str">"outbox_events_published_total").register(registry);
        this.failedCounter = Counter.builder(class="str">"outbox_events_failed_total").register(registry);
    }

    @Scheduled(fixedDelayString = class="str">"${outbox.publisher.delay:5000}")
    public void publishBatch() {
        List<OutboxRecord> records = findNextRecords();
        for (OutboxRecord record : records) {
            publishOne(record);
        }
    }

    @Transactional
    protected void publishOne(OutboxRecord record) {
        try {
            brokerClient.publish(record.eventType(), record.payload());
            jdbcTemplate.update(class="str">"update outbox_events set published_at = ? where id = ?", Instant.now(), record.id());
            publishedCounter.increment();
        } catch (Exception ex) {
            jdbcTemplate.update(class="str">"""
                update outbox_events
                set publish_attempts = publish_attempts + 1, last_error = ?
                where id = ?
                class="str">""", ex.getMessage(), record.id());
            failedCounter.increment();
        }
    }

    private List<OutboxRecord> findNextRecords() {
        return jdbcTemplate.query(class="str">"""
            select id, event_type, payload::text
            from outbox_events
            where published_at is null
            order by created_at
            limit 50
            class="str">""", (rs, rowNum) -> new OutboxRecord(
                UUID.fromString(rs.getString(class="str">"id")),
                rs.getString(class="str">"event_type"),
                rs.getString(class="str">"payload")
        ));
    }
}

In produktionsnahen Umgebungen würde man zusätzlich Locking oder SKIP LOCKED verwenden, wenn mehrere Publisher parallel laufen. Das Prinzip bleibt gleich: Events werden kontrolliert und wiederholbar veröffentlicht.

Billing Consumer mit Idempotenz

Ein Consumer muss damit rechnen, dass Events mehrfach ankommen. Das ist kein Sonderfall, sondern ein normales Verhalten in verteilten Systemen. Billing speichert deshalb verarbeitete Event-IDs.

java
package com.seb4u.demo.spring.billing.integration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.seb4u.demo.spring.billing.application.CreateInvoiceDraftUseCase;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class OrderCreatedConsumer {

    private final ProcessedEventRepository processedEventRepository;
    private final CreateInvoiceDraftUseCase createInvoiceDraftUseCase;
    private final ObjectMapper objectMapper;

    public OrderCreatedConsumer(
            ProcessedEventRepository processedEventRepository,
            CreateInvoiceDraftUseCase createInvoiceDraftUseCase,
            ObjectMapper objectMapper
    ) {
        this.processedEventRepository = processedEventRepository;
        this.createInvoiceDraftUseCase = createInvoiceDraftUseCase;
        this.objectMapper = objectMapper;
    }

    @Transactional
    public void handle(String payload) {
        OrderCreatedMessage message = read(payload);

        if (processedEventRepository.existsByEventId(message.eventId())) {
            return;
        }

        createInvoiceDraftUseCase.createDraft(
                message.orderId(),
                message.customerId(),
                message.totalAmount(),
                message.items()
        );

        processedEventRepository.markProcessed(message.eventId(), class="str">"OrderCreated");
    }

    private OrderCreatedMessage read(String payload) {
        try {
            return objectMapper.readValue(payload, OrderCreatedMessage.class);
        } catch (Exception ex) {
            throw new IllegalArgumentException(class="str">"Invalid OrderCreated payload", ex);
        }
    }
}

Idempotenz ist hier kein Luxus. Ohne Idempotenz könnte eine Rechnung mehrfach erzeugt werden, wenn ein Event erneut zugestellt wird.

Datenbankmigration für Billing

sql
CREATE TABLE invoice_drafts (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL,
    customer_id VARCHAR(80) NOT NULL,
    total_amount NUMERIC(12, 2) NOT NULL,
    status VARCHAR(40) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL
);

CREATE UNIQUE INDEX uq_invoice_drafts_order_id
ON invoice_drafts(order_id);

CREATE TABLE processed_events (
    event_id UUID PRIMARY KEY,
    event_type VARCHAR(120) NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE NOT NULL
);

CREATE TABLE billing_audit_log (
    id UUID PRIMARY KEY,
    action VARCHAR(120) NOT NULL,
    reference_id VARCHAR(120) NOT NULL,
    message TEXT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL
);

Die eindeutige Einschränkung auf order_id verhindert doppelte Rechnungsentwürfe zusätzlich auf Datenbankebene. Das ist wichtig, weil fachliche Sicherheit nicht nur im Code liegen sollte.

Security-Durchlauf

Der Kunde authentifiziert sich über Keycloak. Das Portal erhält ein Access Token. Der Order Service prüft das Token und die Rolle. Der Billing Service ist nicht direkt vom Browser erreichbar. Interne Service-zu-Service-Kommunikation wird über Netzwerkregeln, ServiceAccounts und technische Credentials abgesichert.

Die Rollen können grob so aussehen:

Vorlage / Textauszug

CUSTOMER: darf eigene Bestellung anlegen und anzeigen.

SUPPORT_AGENT: darf Bestellungen suchen und Status prüfen.

BILLING_OPERATOR: darf Rechnungsentwürfe prüfen.

PLATFORM_OPERATOR: darf Deployments und Logs einsehen.

SECURITY_AUDITOR: darf Auditnachweise lesen, aber keine Fachänderungen durchführen.

Diese Rollen dürfen nicht ungeprüft als Adminrechte im Cluster verwendet werden. Fachliche Rollen und Kubernetes-RBAC sind unterschiedliche Ebenen.

Spring Security Konfiguration

java
package com.seb4u.demo.spring.order.infrastructure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(class="str">"/actuator/health/**").permitAll()
                        .requestMatchers(class="str">"/actuator/prometheus").hasRole(class="str">"PLATFORM_OPERATOR")
                        .anyRequest().authenticated())
                .oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
                .build();
    }

    @Bean
    JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(new KeycloakRealmRoleConverter());
        return converter;
    }
}

Health-Endpunkte dürfen erreichbar sein, damit OpenShift Probes funktionieren. Metriken werden restriktiver behandelt, weil sie interne Informationen enthalten können.

Kubernetes-Manifeste für den Sprint

Das Lab verwendet eine klare Trennung zwischen Anwendung und Umgebung. Die Basisdefinition beschreibt den Service allgemein. Umgebungsspezifische Werte liegen in Overlays.

Skizze / Textauszug
platform/kustomize/order-service/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── route.yaml
│   ├── configmap.yaml
│   ├── serviceaccount.yaml
│   ├── networkpolicy.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    ├── test/
    └── prod/

Diese Struktur verhindert, dass für jede Umgebung vollständig getrennte YAML-Welten entstehen.

Deployment mit Probes und Ressourcen

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  labels:
    app.kubernetes.io/name: order-service
    app.kubernetes.io/part-of: alpine-retail
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: order-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: order-service
    spec:
      serviceAccountName: order-service
      containers:
        - name: order-service
          image: registry.example.com/alpine/order-service:1.0.0
          ports:
            - name: http
              containerPort: 8080
          envFrom:
            - configMapRef:
                name: order-service-config
          env:
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: order-db-credentials
                  key: password
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 40
            periodSeconds: 20
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: class="str">"1"
              memory: 1024Mi

Die Probes sorgen dafür, dass OpenShift den Zustand der Anwendung verstehen kann. Ressourcen verhindern, dass ein Service unbegrenzt CPU oder Speicher nutzt.

NetworkPolicy für den Order Service

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-egress
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/name: order-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              platform.alpine.io/name: data-services
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector:
            matchLabels:
              platform.alpine.io/name: messaging
      ports:
        - protocol: TCP
          port: 9092
    - to:
        - namespaceSelector:
            matchLabels:
              platform.alpine.io/name: identity
      ports:
        - protocol: TCP
          port: 443

Diese Policy macht die Architekturregel technisch sichtbar: Order darf zur Datenbank, zum Broker und zur Identität. Es darf nicht beliebig zu allen anderen Services sprechen.

Argo CD Application

yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
  namespace: openshift-gitops
spec:
  project: alpine-retail
  source:
    repoURL: https://git.example.com/alpine/platform-gitops.git
    targetRevision: main
    path: platform/kustomize/order-service/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: order-prod
  syncPolicy:
    automated:
      prune: false
      selfHeal: true
    syncOptions:
      - CreateNamespace=false

prune ist hier bewusst deaktiviert. In produktionsnahen Umgebungen wird automatisches Löschen oft vorsichtiger behandelt als automatisches Aktualisieren.

Observability im Sprint

Der Sprint braucht drei Arten von Beobachtbarkeit.

Erstens: Logs. Jeder Request bekommt eine Trace-ID. Fehler werden strukturiert geloggt. Fachliche IDs wie orderId oder eventId werden ergänzt, aber sensible Daten wie Tokens oder personenbezogene Details werden nicht in Logs geschrieben.

Zweitens: Metriken. Der Order Service liefert Metriken zu HTTP-Anfragen, Datenbankverbindungen, Outbox-Backlog und Fehlern bei Event-Veröffentlichung.

Drittens: Traces. Ein Trace verbindet Portal, Order Service, Outbox Publisher und Billing. Dadurch kann man erkennen, wo ein Ablauf langsam oder fehlerhaft wird.

PrometheusRule für Outbox Backlog

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: order-service-alerts
  namespace: order-prod
spec:
  groups:
    - name: order-service.rules
      rules:
        - alert: OrderOutboxBacklogGrowing
          expr: order_outbox_unpublished_events > 100
          for: 10m
          labels:
            severity: warning
            team: order
          annotations:
            summary: class="str">"Order outbox backlog grows"
            description: class="str">"More than 100 unpublished outbox events exist for longer than 10 minutes."
        - alert: OrderServiceHighErrorRate
          expr: rate(http_server_requests_seconds_count{status=~class="str">"5.."}[5m]) > 1
          for: 5m
          labels:
            severity: critical
            team: order
          annotations:
            summary: class="str">"Order service returns many server errors"
            description: class="str">"The Order Service has an elevated 5xx error rate."

Alerts sind nur nützlich, wenn sie mit Runbooks verbunden sind. Sonst erzeugen sie Lärm statt Hilfe.

Runbook: Outbox Backlog wächst

Vorlage / Textauszug

Runbook: Order Outbox Backlog wächst

Symptom

Der Alert `OrderOutboxBacklogGrowing` ist aktiv. Die Anzahl unveröffentlichter Events steigt seit mindestens 10 Minuten.

Sofortprüfung

oc get pods -n order-prod -l app.kubernetes.io/name=order-service

oc logs -n order-prod deployment/order-service --tail=200 | grep -i outbox

Vorlage / Textauszug
Datenbankprüfung

select count(*)

from outbox_events

where published_at is null;

select event_type, publish_attempts, count(*)

from outbox_events

where published_at is null

group by event_type, publish_attempts

order by publish_attempts desc;

Vorlage / Textauszug
Brokerprüfung

oc get pods -n messaging

oc logs -n messaging statefulset/kafka --tail=100

Vorlage / Textauszug
Häufige Ursachen
  • Broker ist nicht erreichbar.
  • NetworkPolicy blockiert Egress.
  • Credentials für Broker sind falsch.
  • Payload ist inkompatibel.
  • Publisher läuft nicht oder ist falsch konfiguriert.
Entscheidung

Wenn Broker nicht erreichbar ist, kein manueller Datenbankeingriff.

Wenn Payload inkompatibel ist, fehlerhafte Events sichern und fachlich bewerten.

Wenn nur Publisher-Konfiguration fehlerhaft ist, GitOps-Konfiguration korrigieren und ausrollen.

Dieses Runbook verbindet Alert, Anwendung, Datenbank und Plattform. Genau das ist der Zweck eines produktionsnahen Betriebsdokuments.

Testcontainers-End-to-End-Test

Der End-to-End-Test startet PostgreSQL und einen Broker. Er prüft nicht jedes Detail, aber den wichtigsten Pfad: Bestellung anlegen, Outbox Event speichern, Event verarbeiten.

java
package com.seb4u.demo.spring.order;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.math.BigDecimal;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderFlowIntegrationTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(class="str">"postgres:16-alpine");

    private final TestRestTemplate restTemplate;

    OrderFlowIntegrationTest(TestRestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Test
    void createsOrderAndStoresOutboxEvent() {
        var request = new CreateOrderRequest(
                class="str">"customer-1000",
                List.of(new CreateOrderRequest.Item(class="str">"product-42", 2, new BigDecimal(class="str">"19.90")))
        );

        var response = restTemplate.postForEntity(class="str">"/api/orders", new HttpEntity<>(request), CreateOrderResponse.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody()).isNotNull();
        assertThat(response.getBody().orderId()).isNotBlank();
    }
}

Ein späterer Test kann zusätzlich prüfen, ob ein Outbox-Datensatz existiert. Für das Lernbuch reicht hier der erste wichtige Pfad.

CI-Pipeline für den Sprint

yaml
name: order-service-ci

on:
  pull_request:
    paths:
      - class="str">'apps/order-service/**'
      - class="str">'.github/workflows/order-service-ci.yaml'

jobs:
  build-test-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: class="str">'21'
          cache: maven

      - name: Build and test
        working-directory: apps/order-service
        run: mvn -B clean verify

      - name: Build image
        working-directory: apps/order-service
        run: docker build -t registry.example.com/alpine/order-service:${{ github.sha }} .

      - name: Render Kubernetes manifests
        run: kubectl kustomize platform/kustomize/order-service/overlays/test

Die Pipeline baut nicht nur Java. Sie prüft auch, ob die Kubernetes-Manifeste renderbar sind. Dadurch werden YAML-Fehler früher sichtbar.

Pull-Request-Abnahme für diesen Sprint

Ein Pull Request für den End-to-End-Sprint ist nur fertig, wenn mehrere Perspektiven erfüllt sind.

Vorlage / Textauszug

Code: Controller, Use Case, Domäne, Persistenz und Outbox sind getestet.

Datenbank: Migrationen sind vorhanden und rückwärtskompatibel bewertet.

Security: Endpunkte sind geschützt und Rollen dokumentiert.

OpenShift: Deployment, Service, Route, ConfigMap, Secret-Referenz und NetworkPolicy existieren.

GitOps: Argo CD Application zeigt auf ein nachvollziehbares Overlay.

Observability: Logs, Metriken und Alerts sind vorhanden.

Runbook: mindestens ein realer Fehlerfall ist beschrieben.

Dokumentation: README und ADR wurden aktualisiert.

Diese Liste verhindert, dass ein Feature nur aus Anwendungscode besteht.

ADR: Outbox statt synchronem Billing-Aufruf

Vorlage / Textauszug

ADR: Order Service verwendet Outbox statt synchronem Billing-Aufruf

Status

Akzeptiert

Kontext

Beim Anlegen einer Bestellung muss Billing über neue Bestellungen informiert werden. Eine direkte synchrone Kommunikation vom Order Service zum Billing Service würde den Bestellprozess abhängig von der Verfügbarkeit des Billing Service machen.

Entscheidung

Der Order Service speichert ein OrderCreated Event in einer Outbox-Tabelle innerhalb derselben Datenbanktransaktion wie die Bestellung. Ein separater Publisher veröffentlicht das Event an den Broker. Billing konsumiert das Event idempotent.

Konsequenzen

Der Bestellprozess bleibt auch dann stabil, wenn Billing kurzzeitig nicht erreichbar ist. Dafür entsteht zusätzlicher Betriebsaufwand für Outbox Monitoring, Publisher und Backlog-Runbooks.

Dieser ADR zeigt eine echte Architekturentscheidung. Es geht nicht nur darum, ein Pattern zu nennen, sondern die Konsequenzen für Betrieb und Fehlerfälle zu dokumentieren.

Fehlerfall: Billing verarbeitet Event nicht

Ein typischer Fehlerfall im Sprint ist: Bestellung wurde angelegt, aber im Billing gibt es keinen Rechnungsentwurf. Die Analyse darf nicht raten, sondern muss entlang der Architektur laufen.

Prüfreihenfolge:

Vorlage / Textauszug
  1. Existiert die Bestellung in PostgreSQL Orders?
  2. Existiert ein Outbox Event?
  3. Wurde das Event veröffentlicht?
  4. Hat der Broker das Event angenommen?
  5. Hat Billing das Event konsumiert?
  6. Ist das Event in processed_events markiert?
  7. Gibt es einen Fehler im billing_audit_log?
  8. Gibt es Logs mit derselben traceId oder eventId?

Diese Reihenfolge reduziert Chaos. Jede Stufe prüft einen konkreten Übergang.

Betriebsentscheidung: manuelle Korrektur oder Replay?

Wenn Billing ein Event nicht verarbeitet hat, ist die Versuchung groß, manuell in der Datenbank etwas zu korrigieren. Das ist gefährlich. Zuerst muss geklärt werden, ob ein Replay möglich ist.

Ein Replay ist sinnvoll, wenn das Event korrekt ist und nur die Verarbeitung temporär fehlgeschlagen ist. Eine manuelle fachliche Korrektur ist nötig, wenn das Event selbst falsche Daten enthält oder eine fachliche Sonderentscheidung erforderlich ist.

Das Runbook muss deshalb zwischen technischen und fachlichen Fehlern unterscheiden.

Finale HTML-Relevanz dieses Schritts

Dieser Praxissprint ist wichtig für die spätere HTML-Datei, weil er viele vorherige Einzelthemen verbindet. In der HTML-Navigation darf dieser Schritt nicht als künstlicher Anhang erscheinen. Er soll als eigenes Hauptkapitel im Bereich Praxis und Masterbuild stehen.

Die HTML-Ausgabe muss dafür folgende Regeln einhalten:

Vorlage / Textauszug

Navigationseintrag: Finaler End-to-End-Praxissprint

Content-Kategorie: standardmäßig geschlossen

Unterabschnitte: fachlich benannt

Codeblöcke: farbig und horizontal scrollbar

Diagramme: inline oder Mermaid-Quelltext sichtbar

Suchfunktion: optionaler Komfort, nicht Voraussetzung

Abnahme dieses großen Schritts

Dieser Teil ist abgenommen, wenn folgende Punkte erfüllt sind:

Vorlage / Textauszug

Der End-to-End-Ablauf ist fachlich verständlich.

Order, Billing, Outbox und Broker sind verbunden.

Security ist im Ablauf integriert.

OpenShift-Manifeste sind enthalten.

GitOps ist enthalten.

Observability ist enthalten.

Runbook ist enthalten.

ADR ist enthalten.

Test und CI sind enthalten.

Der Inhalt besteht nicht aus Fülltabellen.

Der Inhalt enthält keine generischen Serientitel.

Übung

Erweitere den Sprint um einen Fehlerfall: Der Broker ist erreichbar, aber Billing lehnt das Event wegen inkompatiblem Payload ab.

Beschreibe:

Vorlage / Textauszug
  1. Welche Logs entstehen?
  2. Welche Metrik steigt?
  3. Welche Tabelle zeigt den Fehler?
  4. Welches Runbook wird verwendet?
  5. Welche Änderung muss über GitOps ausgerollt werden?
  6. Ob ein Replay erlaubt ist.

Musterlösung

Eine mögliche Lösung:

Vorlage / Textauszug

Billing schreibt einen strukturierten Fehlerlog mit eventId, eventType und Validierungsfehler. Die Metrik billing_events_failed_total steigt. Das Event wird nicht als verarbeitet markiert. Wenn eine Dead Letter Queue verwendet wird, landet das Event dort; sonst bleibt die Verarbeitung im Fehlerstatus sichtbar.

Das Runbook für Billing-Eventfehler wird verwendet. Zuerst wird geprüft, ob der Payload fachlich korrekt ist. Wenn die Änderung nur eine Consumer-Inkompatibilität ist, wird Billing über Git korrigiert, die Pipeline ausgeführt und das Deployment über Argo CD synchronisiert.

Ein Replay ist erlaubt, wenn das ursprüngliche Event fachlich korrekt war und keine doppelte Rechnung erzeugt wurde. Die Idempotenzprüfung über processed_events und den eindeutigen Index auf order_id schützt zusätzlich vor doppelten Rechnungsentwürfen.

Merksätze

Vorlage / Textauszug

Ein End-to-End-Sprint ist wertvoller als zehn isolierte Demo-Blöcke.

Outbox löst kein Betriebsproblem automatisch; sie macht es kontrollierbar.

Idempotenz ist Pflicht, wenn Events erneut zugestellt werden können.

GitOps ist nicht nur Deployment, sondern Nachvollziehbarkeit.

Runbooks müssen konkrete Architekturübergänge prüfen.

Observability beginnt nicht nach dem Go-live, sondern während der Umsetzung.

Große Praxis-Szenarien für echte Enterprise-Tiefe

Ziel dieses Teils

Dieser Teil ergänzt keine künstlichen Erweiterungen, sondern echte Praxisfälle. Die bisherigen Kapitel erklären Architektur, Infrastruktur, OpenShift, Security, Daten, Betrieb, DevOps und AI-Unterstützung. Jetzt werden diese Themen in langen, zusammenhängenden Szenarien verbunden. Genau so entsteht aus Einzelwissen ein belastbares Enterprise-Verständnis.

Ein Praxis-Szenario ist nur dann wertvoll, wenn es mehrere Ebenen verbindet:

  • fachlicher Auslöser
  • beteiligte Anwendungen
  • Infrastrukturpfad
  • Security-Entscheidung
  • Datenfluss
  • Fehlerbild
  • Monitoring-Sicht
  • Betriebsreaktion
  • Dokumentationsnachweis
  • Lernkontrolle

Die folgenden Szenarien sind bewusst ausführlich. Sie ersetzen keine Tabellen, sondern erzählen den Ablauf so, wie er in einem echten Projekt sichtbar wird.

---

Szenario 1: Bestellung von Customer Portal bis Billing

Ausgangssituation

Ein Kunde meldet sich im Customer Portal an und legt eine Bestellung an. Das Portal läuft als moderne Webanwendung auf OpenShift. Der Login erfolgt über Keycloak. Nach erfolgreicher Anmeldung erhält das Frontend ein Token. Dieses Token wird bei API-Aufrufen an das Backend mitgegeben.

Der Order Service nimmt die Bestellung entgegen, prüft fachliche Regeln und speichert die Bestellung in PostgreSQL. Gleichzeitig schreibt er ein Outbox Event in dieselbe Datenbanktransaktion. Ein Outbox Publisher veröffentlicht dieses Event später an Kafka oder RabbitMQ. Der Billing Service konsumiert das Event und erzeugt daraus einen Rechnungsentwurf.

Der wichtigste Punkt: Der Billing Service ruft nicht direkt die Order-Datenbank ab. Er reagiert auf ein fachliches Event. Dadurch bleibt der Datenbesitz klar getrennt.

Ablauf im Zielbild

Skizze / Textauszug
Kunde
↓
Customer Portal
↓
Keycloak Login
↓
API Gateway / OpenShift Route
↓
Order Service
↓
PostgreSQL Orders
↓
Outbox Event
↓
Kafka oder RabbitMQ
↓
Billing Service
↓
PostgreSQL Billing
↓
Rechnungsentwurf

Warum dieser Ablauf gut ist

Die Bestellung wird im Order Service konsistent gespeichert. Das Event wird nicht direkt im Controller an Kafka geschickt, sondern über das Outbox Pattern abgesichert. Dadurch kann der Broker kurzfristig ausfallen, ohne dass die Bestellung verloren geht. Der Publisher kann später erneut versuchen, das Event zu senden.

Der Billing Service ist fachlich entkoppelt. Er braucht nicht zu wissen, wie der Order Service seine Tabellen intern strukturiert. Er erhält ein Ereignis, das für ihn fachlich relevant ist: Eine Bestellung wurde erstellt.

Beispiel: REST Controller

java
package com.seb4u.demo.spring.order.api;

import com.seb4u.demo.spring.order.application.CreateOrderCommand;
import com.seb4u.demo.spring.order.application.CreateOrderUseCase;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(class="str">"/api/orders")
public class OrderController {

    private final CreateOrderUseCase createOrderUseCase;

    public OrderController(CreateOrderUseCase createOrderUseCase) {
        this.createOrderUseCase = createOrderUseCase;
    }

    @PostMapping
    public ResponseEntity<CreateOrderResponse> createOrder(
            @AuthenticationPrincipal Jwt jwt,
            @Valid @RequestBody CreateOrderRequest request
    ) {
        String customerId = jwt.getSubject();

        var result = createOrderUseCase.createOrder(
                new CreateOrderCommand(customerId, request.items())
        );

        return ResponseEntity.accepted().body(
                new CreateOrderResponse(result.orderId(), result.status())
        );
    }
}

Typisches Fehlerbild

Der Kunde sieht im Portal: „Bestellung wurde angenommen“. Im Billing-System entsteht aber keine Rechnung. Entwickler vermuten zuerst einen Fehler im Billing Service. Der eigentliche Fehler liegt jedoch oft in der Mitte: Die Outbox wächst an, weil der Broker nicht erreichbar ist oder die Publisher-Konfiguration falsch ist.

Systematische Analyse

bash
# Order Service prüfen
oc get pods -n order-prod -l app=order-service
oc logs -n order-prod deployment/order-service --tail=200

# Outbox-Metriken prüfen
curl -s http://order-service:8080/actuator/prometheus | grep outbox

# Datenbank prüfen
psql class="str">"$ORDER_DB" -c class="str">"select count(*) from outbox_events where published_at is null;"

# Broker-Erreichbarkeit prüfen
oc rsh -n order-prod deployment/order-service
nc -vz kafka.messaging.svc.cluster.local 9092

Betriebsentscheidung

Wenn die Outbox wächst, darf man nicht blind Pods neu starten. Zuerst muss klar sein, ob Events noch sicher in der Datenbank liegen. Wenn ja, ist die fachliche Bestellung nicht verloren. Der Betrieb muss dann den Publisher-Pfad reparieren und danach kontrollieren, ob die Events abgearbeitet werden.

Nachweisartefakte

  • README des Order Service erklärt Outbox-Verhalten.
  • ADR dokumentiert, warum Outbox statt direktem Broker-Senden genutzt wird.
  • PrometheusRule alarmiert bei wachsender Outbox.
  • Runbook beschreibt Analyse und sichere Wiederaufnahme.
  • Integrationstest prüft Bestellung plus Outbox-Eintrag.

---

Szenario 2: Datenbankausfall und kontrollierter Restore

Ausgangssituation

Die Orders-Datenbank läuft auf Bare Metal, weil sie hohe I/O-Anforderungen hat. Eines Morgens melden mehrere Services Fehler. Das Customer Portal zeigt keine Bestellungen mehr an. Der Order Service liefert HTTP 503. Billing läuft noch, kann aber keine neuen Order Events verarbeiten.

Der erste Impuls wäre: Datenbank neu starten. In einem Enterprise-System reicht das nicht. Es muss entschieden werden, ob es ein kurzzeitiger Verbindungsfehler, ein Storage-Problem, ein beschädigter Index, ein voller Datenträger oder ein echter Datenverlust ist.

Analysepfad

Skizze / Textauszug
Symptom im Portal
↓
API Fehler
↓
Order Service Readiness rot
↓
Datenbankverbindung schlägt fehl
↓
DBA prüft Bare-Metal-Host
↓
Storage und PostgreSQL Logs prüfen
↓
Entscheidung: Neustart, Reparatur oder Restore

Prüfung im Cluster

bash
oc get pods -n order-prod
oc describe pod -n order-prod -l app=order-service
oc logs -n order-prod deployment/order-service --tail=100
oc get events -n order-prod --sort-by=.lastTimestamp

Prüfung auf Datenbankseite

bash
systemctl status postgresql
journalctl -u postgresql --since class="str">"30 minutes ago"
df -h
free -m
ss -lntp | grep 5432
pg_isready -h localhost -p 5432

Restore-Entscheidung

Ein Restore darf nicht reflexartig durchgeführt werden. Vorher müssen RPO und RTO geprüft werden.

  • RPO beantwortet: Wie viele Daten dürfen maximal verloren gehen?
  • RTO beantwortet: Wie schnell muss das System wieder laufen?

Wenn WAL-Archivierung aktiv ist, kann ein Point-in-Time-Recovery möglich sein. Wenn nur nächtliche Backups existieren, wäre ein Restore auf den letzten Nachtstand ein fachliches Risiko.

Beispiel: Restore-Runbook-Auszug

Vorlage / Textauszug

Runbook: Orders-Datenbank Restore

Vorbedingungen
  • Incident Ticket ist eröffnet.
  • Fachbereich wurde informiert.
  • Letzter konsistenter Backup-Zeitpunkt ist bekannt.
  • WAL-Archivstatus wurde geprüft.
  • Restore-Zielzeitpunkt wurde freigegeben.
Schritte
  1. Schreibzugriffe auf Order Service deaktivieren.
  2. Aktuellen Zustand der beschädigten Datenbank sichern.
  3. Restore auf separatem Host oder separatem Volume durchführen.
  4. Konsistenzprüfung ausführen.
  5. Fachliche Stichproben durchführen.
  6. Anwendung gegen Restore-Ziel testen.
  7. Umschaltung freigeben.
  8. Nachkontrolle der Outbox und Billing-Verarbeitung durchführen.

Wichtiger Lernpunkt

Backup ist erst bewiesen, wenn Restore getestet wurde. Eine Backup-Datei allein ist kein Betriebsnachweis. Der Nachweis entsteht erst durch einen erfolgreichen Restore-Test mit dokumentiertem Ergebnis.

---

Szenario 3: NetworkPolicy blockiert Billing

Ausgangssituation

Nach einem Security-Hardening kann der Billing Service keine Events mehr aus Kafka konsumieren. Die Pods laufen, die Anwendung startet, aber die Lag-Metrik steigt. Im Log steht nur ein Timeout zum Broker.

Das Team vermutet zuerst Kafka. Kafka ist aber gesund. Der Fehler liegt in einer neuen NetworkPolicy, die Egress aus dem Billing Namespace zu streng blockiert.

Warum das realistisch ist

NetworkPolicies sind wichtig, aber sie verändern Kommunikationspfade. Wenn sie ohne genaue Service-Abhängigkeiten eingeführt werden, können funktionierende Systeme plötzlich isoliert werden.

Analyse

bash
oc get networkpolicy -n billing-prod
oc describe networkpolicy -n billing-prod
oc rsh -n billing-prod deployment/billing-service
nc -vz kafka.messaging.svc.cluster.local 9092

Korrekturbeispiel

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-billing-to-kafka
  namespace: billing-prod
spec:
  podSelector:
    matchLabels:
      app: billing-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              platform-zone: messaging
          podSelector:
            matchLabels:
              app: kafka
      ports:
        - protocol: TCP
          port: 9092

Betriebsreaktion

Die Policy wird nicht manuell im Cluster korrigiert, sondern im GitOps-Repository angepasst. Danach synchronisiert Argo CD den Zustand. So bleibt nachvollziehbar, warum die Freigabe existiert.

Nachweis

  • Pull Request mit Änderung der NetworkPolicy
  • Review durch Security oder Platform Team
  • Argo CD Sync-Historie
  • sinkender Kafka Consumer Lag
  • Incident-Kommentar mit Ursache und Prävention

---

Szenario 4: Keycloak-Rollenproblem im Admin Portal

Ausgangssituation

Interne Mitarbeiter melden, dass sie sich im Admin Portal anmelden können, aber keine Bestellungen sehen. Das Frontend lädt, der Login funktioniert, aber API-Aufrufe liefern HTTP 403.

Das ist ein wichtiger Unterschied:

Vorlage / Textauszug

401 bedeutet: nicht authentifiziert.

403 bedeutet: authentifiziert, aber nicht berechtigt.

Der Fehler liegt daher nicht im Login selbst, sondern im Rollen- oder Berechtigungsmodell.

Mögliche Ursachen

  • Rolle fehlt im Keycloak Client.
  • Benutzer ist nicht in der richtigen Gruppe.
  • Rollen werden im Token nicht als erwarteter Claim geliefert.
  • Spring Security liest falschen Claim.
  • API erwartet andere Authority-Namen als im Token vorhanden.

Beispiel: Rollen-Konverter

java
package com.seb4u.demo.spring.admin.security;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class KeycloakRoleConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.addAll(extractRealmRoles(jwt));
        authorities.addAll(extractClientRoles(jwt, class="str">"admin-portal"));
        return new JwtAuthenticationToken(jwt, authorities, jwt.getSubject());
    }

    private List<GrantedAuthority> extractRealmRoles(Jwt jwt) {
        Map<String, Object> realmAccess = jwt.getClaim(class="str">"realm_access");
        if (realmAccess == null || !realmAccess.containsKey(class="str">"roles")) {
            return List.of();
        }
        List<String> roles = (List<String>) realmAccess.get(class="str">"roles");
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(class="str">"ROLE_" + role.toUpperCase()))
                .toList();
    }

    private List<GrantedAuthority> extractClientRoles(Jwt jwt, String clientId) {
        Map<String, Object> resourceAccess = jwt.getClaim(class="str">"resource_access");
        if (resourceAccess == null || !resourceAccess.containsKey(clientId)) {
            return List.of();
        }
        Map<String, Object> client = (Map<String, Object>) resourceAccess.get(clientId);
        List<String> roles = (List<String>) client.getOrDefault(class="str">"roles", List.of());
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(class="str">"ROLE_" + role.toUpperCase()))
                .toList();
    }
}

Runbook-Prüfung

Vorlage / Textauszug
  1. Prüfen, ob Login funktioniert.
  2. HTTP-Status unterscheiden: 401 oder 403.
  3. Token Claims prüfen.
  4. Benutzergruppen in Keycloak prüfen.
  5. Client-Rollen prüfen.
  6. Spring Security Mapping prüfen.
  7. Audit Log auf verweigerte Aktion prüfen.

Lernpunkt

Identity ist nicht nur Login. Ein Benutzer kann korrekt angemeldet sein und trotzdem keine Berechtigung haben. Deshalb müssen Authentifizierung, Autorisierung und fachliche Rollen getrennt verstanden werden.

---

Szenario 5: Outbox Backlog und Dead Letter Queue

Ausgangssituation

Der Order Service schreibt Events korrekt in die Outbox. Der Publisher veröffentlicht Events an Kafka. Trotzdem verarbeitet Billing nicht alle Events. Einige Nachrichten landen in der Dead Letter Queue.

Der Fehler ist kein Infrastrukturfehler, sondern ein fachliches Kompatibilitätsproblem. Ein neues Feld im Event wurde eingeführt, aber Billing erwartet noch das alte Format.

Warum eine DLQ wichtig ist

Ohne Dead Letter Queue würden fehlerhafte Nachrichten immer wieder verarbeitet und den Consumer blockieren. Mit DLQ werden problematische Nachrichten isoliert, damit gültige Nachrichten weiterlaufen können.

Beispiel: Consumer mit Idempotenz

java
package com.seb4u.demo.spring.billing.messaging;

import com.seb4u.demo.spring.billing.application.CreateInvoiceFromOrderUseCase;
import com.seb4u.demo.spring.billing.infrastructure.persistence.ProcessedEventRepository;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class OrderCreatedConsumer {

    private final CreateInvoiceFromOrderUseCase useCase;
    private final ProcessedEventRepository processedEventRepository;

    public OrderCreatedConsumer(
            CreateInvoiceFromOrderUseCase useCase,
            ProcessedEventRepository processedEventRepository
    ) {
        this.useCase = useCase;
        this.processedEventRepository = processedEventRepository;
    }

    @KafkaListener(topics = class="str">"order.created", groupId = class="str">"billing-service")
    @Transactional
    public void consume(OrderCreatedMessage message) {
        if (processedEventRepository.existsByEventId(message.eventId())) {
            return;
        }

        useCase.createInvoice(
                message.eventId(),
                message.orderId(),
                message.customerId(),
                message.totalAmount()
        );

        processedEventRepository.markProcessed(message.eventId());
    }
}

Betriebsentscheidung

DLQ-Nachrichten dürfen nicht automatisch gelöscht werden. Sie enthalten fachliche Hinweise. Der Betrieb muss gemeinsam mit Entwicklung prüfen, ob Reprocessing möglich ist oder ob eine Korrekturtransformation notwendig ist.

Reprocessing-Regel

Vorlage / Textauszug

Nur Nachrichten erneut verarbeiten, wenn:

  • Ursache verstanden wurde,
  • Consumer korrigiert wurde,
  • Idempotenz gewährleistet ist,
  • fachliche Dopplungen ausgeschlossen sind,
  • Reprocessing dokumentiert wurde.

---

Szenario 6: VM-Reporting wird nach OpenShift migriert

Ausgangssituation

Das Reporting läuft auf einer VM. Der Prozess startet jede Nacht, liest Daten aus Orders und Billing, erzeugt CSV-Dateien und legt sie auf einem File Share ab. Das System funktioniert, aber es ist schwer zu warten. Logs liegen lokal auf der VM. Deployment erfolgt manuell. Fehler werden oft erst am nächsten Morgen bemerkt.

Das Ziel ist nicht, blind alles nach OpenShift zu verschieben. Zuerst wird analysiert, welche Teile wirklich containerisierbar sind.

Migrationsschritte

Vorlage / Textauszug
  1. Bestehenden VM-Prozess dokumentieren.
  2. Eingaben und Ausgaben identifizieren.
  3. Abhängigkeiten zu Treibern und File Shares prüfen.
  4. Konfiguration externalisieren.
  5. Logging vereinheitlichen.
  6. Job als Container bauen.
  7. Testweise als Kubernetes Job ausführen.
  8. Danach als CronJob planen.
  9. Monitoring und Alerts ergänzen.
  10. VM-Version erst nach erfolgreicher Parallelphase abschalten.

Beispiel: Kubernetes CronJob

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: reporting-nightly-export
  namespace: reporting-prod
spec:
  schedule: class="str">"15 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: reporting-export
              image: registry.example.com/alpine/reporting-export:1.0.0
              envFrom:
                - configMapRef:
                    name: reporting-config
                - secretRef:
                    name: reporting-secrets
              resources:
                requests:
                  cpu: class="str">"500m"
                  memory: class="str">"1Gi"
                limits:
                  cpu: class="str">"2"
                  memory: class="str">"4Gi"

Lernpunkt

Migration ist kein Kopieren von VM nach Container. Migration bedeutet, Laufzeit, Konfiguration, Datenzugriff, Monitoring, Fehlerbehandlung und Betriebsverantwortung neu zu ordnen.

---

Szenario 7: File Processing zwischen Bare Metal, Object Storage und Broker

Ausgangssituation

Partner liefern große Dateien. Manche Dateien sind klein und können direkt in OpenShift verarbeitet werden. Andere Dateien sind mehrere Gigabyte groß und brauchen Storage-nahe Verarbeitung auf Bare Metal oder VM.

Die Plattform muss beide Fälle unterstützen, ohne zwei völlig unterschiedliche Welten zu bauen.

Zielablauf

Skizze / Textauszug
Partner liefert Datei
↓
Upload API oder SFTP Gateway
↓
Object Storage incoming
↓
File Processing Worker
↓
Validierung
↓
Object Storage processed oder rejected
↓
Event file.accepted oder file.rejected
↓
Integration Service

Beispiel: File-Metadaten-Event

json
{
  class="str">"eventId": class="str">"7f7f4e7e-82e1-4d7f-8f6d-9b8f2f35c101",
  class="str">"eventType": class="str">"file.accepted",
  class="str">"fileId": class="str">"partner-2026-07-06-001",
  class="str">"partnerId": class="str">"partner-at-42",
  class="str">"bucket": class="str">"partner-files-prod",
  class="str">"objectKey": class="str">"processed/2026/07/06/partner-2026-07-06-001.csv",
  class="str">"checksum": class="str">"sha256:4b8a...",
  class="str">"recordCount": 184320,
  class="str">"acceptedAt": class="str">"2026-07-06T01:30:00Z"
}

Fehlerfälle

  • Datei ist nicht lesbar.
  • Format entspricht nicht dem Vertrag.
  • Checksumme stimmt nicht.
  • Object Storage ist nicht erreichbar.
  • Worker hat keinen Speicherplatz.
  • Broker ist nicht erreichbar.
  • Datei wurde doppelt geliefert.

Betriebsregel

Die Originaldatei wird nicht überschrieben. Fehlerhafte Dateien werden mit Fehlerbericht in einen Rejected-Bereich verschoben. Dadurch kann der Partner später nachvollziehen, was passiert ist.

---

Szenario 8: GitOps Rollback nach fehlerhaftem Deployment

Ausgangssituation

Eine neue Version des Billing Service wird ausgerollt. Kurz danach steigen Fehler. Die API ist erreichbar, aber der Consumer verarbeitet Events falsch. Das Team muss schnell reagieren, ohne manuell im Cluster herumzuklicken.

GitOps-Prinzip

Der produktive Zustand wird im GitOps-Repository beschrieben. Argo CD synchronisiert diesen Zustand in den Cluster. Ein Rollback ist deshalb nicht „oc edit deployment“, sondern eine Änderung im Git-Repository.

Beispiel: Image-Rollback im Kustomize Overlay

yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
images:
  - name: registry.example.com/alpine/billing-service
    newTag: 1.4.2

Wenn Version 1.4.3 fehlerhaft war, wird der Tag zurück auf 1.4.2 gesetzt. Der Pull Request dokumentiert Grund und Incident-Referenz.

Rollback-Runbook

Vorlage / Textauszug

Runbook: GitOps Rollback Billing Service

Symptom

Nach Deployment einer neuen Version steigt die Fehlerquote oder der Consumer Lag.

Entscheidung

Rollback wird durchgeführt, wenn:

  • Fehler eindeutig mit neuer Version korreliert,
  • keine schnelle Konfigurationskorrektur ausreicht,
  • fachlicher Schaden durch Weiterbetrieb größer wäre,
  • Rollback-Version bekannt stabil ist.
Schritte
  1. Incident eröffnen.
  2. Fehlerbild und Metriken sichern.
  3. GitOps-Overlay auf letzte stabile Version setzen.
  4. Pull Request reviewen und mergen.
  5. Argo CD Sync prüfen.
  6. Pods und Metriken beobachten.
  7. Consumer Lag prüfen.
  8. Incident dokumentieren.

Lernpunkt

GitOps macht Rollback nachvollziehbar. Es ersetzt aber nicht die Entscheidungskompetenz. Ein Rollback muss fachlich begründet und nachkontrolliert werden.

---

Szenario 9: Zertifikat läuft ab und externe API ist nicht erreichbar

Ausgangssituation

Am Montagmorgen ist das Customer Portal nicht erreichbar. Browser melden ein Zertifikatsproblem. Monitoring zeigt, dass die Anwendung selbst gesund ist. Das Problem liegt vor der Anwendung: Das TLS-Zertifikat der externen Route ist abgelaufen.

Warum das kritisch ist

Ein abgelaufenes Zertifikat ist kein Codefehler. Trotzdem wirkt es für Kunden wie ein Totalausfall. Deshalb gehören Zertifikate in das Betriebsmodell.

Prüfung

bash
openssl s_client -connect customer.alpine.example.com:443 -servername customer.alpine.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
oc get route -n customer-prod customer-portal -o yaml
oc get certificate -A
oc describe certificate -n customer-prod customer-portal-tls

Prävention

  • Zertifikatsablauf überwachen.
  • Alert mindestens 30 Tage vor Ablauf.
  • Verantwortlichen Owner dokumentieren.
  • cert-manager oder automatisierte Erneuerung nutzen.
  • manuelle Zertifikate nur mit klarer Review-Frist verwenden.

---

Szenario 10: AI erzeugt scheinbar korrekten, aber falschen Kubernetes-Code

Ausgangssituation

Ein Entwickler bittet Codex oder Claude Code, ein Deployment mit Secret, Service und Route zu erstellen. Die Ausgabe sieht plausibel aus. Im Review fällt aber auf, dass das Secret direkt im YAML steht, Resource Limits fehlen und die Route öffentlich erreichbar wäre.

Warum das gefährlich ist

AI kann Form und Syntax gut nachbilden. Sie garantiert aber nicht, dass Enterprise-Regeln eingehalten werden. Deshalb braucht das Projekt feste AI-Review-Regeln.

Review-Check

Vorlage / Textauszug
  • Sind Secrets als ExternalSecret oder Secret-Referenz modelliert?
  • Sind Resource Requests und Limits vorhanden?
  • Gibt es Readiness und Liveness Probes?
  • Gibt es Labels für Owner und Environment?
  • Gibt es eine NetworkPolicy?
  • Ist die Route wirklich öffentlich notwendig?
  • Gibt es eine ADR, wenn vom Standard abgewichen wird?

Lernpunkt

AI ist ein Beschleuniger, kein Ersatz für Architektur- und Security-Verantwortung. Jede AI-Ausgabe braucht Review, Tests und Kontextprüfung.

---

Abschluss von Teil 34

Dieser Teil zeigt die Plattform nicht als Sammlung isolierter Technologien, sondern als zusammenhängendes System. Genau das ist der Unterschied zwischen Demo und Enterprise-Lernbuch.

Die wichtigsten Erkenntnisse:

Vorlage / Textauszug

End-to-End-Denken verbindet Fachlichkeit, Code, Infrastruktur und Betrieb.

Ein Fehlerbild hat selten nur eine Ebene.

Outbox, NetworkPolicy, RBAC, GitOps und Monitoring wirken erst gemeinsam richtig.

Restore, Rollback und Reprocessing sind Betriebsentscheidungen, keine reinen Technikbefehle.

AI kann helfen, muss aber durch Regeln, Reviews und Tests begrenzt werden.

Übung

Wähle eines der zehn Szenarien und schreibe daraus ein eigenes Runbook mit diesen Abschnitten:

Vorlage / Textauszug
  1. Symptom
  2. Betroffene Systeme
  3. Sofortprüfung
  4. technische Analyse
  5. fachliche Auswirkung
  6. Entscheidungskriterien
  7. Korrektur
  8. Nachkontrolle
  9. Dokumentation
  10. Prävention

Musterlösung: Kurz-Runbook für NetworkPolicy-Fehler

Vorlage / Textauszug

Runbook: Billing Service erreicht Kafka nicht

Symptom

Billing Consumer Lag steigt. Billing Service läuft, verarbeitet aber keine neuen Order Events.

Betroffene Systeme
  • billing-service
  • Kafka
  • billing-prod Namespace
  • messaging Namespace
  • NetworkPolicy im GitOps Repository
Sofortprüfung

oc get pods -n billing-prod

oc logs -n billing-prod deployment/billing-service --tail=100

oc get networkpolicy -n billing-prod

Vorlage / Textauszug
technische Analyse

Aus dem Billing Pod wird die Verbindung zu Kafka geprüft. Wenn DNS funktioniert, aber TCP-Verbindung blockiert ist, wird die NetworkPolicy geprüft.

fachliche Auswirkung

Neue Rechnungen werden verzögert erstellt. Bestellungen sind nicht verloren, aber Billing hinkt hinterher.

Entscheidung

Wenn Kafka gesund ist und nur Billing blockiert ist, wird die Egress-Policy korrigiert.

Korrektur

Änderung im GitOps Repository, Pull Request, Review und Argo CD Sync.

Nachkontrolle

Consumer Lag muss sinken. Neue Rechnungen müssen entstehen. Logs dürfen keine Broker-Timeouts mehr zeigen.

Prävention

Vor jeder neuen NetworkPolicy wird eine Kommunikationsmatrix geprüft.

Erweiterte Enterprise-Szenarien und Betriebsfälle

Ziel dieses Schritts

Dieser Teil ergänzt größere, zusammenhängende Betriebsfälle. Die bisherigen Teile erklären Architektur, Plattform, Code, Security und Delivery. Jetzt geht es um Situationen, die in einem echten Unternehmen nicht als isolierte Einzelaufgabe auftreten. Ein Incident betrifft oft Anwendung, Netzwerk, Identität, Daten, Observability, GitOps und Kommunikation gleichzeitig.

Der Zweck dieses Schritts ist deshalb nicht, noch mehr Begriffe zu sammeln. Der Zweck ist, fachliche Entscheidungen in realistische Abläufe zu übersetzen. Jeder Fall zeigt, was passiert, welche Signale sichtbar werden, welche Teams beteiligt sind, welche Artefakte geprüft werden und welche dauerhafte Verbesserung daraus entstehen sollte.

1. Betriebsfall: Order Service erzeugt sporadisch HTTP 500

Ein Kunde meldet, dass Bestellungen manchmal fehlschlagen. Im Customer Portal erscheint nur eine allgemeine Fehlermeldung. Für den Betrieb ist wichtig, den Fehler nicht sofort als Frontend-Problem einzuordnen. Das Portal zeigt nur das Symptom. Die Ursache kann im Backend, in der Datenbank, in Keycloak, im Netzwerk oder in einer fehlerhaften Validierung liegen.

Der erste Schritt ist die Korrelation über Trace-ID. Wenn das Portal jede Anfrage mit einer Correlation-ID sendet und der Order Service diese ID in Logs übernimmt, kann man den kompletten Ablauf verfolgen. Ohne diese ID muss man anhand von Uhrzeit, Benutzer und Endpoint suchen. Das ist ungenau und fehleranfällig.

bash
# Fehlerhafte Requests im Order Namespace suchen
oc logs -n order-prod deployment/order-service --since=30m \
  | grep class="str">'HTTP 500' \
  | head -20

# Danach gezielt nach Trace-ID suchen
oc logs -n order-prod deployment/order-service --since=30m \
  | grep class="str">'traceId=7f43a9c2'

Wenn die Logs zeigen, dass der Fehler bei bestimmten Produktkombinationen entsteht, ist die Ursache wahrscheinlich fachlich oder datenbezogen. Wenn der Fehler nur bei Lastspitzen auftritt, muss man Datenbankverbindungen, Threadpools, Timeouts und Resource Limits prüfen. Wenn der Fehler nur nach einem Deployment auftritt, liegt der Fokus auf Release-Diff, GitOps-Commit und Konfiguration.

Ein produktionsnaher Service sollte hier nicht nur Stacktraces ausgeben. Er sollte Fehlerklassen unterscheiden: Validierungsfehler, technische Fehler, externe Abhängigkeit nicht erreichbar, Timeout, Security-Fehler und unerwartete Fehler.

java
package com.seb4u.demo.spring.order.api;

import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.net.URI;

@RestControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    ProblemDetail handleValidation(IllegalArgumentException ex) {
        ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
        detail.setTitle(class="str">"Invalid order request");
        detail.setDetail(ex.getMessage());
        detail.setType(URI.create(class="str">"https://errors.alpine.example.com/order/invalid-request"));
        return detail;
    }

    @ExceptionHandler(ExternalDependencyException.class)
    ProblemDetail handleDependency(ExternalDependencyException ex) {
        ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.SERVICE_UNAVAILABLE);
        detail.setTitle(class="str">"Required dependency is unavailable");
        detail.setDetail(class="str">"The order could not be processed because a required backend dependency is unavailable.");
        detail.setType(URI.create(class="str">"https://errors.alpine.example.com/order/dependency-unavailable"));
        return detail;
    }
}

Die Verbesserung nach dem Incident besteht nicht nur aus einem Bugfix. Gute Verbesserungen sind: bessere Fehlerklassifizierung, Dashboard für 5xx-Rate, Alert auf ungewöhnliche Fehlerquote, Tests für den betroffenen Fall und ein kurzer Eintrag im Runbook.

2. Betriebsfall: Billing verarbeitet Events doppelt

In eventgetriebenen Systemen muss man davon ausgehen, dass eine Nachricht mehrfach ankommt. Broker garantieren je nach Konfiguration nicht automatisch exakt einmalige fachliche Verarbeitung. Deshalb muss der Billing Service idempotent sein.

Das Symptom kann sein, dass für eine Bestellung zwei Rechnungsentwürfe entstehen. Der Fehler ist kritisch, weil er direkt fachliche Daten betrifft. Hier darf der Betrieb nicht einfach Nachrichten löschen, ohne die fachliche Wirkung zu prüfen.

Der Billing Consumer muss vor der Verarbeitung prüfen, ob das Event bereits verarbeitet wurde. Dafür eignet sich eine Tabelle processed_events mit einer eindeutigen Event-ID.

sql
CREATE TABLE processed_events (
    event_id VARCHAR(120) PRIMARY KEY,
    event_type VARCHAR(160) NOT NULL,
    processed_at TIMESTAMP WITH TIME ZONE NOT NULL,
    aggregate_id VARCHAR(120) NOT NULL
);

CREATE TABLE invoices (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL,
    customer_id VARCHAR(80) NOT NULL,
    amount NUMERIC(12, 2) NOT NULL,
    status VARCHAR(40) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE NOT NULL,
    UNIQUE(order_id)
);
java
package com.seb4u.demo.spring.billing.messaging;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BillingEventHandler {

    private final ProcessedEventRepository processedEvents;
    private final InvoiceRepository invoices;

    public BillingEventHandler(ProcessedEventRepository processedEvents, InvoiceRepository invoices) {
        this.processedEvents = processedEvents;
        this.invoices = invoices;
    }

    @Transactional
    public void handle(OrderCreatedMessage message) {
        if (processedEvents.existsById(message.eventId())) {
            return;
        }

        invoices.createDraftInvoice(
                message.orderId(),
                message.customerId(),
                message.totalAmount()
        );

        processedEvents.store(message.eventId(), message.eventType(), message.orderId());
    }
}

Nach dem Incident wird geprüft, ob Duplikate bereits entstanden sind. Wenn ja, braucht es eine fachliche Korrektur. Ein Restore der ganzen Datenbank wäre meist falsch, weil dadurch andere korrekte Rechnungen verloren gehen könnten. Besser ist eine gezielte Korrektur mit Audit-Eintrag.

3. Betriebsfall: GitOps überschreibt manuelle Hotfixes

Ein Team behebt nachts ein Problem direkt im Cluster. Am nächsten Morgen setzt Argo CD den Zustand wieder auf den Git-Stand zurück. Aus Sicht von GitOps ist das korrekt. Aus Sicht des Teams wirkt es wie ein Fehler. In Wirklichkeit zeigt der Fall einen Prozessbruch.

GitOps bedeutet: Git ist die gewünschte Wahrheit. Manuelle Änderungen im Cluster sind nur für Diagnose oder extreme Notfälle erlaubt. Dauerhafte Änderungen müssen ins GitOps-Repository.

bash
# Manuelle Änderung sichtbar machen
oc diff -n order-prod -f platform/overlays/prod/order-service

# Argo CD Status prüfen
argocd app get order-service-prod

# Synchronisationshistorie anzeigen
argocd app history order-service-prod

Ein guter Notfallprozess erlaubt einen temporären Hotfix, verlangt aber danach eine Nachdokumentation. Der Hotfix muss als Pull Request ins GitOps-Repository. Zusätzlich muss im Incident-Log stehen, warum der direkte Eingriff notwendig war.

Vorlage / Textauszug

Emergency Change Record

System

order-service-prod

Grund

Produktionsstörung durch falsch gesetzten Timeout-Wert nach Release 1.18.0.

Temporäre Maßnahme

Timeout im Deployment manuell von 2s auf 8s erhöht.

Dauerhafte Maßnahme

GitOps Pull Request #482 erstellt, um Timeout im Produktions-Overlay anzupassen.

Rückbau

Manuelle Änderung wird nach Argo-CD-Sync nicht mehr benötigt.

Die dauerhafte Verbesserung ist ein klarer Emergency-Change-Prozess. GitOps darf nicht durch heimliche manuelle Änderungen unterlaufen werden.

4. Betriebsfall: Zertifikat erneuert, aber Route nutzt noch altes Secret

Ein Zertifikat wurde erneuert, aber externe Clients melden weiterhin, dass das Zertifikat bald abläuft. Die Ursache kann sein, dass das neue Zertifikat zwar im Secret liegt, aber die Route oder der Router noch das alte Secret verwendet. Möglich ist auch, dass ein externer Load Balancer TLS terminiert und dort ein anderes Zertifikat installiert ist.

Die Diagnose muss die TLS-Kette von außen und im Cluster prüfen.

bash
# Extern sichtbares Zertifikat prüfen
echo | openssl s_client -servername api.alpine.example.com \
  -connect api.alpine.example.com:443 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

# Route prüfen
oc get route -n order-prod order-api -o yaml

# Secret prüfen
oc get secret -n order-prod order-api-tls -o jsonpath=class="str">'{.data.tls\.crt}' \
  | base64 -d \
  | openssl x509 -noout -subject -issuer -dates

Ein guter Plattformbetrieb überwacht nicht nur Pod-Status. Er überwacht auch Zertifikatsablauf. Zertifikate sind ein klassisches Beispiel für ein Problem, das technisch klein wirkt, aber schnell produktionskritisch wird.

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: certificate-expiry-rules
  namespace: platform-monitoring
spec:
  groups:
    - name: tls-expiry
      rules:
        - alert: CertificateExpiresSoon
          expr: probe_ssl_earliest_cert_expiry - time() < 14 * 24 * 3600
          for: 30m
          labels:
            severity: warning
          annotations:
            summary: class="str">"TLS certificate expires in less than 14 days"
            description: class="str">"Check certificate renewal path, route secret and external load balancer."

5. Betriebsfall: VM-Reporting erzeugt falsche Zahlen nach Schemaänderung

Der Reporting Service läuft noch auf einer VM und liest aus einer Datenbankkopie. Nach einer Schemaänderung im Order Service stimmen bestimmte Reports nicht mehr. Das ist ein typischer Hybrid-Fehler: Ein modernes System ändert seine Datenstruktur, aber ein älteres VM-System hängt noch indirekt daran.

Die technische Frage lautet nicht nur: Warum ist der Report falsch? Die wichtigere Architekturfrage lautet: Warum durfte Reporting direkt von einer Struktur abhängen, die einem anderen Service gehört?

In einer sauberen Architektur sollte Reporting nicht direkt produktive Tabellen eines anderen Services interpretieren. Besser sind stabile Exportmodelle, Events, Read Models oder kontrollierte Reporting-Schemas.

sql
CREATE VIEW reporting_order_summary AS
SELECT
    o.id AS order_id,
    o.customer_id,
    o.status,
    o.total_amount,
    o.created_at,
    count(oi.id) AS item_count
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, o.customer_id, o.status, o.total_amount, o.created_at;

Diese View ist kein perfektes Zielbild, aber eine kontrolliertere Übergangslösung. Langfristig sollte Reporting ein eigenes Modell bekommen, das über Events oder definierte Exporte gefüllt wird.

6. Betriebsfall: NetworkPolicy blockiert OpenTelemetry Collector

Nach einer Security-Härtung fehlen Traces. Die Anwendung läuft, Logs sind da, Metriken sind da, aber verteilte Traces kommen nicht mehr im Backend an. Ursache ist oft eine NetworkPolicy, die Egress nur zu Datenbank und Broker erlaubt, aber den OpenTelemetry Collector vergessen hat.

Das zeigt: Security und Observability dürfen nicht gegeneinander arbeiten. Eine gute Policy erlaubt notwendige Betriebsverbindungen bewusst und eng begrenzt.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-order-observability-egress
  namespace: order-prod
spec:
  podSelector:
    matchLabels:
      app: order-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: observability
          podSelector:
            matchLabels:
              app: otel-collector
      ports:
        - protocol: TCP
          port: 4317
        - protocol: TCP
          port: 4318

Nach der Änderung wird nicht nur geprüft, ob der Pod läuft. Es wird geprüft, ob Traces mit korrekter Service-Name-Konvention im Backend sichtbar sind.

7. Betriebsfall: Secret Rotation bricht Billing

Ein Datenbankpasswort wird rotiert. Der neue Secret-Wert wird im Secret Store aktualisiert. Trotzdem schlagen Billing-Verbindungen fehl. Mögliche Ursachen sind: Pod hat Secret noch nicht neu geladen, Connection Pool nutzt alte Verbindungen, ExternalSecret hat nicht synchronisiert, oder Datenbankrolle wurde falsch geändert.

Die Diagnose braucht mehrere Ebenen.

bash
# ExternalSecret Status prüfen
oc get externalsecret -n billing-prod billing-db -o yaml

# Secret-Zeitstempel prüfen
oc get secret -n billing-prod billing-db-secret -o yaml

# Deployment neu starten, falls Anwendung Secrets nur beim Start liest
oc rollout restart deployment/billing-service -n billing-prod

# Rollout überwachen
oc rollout status deployment/billing-service -n billing-prod

Dauerhaft sollte jede Secret Rotation einen Ablauf haben: Änderung im Secret-System, Synchronisation prüfen, Anwendung neu laden oder neu starten, Verbindungstest, Monitoring prüfen und Abschluss dokumentieren.

8. Betriebsfall: Cloud Object Storage Kosten steigen unerwartet

Cloud-Kosten sind ein Betriebsrisiko. Ein File Processing Service schreibt fehlerhafte Dateien in Object Storage, löscht sie aber nie. Nach einigen Wochen steigen Speicherkosten und API-Aufrufkosten. Technisch funktioniert alles, wirtschaftlich ist es fehlerhaft.

Eine Enterprise-Plattform braucht daher Kostenregeln. Object Storage Buckets sollten Tags, Lifecycle-Regeln und Monitoring haben.

hcl
resource class="str">"aws_s3_bucket_lifecycle_configuration" class="str">"partner_files" {
  bucket = aws_s3_bucket.partner_files.id

  rule {
    id     = class="str">"expire-rejected-files"
    status = class="str">"Enabled"

    filter {
      prefix = class="str">"rejected/"
    }

    expiration {
      days = 30
    }
  }

  rule {
    id     = class="str">"archive-processed-files"
    status = class="str">"Enabled"

    filter {
      prefix = class="str">"processed/"
    }

    transition {
      days          = 14
      storage_class = class="str">"STANDARD_IA"
    }
  }
}

Dieser Fall zeigt, dass Cloud-Architektur nicht nur Verfügbarkeit und Skalierung meint. Kostenkontrolle ist ein Architekturmerkmal.

9. Betriebsfall: Argo CD synchronisiert falsche Umgebung

Ein Entwickler verwechselt test und prod Overlay. Die Änderung landet im falschen Pfad. Argo CD macht genau das, was im Repository steht. Der Fehler liegt im Prozess und in fehlenden Schutzmechanismen.

Eine gute Repository-Struktur macht Verwechslungen schwerer. Zusätzlich helfen CODEOWNERS, Branch Protection und Pflichtreviews für Produktionspfade.

Skizze / Textauszug
platform/
├── apps/
│   └── order-service/
│       ├── base/
│       └── overlays/
│           ├── dev/
│           ├── test/
│           └── prod/
└── CODEOWNERS
Vorlage / Textauszug

CODEOWNERS

/platform/apps/*/overlays/prod/ @platform-team @security-team

/platform/argocd/ @platform-team

/security/ @security-team

Der dauerhafte Fix ist nicht nur ein Revert. Der dauerhafte Fix ist eine Schutzregel gegen Wiederholung.

10. Betriebsfall: AI-Vorschlag öffnet zu breite Rechte

Ein AI-Werkzeug schlägt für einen Fehler vor, dem ServiceAccount cluster-admin zu geben. Das Problem wäre damit vielleicht kurzfristig gelöst, aber die Plattform wäre unsicher. Dieser Fall ist wichtig, weil AI-Ausgaben oft plausibel wirken.

Die richtige Reaktion ist ein Minimalrechte-Review. Man prüft, welche Verben und Ressourcen wirklich benötigt werden.

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: order-reader
  namespace: order-prod
rules:
  - apiGroups: [class="str">""]
    resources: [class="str">"configmaps"]
    verbs: [class="str">"get", class="str">"list"]
  - apiGroups: [class="str">""]
    resources: [class="str">"pods"]
    verbs: [class="str">"get", class="str">"list"]

Diese Role ist bewusst eng. Wenn ein Service mehr Rechte braucht, muss der Grund dokumentiert werden. AI darf helfen, aber nicht die Security-Grenzen entscheiden.

11. Betriebsfall: Datenbankmigration dauert länger als das Releasefenster

Eine Migration fügt eine Spalte hinzu und aktualisiert Millionen Zeilen. In Test lief sie schnell, in Produktion blockiert sie länger als erwartet. Das Releasefenster wird überschritten.

Datenbankmigrationen müssen produktionsnah geplant werden. Große Änderungen sollten expand-and-contract nutzen.

sql
-- Phase 1: kompatible Erweiterung
ALTER TABLE orders ADD COLUMN source_system VARCHAR(80);

-- Phase 2: Anwendung schreibt neues Feld optional
-- Phase 3: Backfill in kleinen Batches
UPDATE orders
SET source_system = class="str">'customer-portal'
WHERE source_system IS NULL
  AND id IN (
      SELECT id FROM orders WHERE source_system IS NULL LIMIT 1000
  );

-- Phase 4: Constraint erst nach Backfill
ALTER TABLE orders ALTER COLUMN source_system SET NOT NULL;

Der wichtige Lernpunkt: Eine Migration ist Teil des Releases. Sie braucht Tests, Laufzeitabschätzung, Rollback-Strategie und Monitoring.

12. Betriebsfall: Backup ist vorhanden, Restore aber nicht getestet

Ein Team sagt: Backups laufen täglich. Beim echten Restore merkt man, dass Credentials fehlen, WAL-Dateien nicht vollständig sind oder die Dokumentation veraltet ist. Ein Backup ohne Restore-Test ist nur eine Hoffnung.

Ein Restore-Test braucht eine isolierte Umgebung, klare Schritte und fachliche Prüfung.

bash
# Beispielhafter Restore-Test für PostgreSQL-Lab
createdb orders_restore_test
pg_restore \
  --dbname=orders_restore_test \
  --clean \
  --if-exists \
  /backup/orders/latest.dump

psql orders_restore_test -c class="str">"select count(*) from orders;"
psql orders_restore_test -c class="str">"select max(created_at) from orders;"

Nach dem Restore muss ein fachlicher Smoke Test laufen. Es reicht nicht, dass Tabellen existieren. Man muss prüfen, ob kritische Abfragen, Constraints und Referenzen stimmen.

13. Zusammenfassung der erweiterten Betriebsfälle

Diese Szenarien zeigen, dass Enterprise-Qualität nicht durch ein einzelnes Framework entsteht. Sie entsteht durch wiederholbare Abläufe, klare Verantwortung, konkrete Artefakte, überprüfbare Sicherheitsgrenzen und eine Kultur, die aus Incidents lernt.

Ein guter Plattformstandard fragt immer:

Vorlage / Textauszug

Was ist passiert?

Welche Signale waren sichtbar?

Welche Grenze hat versagt?

Welche Entscheidung war unklar?

Welche Dokumentation fehlte?

Welche Automatisierung hätte geholfen?

Welche Prüfung verhindert Wiederholung?

Damit werden Betriebsfälle zu Lernmaterial und nicht nur zu Störungen.

Teil I: Übungen, Runbooks, Musterlösungen, kompakte SVGs und Fachglossar

Teil I: Übungen, Runbooks, Musterlösungen, kompakte SVGs und Fachglossar

PlanZielbildBuildUmsetzungRunBetriebTeil I: Übungen, Runbooks, Musterlösungen, kompakte SVGs und Fachglossar: Architektur, Umsetzung und Betrieb im Zusammenhang
Konsolidiert aus Rohmaterial: Teil 14, Teil 15, Teil 32, Teil 33.

Übungen, Runbooks und Musterlösungen

1. Warum dieser Teil wichtig ist

Ein Enterprise-Lernprojekt ist erst dann wirklich nützlich, wenn man damit üben kann. Reine Erklärung reicht nicht aus. Wer eine hybride Plattform verstehen will, muss Entscheidungen treffen, Fehler analysieren, Manifeste lesen, Runbooks ausführen und technische Nachweise prüfen können.

Dieser Teil sammelt deshalb keine losen Anhänge. Die Übungen sind nach Fachbereichen geordnet und greifen direkt auf die vorherigen Teile zurück. Jede Aufgabe ist so formuliert, dass sie eine reale Situation aus einem Enterprise-Projekt simuliert. Die Musterlösungen sind nicht nur kurze Antworten, sondern zeigen, wie man eine Lösung begründet.

Wichtig ist: Eine gute Übung prüft nicht nur Wissen, sondern Vorgehen. Im Betrieb zählt nicht, ob man einen Begriff auswendig kennt. Entscheidend ist, ob man aus Symptomen eine Ursache ableiten, die richtige Ebene prüfen und eine nachvollziehbare Entscheidung treffen kann.

---

2. Übungsformat

Jede Übung folgt demselben Aufbau:

Vorlage / Textauszug

Ausgangslage

Aufgabe

Erwartetes Ergebnis

Hinweise

Musterlösung

Bewertungskriterien

Dadurch entsteht ein wiederholbares Trainingsformat. Man kann die Übungen allein bearbeiten, im Team diskutieren oder als Review-Aufgabe für Codex beziehungsweise Claude Code verwenden.

---

3. Übung: Workload richtig einordnen

Ausgangslage

Die Alpine Retail & Services Group plant drei neue Komponenten:

Vorlage / Textauszug
  1. Price Calculation Service
  2. Monthly Archive Export
  3. Partner File Import Worker

Der Price Calculation Service wird häufig von Customer Portal und Admin Portal aufgerufen. Er muss schnell reagieren und soll regelmäßig deployt werden.

Der Monthly Archive Export läuft einmal pro Monat, liest sehr viele Daten und erzeugt große Archivdateien.

Der Partner File Import Worker verarbeitet täglich große Dateien von externen Partnern. Die Dateien liegen in Object Storage, aber einige Validierungsschritte benötigen lokale temporäre Verarbeitung.

Aufgabe

Ordne jede Komponente einer primären Laufzeit zu:

Vorlage / Textauszug

OpenShift

VM

Bare Metal

Cloud Service

Begründe deine Entscheidung fachlich und betrieblich.

Musterlösung

Der Price Calculation Service passt gut zu OpenShift. Er ist ein moderner API-Service, wird häufig aufgerufen, braucht Skalierung, Health Checks, Monitoring und regelmäßige Releases. OpenShift bietet genau dafür ein gutes Betriebsmodell.

Der Monthly Archive Export kann als Kubernetes CronJob laufen, wenn die Laufzeit begrenzt ist und der Zugriff auf Daten kontrolliert erfolgt. Wenn die Datenmenge sehr groß ist oder spezielle alte Exportwerkzeuge benötigt werden, kann auch eine VM sinnvoll sein. Die Entscheidung hängt von Speicherbedarf, Laufzeit, Treibern und Wiederholbarkeit ab.

Der Partner File Import Worker ist ein Grenzfall. Wenn die Dateien über Object Storage verarbeitet werden und die temporären Daten begrenzt bleiben, kann er als containerisierter Worker laufen. Wenn aber sehr große temporäre Dateien, spezielle Dateisysteme oder lokale Performance entscheidend sind, ist VM oder Bare Metal sinnvoll. Wichtig ist, dass die Entscheidung dokumentiert wird.

Bewertungskriterien

Eine gute Antwort nennt nicht nur eine Plattform, sondern erklärt:

Vorlage / Textauszug
  • technische Anforderungen
  • Betriebsanforderungen
  • Security-Auswirkungen
  • Monitoring und Fehlerbehandlung
  • spätere Migrationsmöglichkeit

---

4. Übung: Firewall-Freigabe bewerten

Ausgangslage

Ein Team beantragt folgende Firewall-Regel:

Vorlage / Textauszug

Quelle: OpenShift Application Zone

Ziel: Data Zone

Port: any

Protokoll: TCP

Zweck: Zugriff auf Datenbanken und Broker

Aufgabe

Bewerte diesen Antrag. Formuliere eine bessere Variante.

Musterlösung

Der Antrag ist zu breit. Er erlaubt allen Workloads aus der Application Zone Zugriff auf alle TCP-Ports in der Data Zone. Dadurch entstehen unnötige Risiken. Ein kompromittierter Pod könnte viele Systeme erreichen.

Eine bessere Regel beschreibt konkrete Quelle, konkretes Ziel und konkreten Port:

Vorlage / Textauszug

Quelle: Namespace order-prod, App order-service

Ziel: PostgreSQL orders-db.internal.alpine.example.com

Port: 5432/TCP

Zweck: Order Service speichert und liest Bestellungen

Review: halbjährlich

Für Kafka wäre eine eigene Regel sinnvoll:

Vorlage / Textauszug

Quelle: Namespace order-prod, App order-service

Ziel: kafka.internal.alpine.example.com

Port: 9092/TCP

Zweck: Order Service veröffentlicht OrderCreated Events

Review: halbjährlich

Bewertungskriterien

Eine gute Antwort erkennt das Least-Privilege-Prinzip, trennt Datenbank- und Broker-Zugriffe und verlangt nachvollziehbare Verantwortlichkeit.

---

5. Übung: OpenShift Manifest prüfen

Ausgangslage

Ein Team liefert folgendes Deployment:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: billing-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: billing-service
  template:
    metadata:
      labels:
        app: billing-service
    spec:
      containers:
        - name: billing-service
          image: registry.example.com/billing-service:latest
          ports:
            - containerPort: 8080

Aufgabe

Finde die wichtigsten Probleme und formuliere eine verbesserte Version.

Musterlösung

Das Manifest ist für ein Enterprise-Lab zu schwach. Es nutzt das Tag latest, definiert keine Resource Requests und Limits, keine Readiness Probe, keine Liveness Probe, keine Umgebungsvariablen, keine Security-Einstellungen und keine klare Versionierung.

Eine bessere Version:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: billing-service
  labels:
    app: billing-service
    owner: team-billing
spec:
  replicas: 2
  selector:
    matchLabels:
      app: billing-service
  template:
    metadata:
      labels:
        app: billing-service
    spec:
      serviceAccountName: billing-service
      containers:
        - name: billing-service
          image: registry.example.com/alpine/billing-service:1.4.2
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: openshift
            - name: KAFKA_BOOTSTRAP_SERVERS
              valueFrom:
                secretKeyRef:
                  name: billing-kafka-secret
                  key: bootstrapServers
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 40
            periodSeconds: 20
          resources:
            requests:
              cpu: class="str">"250m"
              memory: class="str">"512Mi"
            limits:
              cpu: class="str">"1"
              memory: class="str">"1Gi"
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            capabilities:
              drop:
                - ALL

Diese Version ist noch nicht vollständig, aber deutlich näher an einem produktionsnahen Manifest.

---

6. Übung: Outbox Backlog analysieren

Ausgangslage

Der Order Service läuft, aber das Monitoring zeigt:

Vorlage / Textauszug

outbox_unpublished_events = 18420

oldest_unpublished_event_age_minutes = 360

Kundenbestellungen werden gespeichert, aber Rechnungen werden nicht erzeugt.

Aufgabe

Beschreibe ein Vorgehen zur Analyse.

Musterlösung

Zuerst wird geprüft, ob der Order Service selbst Fehler meldet:

bash
oc logs -n order-prod deployment/order-service --tail=200

Danach wird geprüft, ob der Outbox Publisher läuft und ob er Kafka erreicht:

bash
oc get pods -n order-prod -l app=order-service
oc describe pod -n order-prod -l app=order-service

Dann wird die Datenbank geprüft:

sql
SELECT event_type, COUNT(*)
FROM outbox_events
WHERE published_at IS NULL
GROUP BY event_type;

SELECT id, event_type, created_at, publish_attempts, last_error
FROM outbox_events
WHERE published_at IS NULL
ORDER BY created_at
LIMIT 20;

Wenn last_error auf Kafka-Verbindungsprobleme zeigt, werden Broker, Secret und NetworkPolicy geprüft. Wenn die Fehler auf Payload-Probleme zeigen, muss der Publisher gegen ungültige Events robuster gemacht werden. Wenn publish_attempts sehr hoch ist, darf man Events nicht blind löschen. Zuerst muss geklärt werden, ob die Events fachlich noch benötigt werden.

Ein möglicher Sofortschritt ist, den Publisher nach Behebung der Ursache kontrolliert weiterlaufen zu lassen. Falls das Backlog sehr groß ist, kann eine temporäre Skalierung oder ein Batch-Publisher nötig sein. Dabei müssen Broker-Last und Billing-Service-Kapazität beachtet werden.

---

7. Übung: Keycloak-Rollenmodell prüfen

Ausgangslage

Ein Admin Portal verwendet folgende Rollen:

Vorlage / Textauszug

admin

user

superuser

Aufgabe

Bewerte dieses Modell und schlage eine bessere Variante vor.

Musterlösung

Das Modell ist zu grob. superuser ist besonders problematisch, weil nicht klar ist, welche Rechte damit verbunden sind. In Enterprise-Systemen sollten Rollen fachliche Tätigkeiten beschreiben.

Eine bessere Struktur wäre:

Vorlage / Textauszug

order.viewer

order.corrector

billing.viewer

billing.approver

customer.support

audit.viewer

platform.operator

Diese Rollen lassen sich besser prüfen. Ein Mitarbeiter im Support braucht vielleicht order.viewer und customer.support, aber nicht billing.approver. Ein Auditor braucht audit.viewer, aber keine Schreibrechte.

Im Admin Portal sollten Rollen außerdem nicht nur im Frontend verborgen werden. Die Backend-API muss Berechtigungen serverseitig prüfen.

Beispiel in Spring Security:

java
@PreAuthorize(class="str">"hasAuthority('billing.approver')")
@PostMapping(class="str">"/invoices/{invoiceId}/approve")
public InvoiceApprovalResponse approve(@PathVariable String invoiceId) {
    return invoiceApprovalUseCase.approve(invoiceId);
}

---

8. Übung: Runbook für fehlgeschlagenes Deployment

Ausgangslage

Argo CD zeigt für order-service-prod den Status Degraded. Das letzte Deployment wurde vor 20 Minuten durchgeführt. Die neue Version ist 1.8.0.

Aufgabe

Schreibe ein Runbook für die erste Analyse.

Musterlösung

Vorlage / Textauszug

Runbook: Order Service Deployment ist Degraded

Symptom

Argo CD zeigt `Degraded` für `order-service-prod`.

Erste Prüfung

argocd app get order-service-prod

oc get pods -n order-prod -l app=order-service

oc describe deployment -n order-prod order-service

Vorlage / Textauszug
Pod-Status prüfen

oc get pods -n order-prod -l app=order-service -o wide

oc describe pod -n order-prod -l app=order-service

oc logs -n order-prod deployment/order-service --tail=200

Vorlage / Textauszug
Häufige Ursachen
  • Image nicht vorhanden
  • ImagePullSecret fehlt
  • Readiness Probe schlägt fehl
  • Datenbank nicht erreichbar
  • falsche ConfigMap oder Secret-Version
  • Resource Limits zu niedrig
  • Migration fehlgeschlagen
Entscheidung

Wenn die neue Version nicht ready wird und Kunden betroffen sind, wird Rollback auf die letzte stabile Version ausgelöst.

Rollback

Im GitOps-Repository wird das Image-Tag zurückgesetzt, zum Beispiel von `1.8.0` auf `1.7.4`. Danach synchronisiert Argo CD die Änderung.

Nacharbeit
  • Ursache dokumentieren
  • Release Notes prüfen
  • fehlende Tests ergänzen
  • ADR nur bei Architekturentscheidung aktualisieren

Ein gutes Runbook vermeidet hektische Einzelaktionen. Es führt von Symptom zu Analyse, Entscheidung und Nacharbeit.

---

9. Übung: Backup-Restore-Test planen

Ausgangslage

Die Orders-Datenbank wird täglich gesichert. Es gibt aber seit drei Monaten keinen Restore-Test.

Aufgabe

Plane einen Restore-Test für die Testumgebung.

Musterlösung

Ein Restore-Test darf nicht direkt in Produktion stattfinden. Zuerst wird eine isolierte Testdatenbank bereitgestellt. Danach wird das letzte Backup wiederhergestellt. Anschließend werden technische und fachliche Prüfungen durchgeführt.

Prüfschritte:

Vorlage / Textauszug
  1. Backup-Artefakt identifizieren
  2. Prüfsumme kontrollieren
  3. Testdatenbank bereitstellen
  4. Restore durchführen
  5. Schema-Version prüfen
  6. Anzahl wichtiger Tabellen prüfen
  7. Stichproben fachlicher Daten prüfen
  8. Anwendung gegen Restore-Datenbank starten
  9. kritische Use Cases testen
  10. Ergebnis dokumentieren

Beispiel für technische Prüfungen:

sql
SELECT COUNT(*) FROM orders;
SELECT COUNT(*) FROM order_items;
SELECT COUNT(*) FROM outbox_events;
SELECT MAX(created_at) FROM orders;

Das Ergebnis wird im Runbook dokumentiert. Wenn der Restore scheitert, ist das ein kritischer Befund. Ein Backup ohne erfolgreichen Restore-Test ist kein verlässliches Backup.

---

10. Übung: AI-Ausgabe prüfen

Ausgangslage

Ein AI-Werkzeug schlägt folgenden Text für Security vor:

Vorlage / Textauszug

Security ist wichtig. Alle Systeme müssen geschützt werden. RBAC, Audit und Hardening sollten umgesetzt werden. Außerdem müssen README, ADR und Runbook gepflegt werden.

Aufgabe

Bewerte den Text und verbessere ihn fachlich.

Musterlösung

Der Text ist zu allgemein. Er nennt Begriffe, erklärt aber keine konkrete Umsetzung. Eine bessere Fassung muss sagen, welche Komponente betroffen ist, welche Regel gilt und wie der Nachweis aussieht.

Besser:

Vorlage / Textauszug

Für den Billing Service werden fachliche Rollen in Keycloak definiert. Die Rolle `billing.approver` erlaubt das Freigeben von Rechnungen. Die API prüft diese Rolle serverseitig mit Spring Security. Kubernetes RBAC wird davon getrennt betrachtet und nur für technische Deployment- und Betriebsrechte verwendet.

Alle Freigabeaktionen schreiben Audit Events mit Benutzer-ID, Rechnungs-ID, Zeitpunkt, Entscheidung und Trace-ID. Diese Events werden zentral geloggt und mindestens 180 Tage aufbewahrt. Das Runbook beschreibt, wie ein Auditor die Freigabehistorie einer Rechnung nachvollzieht.

Diese Version ist besser, weil sie konkrete Rollen, technische Prüfung, Audit-Inhalt und Betriebsnachweis verbindet.

---

11. Runbook-Vorlage

Für das Lab verwenden wir eine einheitliche Runbook-Vorlage:

Vorlage / Textauszug

Runbook: <Name>

Zweck

Kurz beschreiben, welches Problem dieses Runbook behandelt.

Symptom

Woran erkennt man das Problem?

Betroffene Systeme
  • Anwendung
  • Namespace
  • Datenbank
  • Broker
  • externe Abhängigkeiten
Erste Prüfung
Praxisbefehle und Build-Kommandos

Praxisbefehle und Build-Kommandos

PlanZielbildBuildUmsetzungRunBetriebkonkrete Befehle: Architektur, Umsetzung und Betrieb im Zusammenhang
Vorlage / Textauszug
Entscheidungsbaum

Welche Beobachtung führt zu welcher nächsten Prüfung?

Sofortmaßnahmen

Welche Maßnahmen sind erlaubt, ohne Datenverlust zu riskieren?

Eskalation

Wann wird welches Team informiert?

Wiederherstellung

Wie wird der Normalzustand wiederhergestellt?

Nacharbeit

Welche Tests, Dokumentationen oder Alerts müssen angepasst werden?

Diese Vorlage verhindert, dass jedes Team Runbooks völlig anders schreibt.

---

12. ADR-Vorlage

Vorlage / Textauszug

ADR <Nummer>: <Titel>

Status

Vorgeschlagen | Akzeptiert | Abgelehnt | Ersetzt

Kontext

Welche Situation oder welches Problem führt zu dieser Entscheidung?

Entscheidung

Welche Lösung wird gewählt?

Alternativen

Welche anderen Möglichkeiten wurden betrachtet?

Konsequenzen

Welche Vorteile, Nachteile, Risiken und Betriebsfolgen entstehen?

Nachweise

Welche Artefakte zeigen die Umsetzung?

  • Code
  • Manifest
  • Pipeline
  • Runbook
  • Monitoring
  • Test

Ein ADR ist kein Roman. Er soll eine wichtige Entscheidung nachvollziehbar machen.

---

13. Review-Checkliste für Pull Requests

Vorlage / Textauszug

Fachlichkeit:

  • Ist der Zweck der Änderung klar?
  • Ist der betroffene Use Case beschrieben?
  • Gibt es fachliche Tests?

Code:

  • Ist die Paketstruktur sauber?
  • Sind Verantwortlichkeiten getrennt?
  • Gibt es keine versteckte Infrastruktur in der Domäne?

Security:

  • Werden Rollen serverseitig geprüft?
  • Werden Secrets nicht geloggt?
  • Gibt es keine breiten Berechtigungen?

Betrieb:

  • Gibt es Logs, Metriken und Health Checks?
  • Ist das Runbook betroffen?
  • Sind Alerts nötig?

Deployment:

  • Ist das Image versioniert?
  • Sind Ressourcen gesetzt?
  • Sind Probes sinnvoll?
  • Passt das GitOps-Manifest?

Dokumentation:

  • Muss eine README aktualisiert werden?
  • Ist ein ADR nötig?
  • Sind bekannte Grenzen dokumentiert?

Diese Checkliste ist bewusst praktisch. Sie soll im Alltag eines Enterprise-Projekts nutzbar sein.

---

14. Abschlussübung: Mini-End-to-End-Review

Aufgabe

Prüfe eine neue Funktion: Der Billing Service soll Rechnungen manuell freigeben können.

Beantworte:

Vorlage / Textauszug
  1. Welche API entsteht?
  2. Welche Rolle wird benötigt?
  3. Welche Audit-Daten werden geschrieben?
  4. Welche Tests sind nötig?
  5. Welche Metrik ist sinnvoll?
  6. Welche Alert-Regel könnte entstehen?
  7. Muss ein ADR geschrieben werden?
  8. Muss ein Runbook angepasst werden?

Musterlösung

Es entsteht eine API POST /invoices/{id}/approve. Die Rolle billing.approver wird benötigt. Die API prüft die Rolle serverseitig. Audit-Daten enthalten Benutzer-ID, Rechnungs-ID, Zeitpunkt, Entscheidung, vorherigen Status, neuen Status und Trace-ID.

Tests prüfen erfolgreiche Freigabe, fehlende Rolle, ungültigen Status, nicht vorhandene Rechnung und Audit-Erzeugung. Eine sinnvolle Metrik ist billing_invoice_approval_total mit Labels für Ergebnis und Grund. Ein Alert könnte entstehen, wenn ungewöhnlich viele Freigaben fehlschlagen.

Ein ADR ist nur nötig, wenn eine wichtige Architekturentscheidung getroffen wird, zum Beispiel ein neues Audit-Modell. Ein Runbook muss angepasst werden, wenn Betrieb oder Audit später Freigaben nachvollziehen müssen.

---

15. Merksätze

Vorlage / Textauszug

Übungen müssen reales Vorgehen trainieren, nicht nur Begriffe abfragen.

Ein Runbook ist nur gut, wenn es im Störfall wirklich hilft.

Eine Musterlösung soll Entscheidungen begründen, nicht nur Ergebnisse nennen.

Security muss in API, Rolle, Audit, Test und Betrieb sichtbar sein.

GitOps macht nur dann Sinn, wenn Änderungen überprüfbar und rückrollbar sind.

Ein Backup ist erst bewiesen, wenn Restore getestet wurde.

Integrierte Begriffserklärungen und Fachindex

1. Warum dieser Teil kein klassisches Glossar ist

Ein klassisches Glossar steht oft am Ende eines Dokuments. Der Leser muss dann während des Lernens ständig springen: erst Kapitel lesen, dann Begriff suchen, dann zurück zum Kapitel. Für ein großes Enterprise-Lernprojekt ist das unbequem. Deshalb wurden wichtige Begriffe bereits in den vorherigen Teilen direkt im fachlichen Zusammenhang erklärt.

Dieser Teil ist trotzdem wichtig. Er sammelt zentrale Begriffe, aber nicht als isolierte Wortliste. Jeder Begriff wird mit dem Projekt verbunden: Wo taucht er im Lab auf? Warum ist er wichtig? Welche typischen Fehler entstehen? Welche Artefakte zeigen, dass der Begriff nicht nur theoretisch verstanden, sondern praktisch umgesetzt wurde?

Damit wird dieser Teil zu einem Fachindex mit Lernfunktion. Er hilft beim Wiederholen, beim Nachschlagen und beim späteren Erstellen der HTML-Version.

Wichtig ist: Die Begriffserklärungen ersetzen nicht die Fachkapitel. Sie verweisen gedanklich zurück auf Architektur, Infrastruktur, Security, Betrieb, Code und Runbooks.

---

2. Architekturbegriffe

Enterprise-Architektur

Enterprise-Architektur beschreibt nicht nur technische Komponenten. Sie beschreibt, wie Anwendungen, Infrastruktur, Daten, Sicherheit, Betrieb, Organisation und Prozesse zusammenwirken. Im Lab bedeutet das: Der Order Service ist nicht nur ein Java-Service. Er ist Teil eines größeren Systems mit Customer Portal, Billing, Datenbank, Broker, Keycloak, OpenShift, Monitoring, Backup und GitOps.

Ein häufiger Fehler ist, Enterprise-Architektur mit einem großen Diagramm zu verwechseln. Ein Diagramm ist hilfreich, aber nur ein Einstieg. Architektur wird erst belastbar, wenn Entscheidungen dokumentiert, technische Artefakte vorhanden und Betriebsabläufe beschrieben sind.

Nachweise im Lab:

Vorlage / Textauszug
  • ADRs im Ordner docs/adr
  • Zielarchitekturdiagramme
  • Repository-Struktur
  • OpenShift-Manifeste
  • Runbooks
  • CI/CD-Definitionen

Architekturentscheidung

Eine Architekturentscheidung ist eine bewusste Festlegung mit Konsequenzen. Zum Beispiel: Der Order Service läuft auf OpenShift, die große PostgreSQL-Instanz bleibt zunächst auf Bare Metal, und Events werden über das Outbox Pattern veröffentlicht.

Eine gute Entscheidung nennt Kontext, Alternativen, gewählte Lösung und Folgen. Ohne diese Dokumentation wirkt Architektur später beliebig. Neue Teammitglieder wissen dann nicht, warum etwas so gebaut wurde.

Typischer Fehler:

Vorlage / Textauszug

class="str">"Wir machen das so, weil es immer so war."

Besser:

Vorlage / Textauszug

class="str">"Wir lassen die Datenbank zunächst auf Bare Metal, weil I/O-Last, Backup-Prozess und Lizenzmodell aktuell gegen eine sofortige Migration sprechen. Die Entscheidung wird nach dem ersten Lasttest erneut bewertet."

ADR

ADR steht für Architecture Decision Record. Ein ADR ist eine kleine Datei, meistens Markdown, in der eine Architekturentscheidung dokumentiert wird. Im Lab werden ADRs verwendet, um Entscheidungen nachvollziehbar zu machen.

Ein ADR ist kein Roman. Er soll kurz genug sein, damit er gelesen wird, aber konkret genug, damit die Entscheidung später überprüfbar bleibt.

Minimalstruktur:

Vorlage / Textauszug

ADR 004: Order Events über Outbox Pattern

Status

Akzeptiert

Kontext

Der Order Service speichert Bestellungen und muss Billing zuverlässig informieren.

Entscheidung

Events werden zuerst in derselben Datenbanktransaktion in einer Outbox-Tabelle gespeichert.

Konsequenzen

Ein separater Publisher veröffentlicht Events später an Kafka. Monitoring für Outbox Backlog ist verpflichtend.

Zielbild

Das Zielbild beschreibt, wie die Plattform später aussehen soll. Es ist keine vollständige Implementierung, sondern eine Orientierung. Im Lab umfasst das Zielbild moderne Anwendungen auf OpenShift, Datenbanken, Messaging, Identity Provider, Observability, Backup, GitOps und hybride Infrastruktur.

Ein gutes Zielbild hilft bei Entscheidungen. Wenn ein neuer Service entsteht, kann man fragen: Passt er in das Zielbild? Braucht er eine Ausnahme? Muss das Zielbild angepasst werden?

Hybrid-Architektur

Hybrid bedeutet im Lab nicht Chaos, sondern bewusstes Zusammenspiel verschiedener Infrastrukturformen. Cloud, VMs, Bare Metal und Kubernetes/OpenShift werden nach Anforderungen eingesetzt.

Beispiele:

Vorlage / Textauszug

OpenShift: moderne Services, Portale, Event Consumer

VM: ältere Tools, Reporting, Übergangssysteme

Bare Metal: große Datenbanken, Storage, Backup

Cloud: Object Storage, Testumgebungen, externe Integrationen

Der typische Fehler ist eine ideologische Entscheidung: alles in die Cloud oder alles in Kubernetes. Ein Enterprise-System braucht aber oft differenzierte Entscheidungen.

---

3. Infrastrukturbegriffe

Netzwerkzone

Eine Netzwerkzone trennt Systeme nach Schutzbedarf und Funktion. Im Lab gibt es DMZ, Application Zone, Data Zone, Management Zone und Backup Zone. Diese Trennung verhindert, dass ein Fehler oder Angriff sofort alle Systeme betrifft.

Eine gute Netzwerkzone hat klare Regeln:

Vorlage / Textauszug
  • Welche Systeme stehen dort?
  • Wer darf hinein?
  • Wer darf hinaus?
  • Welche Ports sind erlaubt?
  • Wer ist verantwortlich?

DMZ

Die DMZ ist die Zone zwischen Internet und internem Netzwerk. Dort stehen Komponenten, die von außen erreichbar sind oder externen Traffic annehmen. Typische Beispiele sind Load Balancer, Reverse Proxies, API Gateways oder Web Application Firewalls.

Im Lab soll keine Datenbank direkt aus dem Internet erreichbar sein. Externe Zugriffe gehen über kontrollierte Wege: Internet, DMZ, OpenShift Router oder API Gateway, Service, Datenbank.

VLAN

Ein VLAN trennt Netzwerkverkehr logisch. Es kann zum Beispiel ein Management-VLAN, ein Application-VLAN und ein Data-VLAN geben. VLANs helfen, Systeme technisch voneinander zu trennen.

Für Entwickler ist wichtig: Wenn ein Service eine Datenbank nicht erreicht, kann der Grund außerhalb des Codes liegen. VLAN, Routing, Firewall, DNS oder NetworkPolicy können die Ursache sein.

Firewall-Regel

Eine Firewall-Regel erlaubt oder blockiert Netzwerkverkehr. Im Lab muss jede wichtige Regel fachlich begründet sein. Eine gute Regel beschreibt Quelle, Ziel, Port, Protokoll, Zweck und Verantwortliche.

Schlechte Regel:

Vorlage / Textauszug

Allow Application Network to Data Network any port

Gute Regel:

Vorlage / Textauszug

Quelle: order-prod Namespace

Ziel: orders-db.internal.alpine.example.com

Port: 5432/TCP

Zweck: Order Service speichert Bestellungen

Owner: Team Order

Review: halbjährlich

DNS

DNS übersetzt Namen in IP-Adressen. In Enterprise-Systemen ist DNS kritisch, weil Zertifikate, OIDC Redirects, Routes, APIs und interne Dienste davon abhängen.

Typischer Fehler: In Test wird ein anderer Name verwendet als in Produktion, aber der OIDC Client ist nur für einen Redirect konfiguriert. Dann schlägt Login fehl, obwohl Keycloak und Anwendung laufen.

Load Balancer

Ein Load Balancer verteilt Anfragen auf mehrere Ziele. Im Lab gibt es oft mehrere Ebenen: externer Load Balancer, OpenShift Router, Kubernetes Service und Pods.

Wenn ein Endpoint nicht erreichbar ist, muss man systematisch prüfen:

bash
nslookup api.alpine.example.com
curl -vk https://api.alpine.example.com/orders
oc get route -n order-prod
oc get svc -n order-prod
oc get endpoints -n order-prod
oc get pods -n order-prod

Bare Metal

Bare Metal bezeichnet physische Server ohne Virtualisierungsschicht für den eigentlichen Workload. Im Lab eignet sich Bare Metal für große Datenbanken, Storage-nahe Verarbeitung und Backup-Repositories.

Bare Metal ist nicht automatisch moderner oder besser. Es ist nur direkter an der Hardware. Dadurch kann es leistungsfähig sein, aber auch mehr Betriebsverantwortung verursachen.

Virtuelle Maschine

Eine VM ist ein virtualisierter Server. VMs sind im Enterprise-Betrieb weiterhin wichtig. Sie eignen sich für Legacy-Anwendungen, interne Tools, Reporting, Migrationsjobs oder Anwendungen mit Betriebssystemabhängigkeiten.

Eine VM braucht genau wie ein Container saubere Betriebsregeln: Patchmanagement, Monitoring, Logging, Backup, Zugriffskontrolle und Konfigurationsmanagement.

---

4. Cloud-Begriffe

VPC und VNet

VPC oder VNet bezeichnet ein isoliertes Netzwerk in einer Cloud. Darin werden Subnetze, Routing, Security Groups und Verbindungen zu anderen Netzen definiert.

Im Lab wird eine Cloud nicht als magischer Ort betrachtet. Auch dort gelten Zonen, Sicherheitsregeln und Namenskonventionen.

Public Subnet

Ein Public Subnet enthält Ressourcen, die über das Internet erreichbar sein können. Das bedeutet nicht, dass jede Ressource dort offen sein darf. Es bedeutet nur, dass Routing zum Internet grundsätzlich möglich ist.

Typische Komponenten:

Vorlage / Textauszug
  • Public Load Balancer
  • Bastion Host, wenn wirklich nötig
  • API Gateway

Private Subnet

Ein Private Subnet enthält interne Ressourcen ohne direkten Internetzugang von außen. Anwendungskomponenten, Datenbanken und interne Services gehören meist in private Subnetze.

Security Group

Eine Security Group ist eine cloudnahe Firewall-Regelgruppe. Sie definiert, welcher Traffic erlaubt ist. Wie bei klassischen Firewalls gilt: Regeln sollen eng, dokumentiert und überprüfbar sein.

IAM

IAM steht für Identity and Access Management. In der Cloud beschreibt IAM, welche Identität welche Ressource verwenden darf. Ein häufiger Fehler ist zu breite Berechtigung, zum Beispiel AdministratorAccess für eine CI/CD-Pipeline.

Besser ist das Least-Privilege-Prinzip: Jede Identität bekommt nur die Rechte, die sie wirklich braucht.

Object Storage

Object Storage speichert Dateien als Objekte, meistens über eine S3-kompatible API. Im Lab eignet sich Object Storage für Partnerdateien, Exporte, Backups und Austausch zwischen Systemen.

Wichtig: Object Storage ersetzt nicht automatisch eine relationale Datenbank. Es ist gut für Dateien und Objekte, aber nicht für transaktionale Geschäftslogik.

Managed Database

Eine Managed Database wird vom Cloud-Anbieter betrieben. Das reduziert Betriebsaufwand, aber nicht Architekturverantwortung. Backup, Zugriff, Kosten, Migration, Latenz und Datenklassifizierung müssen trotzdem geklärt werden.

---

5. Kubernetes- und OpenShift-Begriffe

Kubernetes

Kubernetes ist eine Plattform zur Orchestrierung von Containern. Es startet, überwacht und skaliert Anwendungen. Kubernetes löst aber nicht automatisch Architekturprobleme. Ein schlecht geschnittener Service bleibt schlecht, auch wenn er in Kubernetes läuft.

OpenShift

OpenShift ist eine Enterprise-Kubernetes-Plattform mit zusätzlichen Funktionen wie Routes, integrierter Registry, Build-Funktionen, Security Constraints, Operatoren und stärkerer Enterprise-Integration.

Im Lab ist OpenShift die Zielplattform für moderne Services.

Namespace

Ein Namespace trennt Ressourcen logisch. Im Lab werden Namespaces nach Domäne und Umgebung getrennt:

Vorlage / Textauszug

order-dev

order-test

order-prod

billing-dev

billing-test

billing-prod

platform-monitoring

security-keycloak

Namespaces helfen bei RBAC, Quotas, NetworkPolicies und Verantwortungsgrenzen.

Pod

Ein Pod ist die kleinste ausführbare Einheit in Kubernetes. Er enthält einen oder mehrere Container. Ein Pod ist vergänglich. Deshalb dürfen wichtige Daten nicht nur im Pod-Dateisystem gespeichert werden.

Deployment

Ein Deployment beschreibt, wie viele Replikate einer Anwendung laufen sollen und welches Container-Image verwendet wird. Es steuert Rollouts und Rollbacks.

Service

Ein Service gibt Pods eine stabile Netzwerkadresse innerhalb des Clusters. Pods können verschwinden und neu entstehen. Der Service bleibt stabil.

Route

Eine OpenShift Route macht einen Service von außen erreichbar. Sie ist besonders wichtig für Web-Anwendungen und APIs.

ConfigMap

Eine ConfigMap enthält nicht-geheime Konfiguration. Beispiele sind Feature Flags, URLs oder technische Einstellungen. Secrets gehören nicht in ConfigMaps.

Secret

Ein Secret enthält sensible Werte wie Passwörter, Tokens oder Zertifikate. Im Lab sollen Secrets möglichst über Vault oder External Secrets verwaltet werden, nicht hart in Git gespeichert werden.

NetworkPolicy

Eine NetworkPolicy begrenzt Netzwerkverkehr im Cluster. Sie ist ein wichtiges Werkzeug für Zero Trust. Ohne Policies können Workloads oft zu viel miteinander kommunizieren.

ServiceAccount

Ein ServiceAccount ist eine Identität für eine Anwendung im Cluster. Anwendungen sollen nicht mit administrativen Rechten laufen. Jeder Service bekommt nur die Rechte, die er braucht.

RBAC

RBAC steht für Role-Based Access Control. In Kubernetes/OpenShift steuert RBAC, welche Benutzer, Gruppen oder ServiceAccounts welche Aktionen ausführen dürfen.

Wichtig ist die Trennung:

Vorlage / Textauszug

Kubernetes RBAC: Wer darf im Cluster Ressourcen verwalten?

Fachliches RBAC: Welcher Benutzer darf im Admin Portal welche Geschäftsfunktion ausführen?

Diese beiden Ebenen dürfen nicht vermischt werden.

SCC

SCC steht für Security Context Constraints. OpenShift nutzt SCCs, um festzulegen, welche Sicherheitskontexte Pods verwenden dürfen. Das betrifft zum Beispiel Root-Rechte, UID, Capabilities und Volume-Typen.

Ein häufiger Fehler ist, einer Anwendung zu breite Rechte zu geben, nur weil sie sonst nicht startet. Besser ist, das Image und die Anwendung so zu bauen, dass sie mit wenig Rechten laufen.

---

6. Security- und Identity-Begriffe

Authentifizierung

Authentifizierung beantwortet die Frage: Wer bist du? Im Lab übernimmt Keycloak diese Aufgabe für Benutzer. Anwendungen vertrauen nicht auf selbstgebaute Login-Logik, sondern verwenden OIDC.

Autorisierung

Autorisierung beantwortet die Frage: Was darfst du? Ein Benutzer kann erfolgreich angemeldet sein, aber trotzdem keine Berechtigung für eine Admin-Funktion haben.

Keycloak

Keycloak ist ein Identity Provider. Er verwaltet Realms, Clients, Benutzer, Gruppen, Rollen und Tokens. Im Lab wird Keycloak an LDAP oder Active Directory angebunden.

OIDC

OIDC steht für OpenID Connect. Es baut auf OAuth 2.0 auf und wird für moderne Authentifizierung verwendet. Eine Anwendung erhält Tokens und prüft Identität und Claims.

JWT

JWT steht für JSON Web Token. Es enthält Claims wie Benutzername, Rollen, Gruppen, Ablaufzeit und Aussteller. Anwendungen dürfen einem JWT nur vertrauen, wenn Signatur, Issuer, Audience und Ablaufzeit korrekt sind.

Claim

Ein Claim ist eine Aussage im Token. Beispiele sind sub, email, preferred_username, roles oder groups. Anwendungen nutzen Claims für fachliche Entscheidungen.

Zero Trust

Zero Trust bedeutet: Kein System wird automatisch vertraut, nur weil es im internen Netzwerk ist. Jeder Zugriff wird anhand von Identität, Kontext, Policy und Nachweis bewertet.

Im Lab bedeutet Zero Trust:

Vorlage / Textauszug
  • nur notwendige Netzwerkverbindungen
  • starke Authentifizierung
  • klare Rollen
  • ServiceAccounts mit wenig Rechten
  • Audit Logs
  • Secrets nicht im Code
  • überprüfbare Policies

Least Privilege

Least Privilege bedeutet: Eine Identität bekommt nur die Rechte, die sie wirklich braucht. Das gilt für Benutzer, ServiceAccounts, CI/CD-Pipelines und Cloud-Rollen.

Audit Logging

Audit Logging dokumentiert sicherheitsrelevante Aktionen. Im Admin Portal muss zum Beispiel nachvollziehbar sein, wer eine Bestellung manuell geändert oder eine Rechnung neu ausgelöst hat.

Ein gutes Audit Log enthält:

Vorlage / Textauszug
  • Wer?
  • Was?
  • Wann?
  • Auf welchem Objekt?
  • Mit welchem Ergebnis?
  • Über welche Anwendung?
  • Mit welcher Trace-ID?

Governance

Governance beschreibt Regeln, Verantwortlichkeiten und Nachweise. Im Lab bedeutet Governance nicht Bürokratie, sondern klare Qualitätsgrenzen: Pull Requests, ADRs, Security Reviews, Runbooks, Tests und Betriebsnachweise.

---

7. Entwicklungs- und Anwendungsbegriffe

Domäne

Die Domäne ist der fachliche Problembereich. Im Lab gehören Bestellung, Rechnung, Kunde, Partnerdatei und Reporting zur Domäne. Gute Software modelliert die Domäne klar, statt nur Datenbanktabellen nach außen zu reichen.

Use Case

Ein Use Case beschreibt einen fachlichen Ablauf. Beispiel: Bestellung anlegen, Rechnung erzeugen, Partnerdatei validieren. Im Code liegt ein Use Case oft in der Anwendungsschicht.

Controller

Ein Controller nimmt HTTP-Anfragen entgegen und übersetzt sie in Use-Case-Aufrufe. Er sollte nicht die gesamte Geschäftslogik enthalten.

Schlecht:

Vorlage / Textauszug

Controller validiert alles, speichert Daten, sendet Events und baut SQL selbst.

Besser:

Vorlage / Textauszug

Controller nimmt Request entgegen, prüft Grundform, ruft Use Case auf und gibt Response zurück.

Repository

Ein Repository kapselt Datenzugriff. Die Domäne muss nicht wissen, ob Daten über JPA, JDBC oder eine externe API gespeichert werden.

Outbox Pattern

Das Outbox Pattern speichert Events zuerst in derselben Datenbanktransaktion wie die fachliche Änderung. Danach veröffentlicht ein separater Publisher die Events an den Broker.

Es verhindert, dass Datenbankzustand und Event-Veröffentlichung auseinanderlaufen.

Idempotenz

Idempotenz bedeutet, dass eine Operation mehrfach ausgeführt werden kann, ohne falsche Mehrfachwirkung zu erzeugen. Bei Messaging ist Idempotenz wichtig, weil Events erneut zugestellt werden können.

Beispiel: Der Billing Service darf eine Rechnung nicht doppelt erzeugen, nur weil ein OrderCreated Event erneut gelesen wird.

API Gateway

Ein API Gateway bündelt und kontrolliert API-Zugriffe. Es kann Routing, Authentifizierung, Rate Limiting, TLS und zentrale Policies unterstützen. Es ersetzt aber keine saubere Servicearchitektur.

Backend for Frontend

Ein Backend for Frontend ist ein Backend, das speziell auf ein Frontend zugeschnitten ist. Es kann sinnvoll sein, wenn ein Customer Portal andere API-Bedürfnisse hat als ein Admin Portal.

---

8. Daten- und Integrationsbegriffe

Datenbesitz

Datenbesitz bedeutet: Ein Service oder eine Domäne ist verantwortlich für bestimmte Daten. Andere Services greifen nicht einfach direkt auf seine Tabellen zu.

Im Lab besitzt der Order Service Bestelldaten. Billing besitzt Rechnungsdaten. Reporting darf Kopien oder Views nutzen, aber nicht heimlich Geschäftslogik über fremde Tabellen erzwingen.

Gemeinsame Datenbank

Eine gemeinsame Datenbank zwischen mehreren Services ist oft ein Legacy-Muster. Es wirkt einfach, führt aber zu starker Kopplung. Änderungen an Tabellen betreffen dann mehrere Systeme gleichzeitig.

Migration

Eine Migration verändert Datenbankstruktur oder Daten. Im Lab werden Flyway oder Liquibase verwendet. Migrationen gehören in Git und werden im Deployment kontrolliert ausgeführt.

Event

Ein Event beschreibt etwas, das fachlich passiert ist. Beispiel: OrderCreated, InvoiceGenerated, PartnerFileRejected. Ein Event ist keine Anweisung, sondern eine Tatsache.

Command

Ein Command fordert eine Aktion an. Beispiel: CreateOrder, CancelOrder, GenerateInvoice. Commands und Events sollten nicht verwechselt werden.

Kafka

Kafka ist ein verteilter Event-Streaming-Broker. Es eignet sich für Event Streams, Replay, hohe Durchsatzraten und langlebige Topics.

RabbitMQ

RabbitMQ ist ein Message Broker, der stark bei Queues, Routing und klassischem Messaging ist. Für viele Unternehmensintegrationen ist RabbitMQ gut geeignet.

Dead Letter Queue

Eine Dead Letter Queue nimmt Nachrichten auf, die nicht erfolgreich verarbeitet werden konnten. Sie ist wichtig, damit fehlerhafte Nachrichten nicht endlos den normalen Fluss blockieren.

Anti-Corruption Layer

Ein Anti-Corruption Layer schützt eine moderne Domäne vor schlechten oder alten Modellen eines Legacy-Systems. Er übersetzt externe Begriffe, Datenstrukturen und Fehlerbilder in saubere interne Modelle.

---

9. Observability- und Betriebsbegriffe

Monitoring

Monitoring beobachtet Systeme anhand von Metriken, Logs und Zuständen. Es zeigt, ob etwas funktioniert oder nicht.

Observability

Observability geht weiter. Sie hilft, aus Systemsignalen zu verstehen, warum etwas passiert. Logs, Metriken und Traces werden gemeinsam betrachtet.

Log

Ein Log ist ein Ereignisprotokoll. Gute Logs sind strukturiert, enthalten Trace-ID, Benutzer- oder Systemkontext und vermeiden sensible Daten.

Metrik

Eine Metrik ist ein messbarer Wert. Beispiele sind Request-Dauer, Fehlerrate, CPU-Nutzung, Outbox Backlog oder Anzahl fehlerhafter Partnerdateien.

Trace

Ein Trace verfolgt eine Anfrage über mehrere Systeme. Das ist wichtig, wenn ein Customer-Portal-Aufruf über API Gateway, Order Service, Datenbank und Broker läuft.

SLI

SLI steht für Service Level Indicator. Ein SLI ist eine konkrete Messgröße, zum Beispiel Erfolgsrate oder Latenz.

SLO

SLO steht für Service Level Objective. Ein SLO definiert ein Ziel, zum Beispiel: 99,5 Prozent der Order-API-Aufrufe sollen unter 500 Millisekunden bleiben.

Alert

Ein Alert informiert, wenn ein Zustand Aufmerksamkeit braucht. Ein schlechter Alert ist laut, aber unklar. Ein guter Alert ist konkret, handlungsorientiert und mit einem Runbook verknüpft.

Runbook

Ein Runbook beschreibt, was bei einem bestimmten Betriebsproblem zu tun ist. Es verbindet Symptome, Prüfkommandos, Ursachen, Maßnahmen und Eskalation.

---

10. Backup- und DR-Begriffe

Backup

Ein Backup ist eine Sicherung von Daten oder Konfiguration. Ein Backup ist nur dann wertvoll, wenn Restore getestet wurde.

Restore

Restore ist die Wiederherstellung aus einem Backup. Im Lab wird Restore nicht nur theoretisch erwähnt, sondern als Übung und Runbook behandelt.

Disaster Recovery

Disaster Recovery beschreibt den Wiederanlauf nach einem größeren Ausfall. Es geht nicht nur um Daten, sondern um Reihenfolge, Abhängigkeiten, Kommunikation und Validierung.

RPO

RPO steht für Recovery Point Objective. Es beschreibt, wie viel Datenverlust maximal akzeptabel ist.

RTO

RTO steht für Recovery Time Objective. Es beschreibt, wie lange die Wiederherstellung maximal dauern darf.

WAL

WAL steht für Write-Ahead Log. PostgreSQL nutzt WAL, um Änderungen nachvollziehbar zu speichern. WAL-Archivierung ist wichtig für Point-in-Time-Recovery.

VolumeSnapshot

Ein VolumeSnapshot ist eine Momentaufnahme eines Persistent Volumes. In Kubernetes/OpenShift kann das für bestimmte Restore-Szenarien nützlich sein, ersetzt aber nicht jedes Datenbankbackup.

---

11. DevOps- und GitOps-Begriffe

DevOps

DevOps verbindet Entwicklung und Betrieb. Im Lab bedeutet das: Entwickler schreiben nicht nur Code, sondern berücksichtigen Deployment, Monitoring, Security und Runbooks. Betrieb arbeitet nicht nur reaktiv, sondern mit Automatisierung, Git und Feedback an Teams.

CI

CI steht für Continuous Integration. Codeänderungen werden automatisch gebaut und getestet.

CD

CD steht für Continuous Delivery oder Continuous Deployment. Im Enterprise-Kontext bedeutet es oft: Artefakte sind jederzeit auslieferbar, aber produktive Freigabe kann kontrolliert erfolgen.

GitOps

GitOps bedeutet: Der gewünschte Plattformzustand liegt in Git. Ein Controller wie Argo CD vergleicht Git mit dem Cluster und synchronisiert Abweichungen.

Argo CD

Argo CD ist ein GitOps-Werkzeug für Kubernetes/OpenShift. Es deployt Anwendungen aus Git-Repositories und zeigt Synchronisationszustand, Drift und Fehler.

Kustomize

Kustomize passt Kubernetes-Manifeste ohne Templates an. Es eignet sich gut für Overlays wie dev, test und prod.

Helm

Helm ist ein Paket- und Template-System für Kubernetes. Es eignet sich für wiederverwendbare Charts, kann aber bei komplexen Templates schwer lesbar werden.

Image Tag

Ein Image Tag identifiziert eine Container-Image-Version. Für produktive Deployments sollten nachvollziehbare Tags oder Digests verwendet werden. latest ist für produktionsnahe Umgebungen ungeeignet.

Rollback

Rollback bedeutet Rückkehr zu einer vorherigen Version. Bei Datenbankmigrationen ist Rollback schwieriger als bei Container Images. Deshalb braucht Release Management auch Rollforward-Strategien.

---

12. AI-Begriffe im Enterprise-Projekt

Codex

Codex wird im Lab als Unterstützung für Code, Tests, Refactoring und technische Artefakte betrachtet. Codex darf Vorschläge machen, aber nicht ungeprüft produktionsrelevante Entscheidungen ersetzen.

Claude Code

Claude Code wird im Lab als Unterstützung für größere Code- und Architekturaufgaben betrachtet, besonders bei Analyse, Refactoring und Dokumentation. Auch hier gilt: AI-Ergebnisse müssen überprüft werden.

AI-Fülltext

AI-Fülltext ist Text, der sprachlich korrekt wirkt, aber fachlich wenig aussagt. Der hochgeladene Problemausschnitt war genau so ein Beispiel: dieselbe Struktur wiederholt sich, nur einzelne Begriffe werden ersetzt.

Gegenmaßnahme:

Vorlage / Textauszug
  • konkrete Beispiele fordern
  • Wiederholungen prüfen
  • Code und Artefakte verlangen
  • fachliche Unterschiede kontrollieren
  • keine künstlichen Nummern akzeptieren

Prompt-Regel

Eine Prompt-Regel ist eine feste Vorgabe für AI-Arbeit. Im Lab lautet eine wichtige Regel: Kein Kapitel darf nur aus Tabellen oder Glossar bestehen, und keine Inhalte dürfen künstlich durch austauschbare Absätze verlängert werden.

---

13. Fachindex nach Themen

Architektur

Vorlage / Textauszug

ADR

Architekturentscheidung

Enterprise-Architektur

Hybrid-Architektur

Zielbild

Domänenschnitt

Betriebsmodell

Infrastruktur

Vorlage / Textauszug

DMZ

VLAN

Subnetz

Firewall

DNS

Load Balancer

Bare Metal

Virtuelle Maschine

Storage

Bastion Host

Cloud

Vorlage / Textauszug

VPC

VNet

Public Subnet

Private Subnet

IAM

Security Group

Object Storage

Managed Database

VPN

Direct Connect

Kubernetes/OpenShift

Vorlage / Textauszug

Namespace

Pod

Deployment

Service

Route

ConfigMap

Secret

ServiceAccount

RBAC

SCC

NetworkPolicy

PersistentVolume

CronJob

Operator

Security

Vorlage / Textauszug

Keycloak

OIDC

JWT

Claim

RBAC

Zero Trust

Least Privilege

Audit Logging

Secret Rotation

Policy as Code

Anwendungen

Vorlage / Textauszug

Use Case

Controller

Repository

Domain Model

Outbox Pattern

Idempotenz

API Gateway

Backend for Frontend

OpenAPI

Testcontainers

Daten und Integration

Vorlage / Textauszug

Datenbesitz

Migration

Event

Command

Kafka

RabbitMQ

Dead Letter Queue

Anti-Corruption Layer

CDC

Object Storage

Betrieb

Vorlage / Textauszug

Monitoring

Observability

Log

Metrik

Trace

SLI

SLO

Alert

Runbook

Incident

Postmortem

Backup und DR

Vorlage / Textauszug

Backup

Restore

Disaster Recovery

RPO

RTO

WAL

VolumeSnapshot

Restore-Test

DevOps und GitOps

Vorlage / Textauszug

CI

CD

GitOps

Argo CD

Kustomize

Helm

Image Tag

Rollback

Rollforward

Release Notes

---

14. Prüfregel für spätere HTML-Erzeugung

Bei der späteren HTML-Erzeugung muss dieser Teil nicht als riesiger Glossarblock am Ende erscheinen. Stattdessen wird er als Fachindex eingebunden und zusätzlich mit den Hauptkapiteln verlinkt.

Die HTML-Version soll folgende Regeln einhalten:

Vorlage / Textauszug
  • Begriffe bleiben mit Fachkapiteln verbunden.
  • Keine isolierte Glossarwüste als Hauptinhalt.
  • Jeder Begriff erhält Kontext, Fehlerbild und Nachweis.
  • Fachindex ist klappbar und standardmäßig geschlossen.
  • Navigation verwendet fachliche Namen.
  • Keine generischen Begriffe wie Anhang 16 oder Praxisdurchlauf X.

---

15. Übung

Wähle fünf Begriffe aus diesem Teil und ordne sie konkreten Artefakten im Lab zu.

Beispiel-Fragen:

Vorlage / Textauszug
  1. Welches Artefakt zeigt eine Architekturentscheidung?
  2. Welches Manifest zeigt eine NetworkPolicy?
  3. Wo wird ein Secret technisch definiert?
  4. Wo wird ein Restore beschrieben?
  5. Wo wird ein SLO messbar gemacht?

---

16. Musterlösung

Eine mögliche Lösung:

Vorlage / Textauszug

ADR:

Nachweis durch docs/adr/ADR-001-hybrid-platform.md

NetworkPolicy:

Nachweis durch platform/network-policies/order-prod-egress.yaml

Secret:

Nachweis durch security/secrets/order-db-external-secret.yaml oder ExternalSecret im GitOps-Repo

Restore:

Nachweis durch docs/runbooks/postgresql-restore.md

SLO:

Nachweis durch observability/prometheus/order-service-slo-rules.yaml und Grafana-Dashboard

Diese Zuordnung zeigt, dass Begriffe nicht nur gelernt, sondern im Projekt verankert werden.

---

17. Merksätze

Vorlage / Textauszug

Ein Begriff ist erst wirklich verstanden, wenn man ihn im Projekt zeigen kann.

Ein Glossar ohne Kontext hilft wenig.

Ein Fachindex ist nützlich, wenn er auf echte Kapitel, Artefakte und Fehlerbilder verweist.

Enterprise-Wissen besteht aus Sprache, Code, Infrastruktur, Betrieb und Nachweisen.

Die spätere HTML-Datei darf groß sein, aber nur durch echten Inhalt, nicht durch künstliche Wiederholung.

Kompakte SVGs und großer Fachglossar

Ziel dieses Schritts

Dieser Schritt ergänzt das Masterbuch um eine kompakte visuelle Referenz und einen großen Fachglossar. Die SVGs sind bewusst klein, direkt eingebettet und ohne externe Bilddateien nutzbar. Der Glossar ist als Nachschlagebereich gedacht und ersetzt nicht die fachlichen Kapitel. Er unterstützt die spätere HTML-Version, weil Leser zentrale Begriffe schnell finden, ohne zwischen mehreren Anhängen suchen zu müssen.

Wichtig bleibt: Die SVGs und Begriffe werden nicht als künstliche Füllmasse verwendet. Sie sind ein Navigations- und Verständniswerkzeug für die bereits geschriebenen Fachkapitel.

Kompakte SVG-Übersichten

Die folgenden Diagramme sind bewusst reduziert. Sie zeigen nicht jedes Detail, sondern den Kernfluss. Für mobile Geräte ist das besser als große, überladene Diagramme.

Hybrid-Zielbild kompakt

Hybrid-Zielbild kompakt APIs Legacy Daten Cloud OpenShift VMs Bare Metal

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Zonenmodell kompakt

Zonenmodell kompakt 443 Routes 5432/9092 Internet DMZ App Zone Data Zone

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

OIDC-Fluss kompakt

OIDC-Fluss kompakt Login Token JWT Browser Keycloak API Gateway Service

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Outbox-Fluss kompakt

Outbox-Fluss kompakt TX poll event Order DB + Outbox Publisher Billing

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

GitOps-Fluss kompakt

GitOps-Fluss kompakt merge watch sync Pull Request Git Repo Argo CD Cluster

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Observability kompakt

Observability kompakt Prom Loki Service Metriken Logs Grafana

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Backup/Restore kompakt

Backup/Restore kompakt WAL restore Nachweis Prod DB Backup Store Restore Test DR Freigabe

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

VM zu OpenShift kompakt

VM zu OpenShift kompakt Facade Slice Deploy VM App Strangler Neue API OpenShift

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Security-Grenzen kompakt

Security-Grenzen kompakt wer darf Nachweis ServiceAccount RBAC NetworkPolicy Audit

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Release-Fluss kompakt

Release-Fluss kompakt scan tag sync CI Image Overlay Prod

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

File Processing kompakt

File Processing kompakt file parse ok/error Object Store Worker VM Validierung Event

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

DR-Wiederanlauf kompakt

DR-Wiederanlauf kompakt 1 2 3 Identity Daten Plattform Apps

Kurzer Nutzen: Dieses Diagramm dient als schneller visueller Anker für den jeweiligen Architekturfluss. Details bleiben im passenden Fachkapitel erklärt.

Großer Fachglossar

Der Glossar ist alphabetisch und praxisbezogen. Jede Erklärung bezieht sich auf das Enterprise Platform Lab und vermeidet reine Wörterbuchdefinitionen.

ADR

Architecture Decision Record. Dokumentiert eine wichtige Entscheidung mit Kontext, Entscheidung und Konsequenzen, damit Architektur nicht nur mündlich existiert.

API Gateway

Zentrale Eingangsschicht für APIs. Es kann Routing, Authentifizierung, Rate Limits und Protokollwechsel bündeln, ersetzt aber keine saubere Servicegrenze.

Application Zone

Netzwerkzone für Anwendungssysteme. Sie darf typischerweise nicht direkt aus dem Internet erreichbar sein und spricht nur kontrolliert mit der Data Zone.

Argo CD

GitOps-Werkzeug, das Kubernetes- oder OpenShift-Zustand aus Git synchronisiert. Git wird dadurch zur nachvollziehbaren Quelle der Deployments.

Audit Log

Protokoll sicherheits- oder fachlich relevanter Aktionen. Wichtig ist nicht nur das Speichern, sondern die spätere Auswertbarkeit im Störungs- und Compliance-Fall.

Backlog

Ansammlung unbearbeiteter Arbeit, etwa nicht veröffentlichter Outbox-Events. Ein Backlog ist ein Betriebsindikator und muss überwacht werden.

Backup

Kopie von Daten oder Systemzustand. Ein Backup ist erst wertvoll, wenn Restore regelmäßig geprüft wurde.

Bare Metal

Physischer Server ohne Virtualisierungsschicht. Sinnvoll bei besonderen Storage-, Latenz- oder Performance-Anforderungen.

Bastion Host

Kontrollierter Zugangspunkt für Administration. Er reduziert direkte SSH-Zugriffe aus beliebigen Netzen.

Billing Service

Service für Rechnungs- oder Abrechnungslogik. Im Lab verarbeitet er Events vom Order Service idempotent.

Blue-Green Deployment

Release-Strategie mit zwei Umgebungen. Die neue Version wird parallel bereitgestellt und Traffic wird kontrolliert umgeschaltet.

Broker

Messaging-System wie Kafka oder RabbitMQ. Es entkoppelt Producer und Consumer, beseitigt aber nicht die Pflicht zu Idempotenz und Fehlerbehandlung.

Canary Release

Schrittweise Auslieferung einer neuen Version an einen kleinen Teil der Nutzer oder des Traffics.

CD

Continuous Delivery oder Continuous Deployment. In diesem Lab bedeutet es nachvollziehbares Ausliefern über Pipeline und GitOps.

CI

Continuous Integration. Code wird gebaut, getestet und geprüft, bevor er als lieferfähig gilt.

Cloud Landing Zone

Grundstruktur in der Cloud mit Accounts, Netzwerk, IAM, Logging, Security und Kostenkontrolle.

ConfigMap

Kubernetes-Objekt für nicht geheime Konfiguration. Sensible Werte gehören nicht in ConfigMaps.

Container Image

Paket aus Anwendung, Runtime und Abhängigkeiten. Es muss versioniert, gescannt und reproduzierbar gebaut werden.

Correlation-ID

Kennung, mit der zusammengehörige Logs über mehrere Services verbunden werden können.

CronJob

Kubernetes-Ressource für zeitgesteuerte Jobs. Geeignet für periodische Aufgaben, aber nicht für dauerhaft laufende Services.

Customer Portal

Frontend für Kunden. Es ruft APIs auf, speichert keine Secrets und nutzt OIDC für Anmeldung.

Data Zone

Netzwerkzone für Datenbanken, Broker und Storage. Sie ist besonders geschützt und sollte nicht direkt aus DMZ oder Internet erreichbar sein.

Dead Letter Queue

Ablage für Nachrichten, die nicht erfolgreich verarbeitet werden konnten. Sie ist kein Mülleimer, sondern ein Betriebsnachweis.

Deployment

Kubernetes-Ressource für zustandslose Pods. Sie beschreibt gewünschte Replikas, Container, Probes und Ressourcen.

DevOps

Arbeitsmodell, bei dem Entwicklung und Betrieb gemeinsam Verantwortung für lauffähige, beobachtbare und wiederherstellbare Systeme übernehmen.

Disaster Recovery

Wiederanlauf nach großem Ausfall. DR braucht Reihenfolge, Verantwortliche, Datenwiederherstellung und Tests.

DMZ

Demilitarized Zone. Übergangszone für öffentlich erreichbare Komponenten, ohne direkte Öffnung interner Systeme.

DNS

Namensauflösung. Fehler in DNS wirken oft wie Anwendungsfehler und müssen deshalb Teil der Architekturprüfung sein.

Dockerfile

Build-Anweisung für Container Images. Es sollte klein, nachvollziehbar, sicher und ohne unnötige Tools aufgebaut sein.

Environment Promotion

Kontrolliertes Überführen einer Version von Dev nach Test, Stage und Prod.

ExternalSecret

Ressource, die Secrets aus einem externen Secret Store in Kubernetes nutzbar macht.

Feature Flag

Schalter, mit dem Funktionen aktiviert oder deaktiviert werden können, ohne zwingend neu zu deployen.

File Processing

Verarbeitung von Partnerdateien oder Exporten. Kann auf VM, Bare Metal oder Kubernetes laufen, je nach Storage- und Lastprofil.

Firewall-Matrix

Dokumentation erlaubter Netzwerkflüsse mit Quelle, Ziel, Port, Zweck und Verantwortlichkeit.

Flyway

Werkzeug für versionierte Datenbankmigrationen. Es macht Schemaänderungen nachvollziehbar.

GitOps

Betriebsmodell, bei dem gewünschter Plattformzustand in Git liegt und automatisiert synchronisiert wird.

Grafana

Visualisierung für Metriken, Logs und teilweise Traces. Grafana ersetzt keine sauberen SLOs.

Hardening

Härtung von Systemen durch Reduktion von Angriffsfläche, sichere Defaults, eingeschränkte Rechte und Nachweise.

Health Check

Prüfung, ob ein Service grundsätzlich läuft. Readiness und Liveness haben unterschiedliche Bedeutung.

Helm

Paket- und Template-Werkzeug für Kubernetes-Ressourcen. Mächtig, aber bei falscher Nutzung schwer lesbar.

Hybrid Architektur

Architektur, die Cloud, VM, Bare Metal und Kubernetes gezielt kombiniert statt alles ideologisch auf eine Plattform zu zwingen.

IAM

Identity and Access Management. Regelt Identitäten, Rollen, Policies und Zugriffsrechte.

Idempotenz

Eigenschaft, dass eine wiederholte Verarbeitung keine falschen Mehrfachwirkungen erzeugt.

Incident

Störung mit Auswirkung auf Betrieb oder Sicherheit. Ein Incident braucht Triage, Eindämmung, Kommunikation und Nachbereitung.

Ingress

Kubernetes-Mechanismus für eingehenden HTTP/S-Traffic. In OpenShift wird häufig Route verwendet.

Jenkinsfile

Pipeline-Definition für Jenkins. In Enterprise-Umgebungen oft weiterhin wichtig.

Kafka

Event-Streaming-Plattform. Gut für Event Logs und skalierbare Verarbeitung, verlangt aber saubere Topic- und Consumer-Strategien.

Keycloak

Identity Provider für OIDC, OAuth2 und SAML. Im Lab verbindet er Anwendungen mit LDAP/AD und Rollen.

Kustomize

Werkzeug für Kubernetes-Overlays ohne Template-Sprache. Gut für klare Umgebungsvarianten.

LDAP

Verzeichnisdienst für Benutzer, Gruppen und Organisationseinheiten. Häufig Grundlage für Enterprise-Identity.

Liveness Probe

Kubernetes-Prüfung, ob ein Container neu gestartet werden soll. Sie darf nicht mit fachlicher Bereitschaft verwechselt werden.

Loki

Log-System, das häufig mit Grafana verwendet wird. Es arbeitet labelorientiert und passt gut zu Kubernetes-Logs.

Managed Database

Cloud-Datenbankdienst mit ausgelagertem Betrieb. Er reduziert Aufwand, verändert aber Kosten-, Netzwerk- und Governance-Fragen.

Maven

Build-Werkzeug im Java-Ökosystem. Für Enterprise-Projekte wichtig wegen Modulstruktur, Plugins und reproduzierbaren Builds.

Micrometer

Metrik-Bibliothek für JVM-Anwendungen. Sie verbindet Anwendungscode mit Monitoring-Systemen wie Prometheus.

MinIO

S3-kompatibler Object Storage, oft für On-Prem- oder Lab-Umgebungen genutzt.

Namespace

Kubernetes-Isolations- und Organisationsbereich. Nützlich für Teams, Umgebungen, RBAC und NetworkPolicies.

NetworkPolicy

Kubernetes-Regel für erlaubte Pod-Kommunikation. Ein praktischer Baustein für Zero Trust im Cluster.

Node Exporter

Prometheus Exporter für Linux-Host-Metriken wie CPU, RAM, Disk und Netzwerk.

Object Storage

Speicher für Objekte über API, meist S3-kompatibel. Gut für Dateien, Exporte, Backups und Partnerdaten.

OIDC

OpenID Connect. Protokoll für Authentifizierung auf Basis von OAuth2.

OpenAPI

Maschinenlesbare API-Beschreibung. Hilft bei Dokumentation, Client-Generierung und Review.

OpenShift

Enterprise-Kubernetes-Plattform mit zusätzlichen Funktionen wie Routes, SCC, integrierter Registry und Operator-Ökosystem.

OpenTelemetry

Standard für Traces, Metriken und Logs. Hilft, verteilte Abläufe sichtbar zu machen.

Outbox Pattern

Muster, bei dem Datenänderung und Event in derselben Datenbanktransaktion gespeichert werden.

PodDisruptionBudget

Kubernetes-Regel, die Mindestverfügbarkeit bei freiwilligen Unterbrechungen schützt.

PostgreSQL

Relationale Datenbank. Im Lab zentrale Datenhaltung für Order und Billing.

Prometheus

Monitoring-System für Metriken. Es sammelt Werte über Pull-Mechanismus und wertet Alerts aus.

PrometheusRule

Kubernetes/OpenShift-Ressource für Alert-Regeln. Sie macht Betriebswissen versionierbar.

Pull Request

Review-Einheit für Code, Manifeste oder Dokumentation. Im Lab eine Qualitätsgrenze.

RabbitMQ

Message Broker mit Queues und Routing. Gut für klassische asynchrone Verarbeitung.

RBAC

Role Based Access Control. Rollen und Bindings legen fest, wer was darf.

Readiness Probe

Kubernetes-Prüfung, ob ein Pod Traffic erhalten darf. Sie sollte abhängige Startbedingungen berücksichtigen.

Release Notes

Beschreibung ausgelieferter Änderungen, Risiken und Betriebsfolgen.

ResourceQuota

Namespace-Grenze für Ressourcenverbrauch. Verhindert unkontrollierten Verbrauch durch fehlerhafte Workloads.

Restore

Wiederherstellung aus Backup. Restore ist der eigentliche Beweis für ein funktionierendes Backup.

Route

OpenShift-Ressource für extern erreichbare HTTP/S-Endpunkte.

Runbook

Betriebsanleitung für wiederkehrende Fehlerbilder oder Aufgaben. Es muss ausführbar, nicht nur beschreibend sein.

SCC

Security Context Constraints in OpenShift. Sie begrenzen, wie Pods laufen dürfen.

Secret

Kubernetes-Objekt für sensible Werte. Es braucht Schutz, Rotation und klare Besitzverhältnisse.

Service

Kubernetes-Abstraktion für Netzwerkzugriff auf Pods. Selector und Ports müssen sauber passen.

ServiceAccount

Identität für Workloads im Cluster. Wichtig für RBAC, externe Zugriffe und Audit.

ServiceMonitor

Ressource des Prometheus Operators, die beschreibt, welche Services Metriken liefern.

SFTP Gateway

Kontrollierter Dateiübergabepunkt für Partner oder Legacy-Systeme.

SLO

Service Level Objective. Zielwert für Verfügbarkeit oder Qualität, z. B. Erfolgsrate oder Latenz.

SLI

Service Level Indicator. Messwert, auf dem ein SLO basiert.

Software Supply Chain

Lieferkette von Code, Dependencies, Build, Image, Registry und Deployment.

Spring Boot Actuator

Spring-Boot-Modul für Health, Metrics und Betriebsendpunkte.

Strangler Pattern

Modernisierungsmuster, bei dem Legacy-Funktionalität schrittweise durch neue Services ersetzt wird.

Terraform/OpenTofu

Infrastructure-as-Code-Werkzeuge zur Beschreibung von Cloud- und Infrastrukturressourcen.

Testcontainers

Java-Testbibliothek, die echte Infrastrukturkomponenten als Container für Integrationstests startet.

Trace

Verfolgung eines Requests über mehrere Services. Hilft bei Latenz- und Fehleranalyse.

Trace-ID

Eindeutige Kennung eines technischen Ablaufs über Servicegrenzen hinweg.

Vault

Secret-Management-System für dynamische und statische Geheimnisse.

VLAN

Logische Netzwerksegmentierung. Hilft bei Zonierung und Zugriffstrennung.

VM

Virtuelle Maschine. Weiterhin wichtig für Legacy, Spezialtools und Übergangsarchitekturen.

VolumeSnapshot

Kubernetes-Mechanismus für Storage-Snapshots, abhängig vom Storage-Treiber.

WAL

Write-Ahead Log. Bei PostgreSQL wichtig für Recovery und Point-in-Time-Restore.

Zero Trust

Sicherheitsprinzip: Kein implizites Vertrauen aufgrund von Netzwerkposition. Identität, Kontext und Policies entscheiden.

Zone

Sicherheits- oder Betriebsbereich im Netzwerk. Zonen strukturieren erlaubte Kommunikationswege.

Admin Portal

Interne Oberfläche für Mitarbeiter. Benötigt stärkere Rollen, Audit und oft separate Freigaben.

Anti-Corruption Layer

Schicht, die ein modernes Domänenmodell vor Legacy-Strukturen schützt.

Backpressure

Kontrollmechanismus, wenn Verbraucher langsamer sind als Erzeuger. Wichtig bei Messaging und Streaming.

Circuit Breaker

Resilienz-Muster, das wiederholte fehlgeschlagene Aufrufe zeitweise unterbricht.

Config Drift

Abweichung zwischen dokumentiertem oder gewünschtem Zustand und realem Systemzustand.

Dependency Scan

Prüfung von Bibliotheken auf bekannte Schwachstellen oder Lizenzprobleme.

Image Registry

Speicherort für Container Images. Muss geschützt, gescannt und versioniert genutzt werden.

Immutable Infrastructure

Prinzip, Systeme nicht im laufenden Zustand manuell zu verändern, sondern neu reproduzierbar bereitzustellen.

Least Privilege

Prinzip, nur die minimal notwendigen Rechte zu vergeben.

MTTR

Mean Time To Recovery. Durchschnittliche Zeit bis zur Wiederherstellung nach Störung.

Policy as Code

Sicherheits- oder Governance-Regeln werden versioniert und automatisch geprüft.

RPO

Recovery Point Objective. Maximal tolerierbarer Datenverlust.

RTO

Recovery Time Objective. Maximal tolerierbare Wiederherstellungszeit.

Sidecar

Zusätzlicher Container im selben Pod, z. B. für Proxy oder Hilfsfunktionen.

SLA

Service Level Agreement. Vertragliche oder organisatorische Zusage für Servicequalität.

Structured Logging

Logs in strukturierter Form, häufig JSON, damit sie maschinell auswertbar sind.

Tenant

Logischer Mandant oder Verantwortungsbereich. In Plattformen wichtig für Trennung und Governance.

TLS

Transportverschlüsselung. Zertifikate, Hostnamen und Trust Chains müssen zusammenpassen.

VPN

Verschlüsselte Netzwerkverbindung, z. B. zwischen Standort und Cloud.

WAF

Web Application Firewall. Schutzschicht gegen typische Webangriffe, ersetzt aber keine sichere Anwendung.

Webhook

HTTP-Rückruf bei Ereignissen. Muss abgesichert, nachvollziehbar und fehlertolerant sein.

Abnahmeregel für diesen Schritt

Die SVGs müssen im finalen HTML inline erscheinen und dürfen keine externen Dateien benötigen. Der Glossar darf groß sein, aber er muss klar als Referenzteil markiert bleiben. In den Hauptkapiteln sollen Begriffe weiterhin im Kontext erklärt werden.

Übung

Wähle fünf Begriffe aus dem Glossar und ordne sie jeweils einem konkreten Artefakt im Lab zu, zum Beispiel Manifest, README, ADR, Runbook, Test oder Dashboard.

Musterlösung

  • NetworkPolicy gehört zu Kubernetes-Manifests und begrenzt erlaubte Pod-Kommunikation.
  • ADR gehört zu docs/adr und dokumentiert eine Architekturentscheidung.
  • Outbox Pattern gehört zu Datenbankmigration, Anwendungscode und Runbook für Backlog.
  • ServiceMonitor gehört zur Observability-Konfiguration und macht Metriken auffindbar.
  • Restore gehört zu Backup-Runbooks und muss regelmäßig getestet werden.

Zusätzliche Architektur-SVGs, Glossar-Querverweise und HTML-Ausbau

Dieser Teil erweitert das Masterbuch nicht durch Wiederholung, sondern durch kompakte visuelle Orientierung und bessere Querverweise. Die SVGs sind bewusst klein gehalten: Sie sollen in der HTML-Datei nicht wie große Poster wirken, sondern als schnelle Orientierungskarten neben dem Fachtext dienen.

1. Warum weitere kompakte SVGs sinnvoll sind

Bei großen Enterprise-Lernbüchern entsteht schnell ein anderes Problem als bei kurzen Tutorials: Der Leser verliert die Gesamtkarte. Viele Kapitel erklären einzelne Themen gut, aber der Zusammenhang zwischen Netzwerk, Identity, Deployment, Daten, Betrieb und Recovery wird schwerer sichtbar. Kompakte SVGs lösen dieses Problem, wenn sie nicht als Dekoration eingesetzt werden, sondern als fachliche Wegweiser.

Eine gute kompakte Grafik beantwortet nicht jede Detailfrage. Sie zeigt, in welcher Reihenfolge gedacht werden soll. Für das Lab bedeutet das: Ein Diagramm darf klein sein, muss aber eine klare Aussage haben.

2. End-to-End-Sicht

Kunden- und Mitarbeiterzugriffe werden nicht direkt auf interne Services geleitet. Die Architektur trennt Einstieg, Authentifizierung, Service-Zugriff und Datenhaltung. Diese Sicht hilft besonders bei Reviews, weil sie zeigt, wo Vertrauen beginnt und wo es wieder geprüft werden muss.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

3. Namespace- und Zonenmodell

Namespaces, Netzwerkzonen und Verantwortlichkeiten werden zusammen betrachtet. Ein Namespace ist kein Ersatz für eine Firewall, aber ein wichtiger Container für Berechtigungen, Quotas, NetworkPolicies und Betriebszuordnung.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

4. Datenfluss mit Outbox

Der Order Service speichert Bestellung und Outbox-Ereignis in einer Transaktion. Erst danach veröffentlicht ein Publisher das Ereignis. Dadurch werden fachliche Änderungen und technische Kommunikation entkoppelt.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

5. Security-Kette

Identity, RBAC, Secrets, NetworkPolicy, Container-Härtung und Audit Logging bilden eine Kette. Schwachstellen entstehen oft dort, wo eines dieser Glieder fehlt oder nur dokumentiert, aber nicht technisch umgesetzt ist.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

6. Betriebskette

Health Checks, strukturierte Logs, Metriken, Traces, Alerts, Dashboards und Runbooks gehören zusammen. Ein Alert ohne Runbook hilft nur begrenzt; ein Runbook ohne Messwerte bleibt theoretisch.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

7. Recovery-Kette

Backup ist nur der erste Schritt. Entscheidend sind Restore-Test, Wiederanlaufreihenfolge, Datenkonsistenz, fachliche Nachprüfung und dokumentierte Entscheidung, ob Rollback oder Rollforward sinnvoller ist.

User Gateway Service Data Logs · Metrics · Traces · Audit

Praxisbezug: In der späteren HTML-Version wird diese Grafik direkt im passenden Abschnitt eingebettet. Sie wird nicht als externer Asset-Ordner ausgelagert und nicht als separater Anhang versteckt.

8. Glossar-Erweiterung mit Querverweisen

Das Glossar wird nicht als Ersatz für Fachkapitel verwendet. Es dient als Nachschlagewerk, wenn ein Begriff im Text auftaucht und kurz eingeordnet werden soll. Die Begriffe werden mit Projektkontext formuliert, nicht als abstrakte Lexikondefinition.

Architecture Fitness Function

Eine prüfbare Regel, die sicherstellt, dass Architekturentscheidungen nicht nur beschrieben, sondern automatisch oder halbautomatisch kontrolliert werden.

Golden Path

Ein empfohlener Standardweg für Teams, um neue Services mit bewährter Struktur, Pipeline, Security und Observability anzulegen.

Platform Contract

Eine Vereinbarung zwischen Plattformteam und Anwendungsteam, welche Laufzeit-, Sicherheits-, Monitoring- und Deploymentregeln gelten.

Operational Readiness

Nachweis, dass ein Service nicht nur entwickelt, sondern auch betrieben, überwacht, gesichert und wiederhergestellt werden kann.

Blast Radius

Der mögliche Schadensbereich eines Fehlers oder Angriffs. Gute Architektur begrenzt diesen Bereich durch Zonen, Rechte und Isolation.

Control Plane

Steuerungsebene einer Plattform, zum Beispiel Kubernetes API, Argo CD oder zentrale Identity-Komponenten.

Data Plane

Ebene, auf der echte Anwendungsdaten und Netzwerkpakete fließen, zum Beispiel Service-Traffic, Datenbankzugriffe oder Broker-Nachrichten.

Progressive Delivery

Schrittweise Auslieferung über Canary, Blue-Green oder Feature Flags, damit Risiken beim Release begrenzt werden.

Policy Drift

Abweichung zwischen dokumentierten Regeln und tatsächlich aktiven Policies, zum Beispiel veraltete Firewall-Freigaben oder zu breite RBAC-Rechte.

Runbook Debt

Betriebsschuld, die entsteht, wenn Systeme ohne aktuelle Runbooks produktionsnah genutzt werden.

9. HTML-Regel für diese Erweiterung

Alle neuen SVGs müssen inline bleiben. Die Navigation verweist auf fachliche Namen, nicht auf technische Dateinamen. Die Content-Kategorie bleibt standardmäßig geschlossen, damit die mobile Darstellung nicht überladen wirkt.

10. Übung

Wähle drei der neuen Begriffe aus und ordne sie einem konkreten Artefakt im Lab zu: README, ADR, Kubernetes-Manifest, Pipeline, Monitoring-Dashboard oder Runbook. Begründe kurz, warum dieser Begriff genau dort sichtbar werden muss.

11. Musterlösung

Golden Path gehört in README und Repository-Template, weil neue Teams dadurch denselben Startpunkt erhalten. Operational Readiness gehört in Pull-Request-Checkliste und Release-Abnahme, weil ein Service sonst technisch fertig, aber betrieblich unfertig sein kann. Policy Drift gehört in Security-Review und regelmäßige Prüfung, weil dokumentierte Regeln ohne technische Kontrolle schnell veralten.

Teil J: Großer Fachglossar und Nachschlageindex

Großer Fachglossar und Nachschlageindex

Dieser Glossar wurde neu generiert und ergänzt die konsolidierte Fassung als eigener Referenzteil. Er ist kein Ersatz für die Fachkapitel, sondern ein schneller Nachschlagebereich für Architektur, Infrastruktur, OpenShift, Security, Anwendungen, Daten, Betrieb, Backup, DevOps und AI.

Leseregel: Jeder Begriff enthält Bedeutung im Lab, typischen Fehler und einen konkreten Nachweis. Dadurch bleibt der Glossar praktisch und überprüfbar.
Architektur und Zielbild · 12 Begriffe

Hybrid-Architektur

Bedeutung: bewusste Kombination aus Cloud, VM, Bare Metal und OpenShift, damit Workloads nach Anforderungen platziert werden.

Typischer Fehler: alles pauschal in Kubernetes verschieben oder alles auf VMs belassen.

Nachweis: ADR zur Workload-Platzierung und Infrastruktur-Matrix.

Zielarchitektur

Bedeutung: gemeinsames Bild von Anwendungen, Datenflüssen, Infrastrukturzonen und Betriebsverantwortung.

Typischer Fehler: Diagramm zeigt nur Kästchen, aber keine Sicherheits- oder Betriebsgrenzen.

Nachweis: Architekturdiagramm, README, ADRs.

Betriebsmodell

Bedeutung: Regeln, Rollen und Abläufe für Deployment, Monitoring, Security und Wiederherstellung.

Typischer Fehler: nur technische Installation beschreiben, aber keine Zuständigkeiten festlegen.

Nachweis: Runbooks, On-Call-Regeln, Servicekatalog.

Workload-Zuordnung

Bedeutung: Entscheidung, ob ein System in Cloud, VM, Bare Metal oder OpenShift läuft.

Typischer Fehler: Modeentscheidung ohne technische Begründung.

Nachweis: Workload-Katalog mit Kriterien.

Golden Path

Bedeutung: empfohlener Standardweg für neue Services inklusive Build, Security, Observability und Deployment.

Typischer Fehler: jedes Team baut eigene Struktur und eigene Pipeline.

Nachweis: Template-Repository und Plattformdokumentation.

Platform Contract

Bedeutung: verbindliche Vereinbarung zwischen Plattformteam und Anwendungsteam.

Typischer Fehler: Plattform stellt Dienste bereit, aber niemand kennt Pflichten und Grenzen.

Nachweis: Contract-Dokument und PR-Checkliste.

Service Ownership

Bedeutung: klare Verantwortung für Code, Deployment, Betrieb und fachliche Korrektheit.

Typischer Fehler: Betrieb, Entwicklung und Fachbereich schieben Probleme weiter.

Nachweis: README mit Owner und Eskalationsweg.

Bounded Context

Bedeutung: fachlich abgegrenzter Bereich mit eigener Sprache und eigenem Datenmodell.

Typischer Fehler: eine gemeinsame Datenbank für alle Domänen verwenden.

Nachweis: Kontextkarte und API-Verträge.

Domänenschnitt

Bedeutung: fachliche Trennung von Services nach Verantwortung statt nach technischen Schichten.

Typischer Fehler: Customer, Order und Billing in einem unklaren Service mischen.

Nachweis: Service-Schnitt und ADR.

Strangler Pattern

Bedeutung: schrittweise Ablösung eines Legacy-Systems über neue Schnittstellen.

Typischer Fehler: Legacy-System in einem Big Bang ersetzen wollen.

Nachweis: Migrationsplan und Routing-Regeln.

Architekturentscheidung

Bedeutung: dokumentierte Wahl mit Kontext, Alternativen und Konsequenzen.

Typischer Fehler: Entscheidungen nur im Chat oder Meeting treffen.

Nachweis: ADR-Datei.

Fitness Function

Bedeutung: messbare Architekturregel, die automatisiert oder regelmäßig geprüft wird.

Typischer Fehler: Architekturprinzipien formulieren, aber nie prüfen.

Nachweis: CI-Checks, Policy-as-Code, Quality Gate.

Netzwerk und Infrastruktur · 16 Begriffe

DMZ

Bedeutung: Sicherheitszone zwischen Internet und internen Systemen.

Typischer Fehler: Internetverkehr direkt zu App- oder Datenbankzone erlauben.

Nachweis: Firewall-Matrix und Netzdiagramm.

Application Zone

Bedeutung: Zone für OpenShift, Applikations-VMs und interne Services.

Typischer Fehler: Anwendungen und Datenbanken in derselben Zone vermischen.

Nachweis: Zonenkonzept.

Data Zone

Bedeutung: geschützte Zone für Datenbanken, Broker und Storage.

Typischer Fehler: Datenbanken aus der DMZ erreichbar machen.

Nachweis: Firewall-Regeln und DB-Zugriffsliste.

Management Zone

Bedeutung: Zone für Bastion, Monitoring, GitOps und Administrationszugänge.

Typischer Fehler: Adminzugriffe direkt von beliebigen Clients erlauben.

Nachweis: Bastion-Konzept und Audit Logs.

Backup Zone

Bedeutung: getrennte Zone für Backup-Repositories und Restore-Infrastruktur.

Typischer Fehler: Backups im selben Fehlerbereich wie Produktion speichern.

Nachweis: Backup-Architektur und Restore-Test.

VLAN

Bedeutung: logische Netztrennung auf Layer 2.

Typischer Fehler: VLAN als Sicherheitsersatz ohne Firewall-Regeln betrachten.

Nachweis: Netzplan und Switch-Konfiguration.

Subnetz

Bedeutung: IP-Adressbereich für eine Zone oder Umgebung.

Typischer Fehler: IP-Adressen ohne Namens- und Zonenkonzept vergeben.

Nachweis: IPAM-Liste und DNS-Konzept.

Firewall-Matrix

Bedeutung: fachliche Dokumentation erlaubter Verbindungen.

Typischer Fehler: breite Regeln wie any-to-any erlauben.

Nachweis: Genehmigte Matrix und Review-Datum.

Load Balancer

Bedeutung: Komponente zur Verteilung und Verfügbarkeit von Traffic.

Typischer Fehler: Load Balancer, Route und Service verwechseln.

Nachweis: Traffic-Diagramm und Health Checks.

Reverse Proxy

Bedeutung: Einstiegspunkt, der externe Requests an interne Dienste weiterleitet.

Typischer Fehler: Proxy als Sicherheitsgrenze ohne Authentifizierung betrachten.

Nachweis: Proxy-Konfiguration und TLS-Nachweis.

Bastion Host

Bedeutung: kontrollierter Einstiegspunkt für Administration.

Typischer Fehler: SSH auf alle Server direkt freigeben.

Nachweis: Bastion-Log und Zugriffskonzept.

DNS-Zone

Bedeutung: Namensraum für interne oder externe Hostnamen.

Typischer Fehler: interne Datenbanknamen öffentlich auflösen lassen.

Nachweis: DNS-Dokumentation und Split-Horizon-Konzept.

NTP/Chrony

Bedeutung: Zeitsynchronisierung für Logs, Tokens, Zertifikate und Audits.

Typischer Fehler: unterschiedliche Serverzeiten ignorieren.

Nachweis: Zeitquellen-Konfiguration.

TLS-Zertifikat

Bedeutung: Nachweis und Verschlüsselung für einen Hostnamen.

Typischer Fehler: Ablaufdatum nicht überwachen.

Nachweis: Zertifikatsinventar und Alert.

Bare Metal

Bedeutung: physische Server ohne Virtualisierungsschicht.

Typischer Fehler: Bare Metal als automatisch besser betrachten.

Nachweis: Workload-Begründung und Betriebsplan.

Virtuelle Maschine

Bedeutung: isolierte Serverlaufzeit für Legacy, Tools oder Übergangssysteme.

Typischer Fehler: VMs manuell konfigurieren und nicht dokumentieren.

Nachweis: Ansible-Playbook und CMDB-Eintrag.

Cloud und Plattformdienste · 14 Begriffe

VPC

Bedeutung: isoliertes virtuelles Netzwerk in der Cloud.

Typischer Fehler: Cloud-Netz ohne Subnetze und Routingregeln erstellen.

Nachweis: Terraform-Modul und Netzdiagramm.

VNet

Bedeutung: Azure-Variante eines virtuellen Netzwerks.

Typischer Fehler: AWS- und Azure-Begriffe vermischen, ohne Konzept zu erklären.

Nachweis: Cloud-Netzwerkdokumentation.

Public Subnet

Bedeutung: Subnetz mit kontrolliertem externem Zugang.

Typischer Fehler: Workloads mit Daten direkt öffentlich platzieren.

Nachweis: Routingtabelle und Security Group.

Private Subnet

Bedeutung: Subnetz ohne direkten Internetzugang.

Typischer Fehler: private Systeme trotzdem über öffentliche IPs ansprechen.

Nachweis: Cloud-Subnetzplan.

Security Group

Bedeutung: Cloud-Firewall nahe an Instanzen oder Services.

Typischer Fehler: zu breite Regeln dauerhaft erlauben.

Nachweis: IaC-Regeln und Review.

IAM

Bedeutung: Identitäts- und Berechtigungsmodell der Cloud.

Typischer Fehler: Root- oder Adminrechte in Pipelines verwenden.

Nachweis: IAM-Policy und Least-Privilege-Review.

Object Storage

Bedeutung: S3-kompatible Ablage für Dateien, Exporte und Backups.

Typischer Fehler: Objektspeicher wie ein POSIX-Dateisystem benutzen.

Nachweis: Bucket-Policy und Lifecycle-Regel.

Lifecycle Policy

Bedeutung: Regel zum Verschieben oder Löschen alter Objekte.

Typischer Fehler: Daten unbegrenzt speichern und Kosten ignorieren.

Nachweis: Bucket-Konfiguration.

Managed Database

Bedeutung: vom Cloud-Anbieter betriebene Datenbank.

Typischer Fehler: Betriebsverantwortung vollständig abgeben und Restore nicht testen.

Nachweis: Service-ADR und Restore-Nachweis.

Transit Gateway

Bedeutung: zentrale Kopplung mehrerer Netze.

Typischer Fehler: spaghettiartige Peerings ohne Governance bauen.

Nachweis: Netzwerk-Hub-Dokumentation.

VPN

Bedeutung: verschlüsselte Verbindung zwischen Netzen.

Typischer Fehler: VPN als Ersatz für saubere Firewall-Regeln behandeln.

Nachweis: VPN-Konfiguration und Routenprüfung.

Direct Connect/ExpressRoute

Bedeutung: dedizierte Verbindung zur Cloud.

Typischer Fehler: ohne Fallback- und DR-Konzept planen.

Nachweis: Connectivity-ADR.

Kostenstelle

Bedeutung: Zuordnung von Cloud-Kosten zu Teams oder Services.

Typischer Fehler: Ressourcen ohne Tags erzeugen.

Nachweis: Tagging-Policy und Kostenbericht.

Budget Alert

Bedeutung: Warnung bei ungewöhnlichen Kosten.

Typischer Fehler: erst am Monatsende auf Kosten reagieren.

Nachweis: Cloud-Budget und Alert-Regel.

Kubernetes und OpenShift · 25 Begriffe

Namespace

Bedeutung: logische Grenze für Ressourcen, Teams oder Umgebungen.

Typischer Fehler: alle Workloads in einem Namespace sammeln.

Nachweis: Namespace-Konzept und Labels.

Pod

Bedeutung: kleinste ausführbare Einheit im Cluster.

Typischer Fehler: Pod als dauerhafte Maschine betrachten.

Nachweis: Deployment-Manifest.

Deployment

Bedeutung: gewünschter Zustand für replizierte Pods.

Typischer Fehler: Pods manuell erzeugen.

Nachweis: Deployment-YAML und Rollout-Historie.

Service

Bedeutung: stabiler interner Zugriffspunkt auf Pods.

Typischer Fehler: Pod-IP direkt verwenden.

Nachweis: Service-Manifest.

Route

Bedeutung: OpenShift-Objekt für externen HTTP/S-Zugriff.

Typischer Fehler: Route ohne TLS- und Auth-Konzept veröffentlichen.

Nachweis: Route-Manifest und Zertifikat.

Ingress

Bedeutung: Kubernetes-Objekt für externen HTTP/S-Zugriff.

Typischer Fehler: Ingress-Regeln ohne zentrale Gateway-Strategie verteilen.

Nachweis: Ingress- oder Gateway-ADR.

ConfigMap

Bedeutung: nicht geheime Konfiguration für Anwendungen.

Typischer Fehler: Passwörter in ConfigMaps speichern.

Nachweis: Manifest-Review.

Secret

Bedeutung: Kubernetes-Objekt für sensible Werte.

Typischer Fehler: Secrets im Git unverschlüsselt speichern.

Nachweis: Secret-Management-Konzept.

ExternalSecret

Bedeutung: Abbildung externer Secret-Stores ins Cluster.

Typischer Fehler: Rotation nicht testen.

Nachweis: ExternalSecret-Manifest und Vault-Policy.

ServiceAccount

Bedeutung: technische Identität eines Pods.

Typischer Fehler: Default-ServiceAccount für alles verwenden.

Nachweis: ServiceAccount und RBAC-Manifest.

RBAC

Bedeutung: rollenbasierte technische Berechtigung im Cluster.

Typischer Fehler: fachliche Rollen und Kubernetes-Rollen verwechseln.

Nachweis: Role/RoleBinding-Review.

SecurityContext

Bedeutung: Sicherheitsoptionen für Container und Pod.

Typischer Fehler: Container als root laufen lassen.

Nachweis: SecurityContext im Deployment.

SCC

Bedeutung: OpenShift Security Context Constraint.

Typischer Fehler: privileged SCC ohne Begründung vergeben.

Nachweis: SCC-Zuordnung und Ausnahme-ADR.

NetworkPolicy

Bedeutung: Netzwerkregel zwischen Pods und Namespaces.

Typischer Fehler: alle Pods frei sprechen lassen.

Nachweis: Policy-Manifest und Verbindungstest.

PersistentVolume

Bedeutung: dauerhafte Speicherressource im Cluster.

Typischer Fehler: Daten im Container-Dateisystem speichern.

Nachweis: PV/PVC-Manifest.

PVC

Bedeutung: Anforderung einer Anwendung an persistenten Speicher.

Typischer Fehler: Storage-Klasse ohne Backup-Fähigkeit wählen.

Nachweis: PVC und StorageClass-ADR.

Job

Bedeutung: einmaliger Kubernetes-Task.

Typischer Fehler: lange laufende Services als Job bauen.

Nachweis: Job-Manifest und Logs.

CronJob

Bedeutung: zeitgesteuerter Kubernetes-Task.

Typischer Fehler: CronJobs ohne idempotente Logik verwenden.

Nachweis: CronJob-Manifest und Runbook.

Resource Requests

Bedeutung: garantierte Mindestressourcen für Scheduling.

Typischer Fehler: keine Requests setzen und Scheduling dem Zufall überlassen.

Nachweis: Deployment-Manifest.

Resource Limits

Bedeutung: Obergrenze für Ressourcenverbrauch.

Typischer Fehler: Limits zu niedrig setzen und OOMKills erzeugen.

Nachweis: Lasttest und Manifest.

Liveness Probe

Bedeutung: Prüfung, ob ein Container neu gestartet werden soll.

Typischer Fehler: Liveness für abhängige Datenbankfehler missbrauchen.

Nachweis: Probe-Konfiguration.

Readiness Probe

Bedeutung: Prüfung, ob ein Pod Traffic erhalten darf.

Typischer Fehler: Pod zu früh trafficfähig machen.

Nachweis: Readiness-Endpunkt.

Startup Probe

Bedeutung: Startschutz für langsam startende Anwendungen.

Typischer Fehler: lange Startzeit mit hoher Liveness-Toleranz kaschieren.

Nachweis: Startup-Probe-Manifest.

PodDisruptionBudget

Bedeutung: Mindestverfügbarkeit bei geplanten Unterbrechungen.

Typischer Fehler: Wartung ohne Verfügbarkeitsregeln planen.

Nachweis: PDB-Manifest.

HPA

Bedeutung: automatische Skalierung anhand Metriken.

Typischer Fehler: Skalierung ohne Lasttest aktivieren.

Nachweis: HPA-Manifest und k6-Bericht.

Identity, Security und Governance · 18 Begriffe

Keycloak

Bedeutung: Identity Provider für OIDC, Rollen und Clients.

Typischer Fehler: Login-Server ohne Realm- und Client-Konzept betreiben.

Nachweis: Keycloak-Export und Realm-Dokumentation.

OIDC

Bedeutung: Protokoll für moderne Authentifizierung.

Typischer Fehler: Access Token als Session-Ersatz missbrauchen.

Nachweis: Client-Konfiguration und Token-Claims.

OAuth2

Bedeutung: Autorisierungsframework für delegierten Zugriff.

Typischer Fehler: OAuth2 als Login-Protokoll erklären.

Nachweis: Security-ADR.

JWT

Bedeutung: signierter Token mit Claims.

Typischer Fehler: Token ungeprüft vertrauen.

Nachweis: Resource-Server-Konfiguration.

Realm

Bedeutung: logischer Mandant in Keycloak.

Typischer Fehler: alle Umgebungen in einem Realm mischen.

Nachweis: Realm-Strategie.

Client

Bedeutung: Anwendung oder API in Keycloak.

Typischer Fehler: Redirect-URIs zu breit konfigurieren.

Nachweis: Client-Konfiguration.

Claim

Bedeutung: Attribut im Token.

Typischer Fehler: fachliche Berechtigung hart aus Benutzernamen ableiten.

Nachweis: Token-Mapping.

Fachliche Rolle

Bedeutung: Business-Rolle wie ADMIN oder BILLING_APPROVER.

Typischer Fehler: fachliche Rollen mit Cluster-RBAC vermischen.

Nachweis: Rollenmatrix.

Least Privilege

Bedeutung: nur notwendige Rechte vergeben.

Typischer Fehler: Adminrechte als Standard verwenden.

Nachweis: Berechtigungsreview.

Zero Trust

Bedeutung: kein Zugriff ohne explizite Identität und Regel.

Typischer Fehler: internes Netz automatisch vertrauen.

Nachweis: NetworkPolicy, RBAC, Audit.

Audit Log

Bedeutung: nachvollziehbare Aufzeichnung sicherheitsrelevanter Aktionen.

Typischer Fehler: nur technische Logs ohne fachlichen Kontext speichern.

Nachweis: Audit-Event-Schema.

Secret Rotation

Bedeutung: regelmäßiger Austausch sensibler Werte.

Typischer Fehler: Passwortwechsel ohne Restart-/Reload-Plan durchführen.

Nachweis: Rotation-Runbook.

Vault

Bedeutung: zentraler Secret-Store.

Typischer Fehler: Vault einführen, aber Apps trotzdem mit statischen Secrets betreiben.

Nachweis: Vault-Policy und ExternalSecret.

Policy as Code

Bedeutung: prüfbare Regeln für Infrastruktur und Deployments.

Typischer Fehler: Regeln nur in Wiki-Text formulieren.

Nachweis: OPA/Kyverno/Conftest.

Container Image Scan

Bedeutung: Prüfung von Images auf Schwachstellen.

Typischer Fehler: Scan-Warnungen ignorieren oder pauschal akzeptieren.

Nachweis: Scan-Bericht und Ausnahmeprozess.

SBOM

Bedeutung: Software Bill of Materials für Abhängigkeiten.

Typischer Fehler: nicht wissen, welche Bibliotheken im Image sind.

Nachweis: SBOM-Artefakt in CI.

Supply Chain Security

Bedeutung: Schutz der Build- und Lieferkette.

Typischer Fehler: Build-Skripte aus unklaren Quellen ausführen.

Nachweis: Signierte Images und Pipeline-Policy.

Change Governance

Bedeutung: Regelwerk für kontrollierte Änderungen.

Typischer Fehler: GitOps umgehen und manuell patchen.

Nachweis: PR, Approval und Argo-CD-Historie.

Anwendungen und Entwicklung · 18 Begriffe

Spring Boot

Bedeutung: Java-Framework für moderne Services.

Typischer Fehler: alles in Controller-Klassen schreiben.

Nachweis: Schichtenstruktur und Tests.

Quarkus

Bedeutung: Java-Framework mit Cloud-native-Fokus.

Typischer Fehler: Frameworkwahl ohne Laufzeit- und Teamkriterien treffen.

Nachweis: Framework-ADR.

REST API

Bedeutung: ressourcenorientierte HTTP-Schnittstelle.

Typischer Fehler: fachliche Aktionen unsauber als generische Endpunkte bauen.

Nachweis: OpenAPI-Datei.

OpenAPI

Bedeutung: maschinenlesbare API-Beschreibung.

Typischer Fehler: API ändern ohne Vertrag anzupassen.

Nachweis: openapi.yaml und Contract-Test.

DTO

Bedeutung: Transportobjekt für API-Daten.

Typischer Fehler: JPA-Entities direkt nach außen geben.

Nachweis: API-Modell und Mapper.

Use Case

Bedeutung: Anwendungsfall mit Transaktions- und Geschäftslogik.

Typischer Fehler: Businesslogik in Controller oder Repository verteilen.

Nachweis: Application-Service-Klasse.

Domain Model

Bedeutung: fachliches Modell mit Regeln und Begriffen.

Typischer Fehler: Datenbankstruktur direkt als Domäne übernehmen.

Nachweis: Domain-Paket und Tests.

Repository

Bedeutung: Abstraktion für Persistenzzugriff.

Typischer Fehler: SQL/JPA-Abhängigkeiten in Domänenlogik mischen.

Nachweis: Repository-Interface und Adapter.

Controller

Bedeutung: HTTP-Eingang einer Anwendung.

Typischer Fehler: Controller für Geschäftsentscheidung missbrauchen.

Nachweis: Controller-Test.

Mapper

Bedeutung: Übersetzung zwischen API, Domain und Persistenz.

Typischer Fehler: Mapping über verstreute manuelle Kopien durchführen.

Nachweis: Mapper-Klasse und Tests.

Validation

Bedeutung: Prüfung von Eingaben und Regeln.

Typischer Fehler: nur Frontend validiert, Backend vertraut blind.

Nachweis: Bean Validation und Fachtests.

Exception Mapping

Bedeutung: Übersetzung technischer Fehler in API-Antworten.

Typischer Fehler: Stacktraces an Clients zurückgeben.

Nachweis: ControllerAdvice oder ErrorHandler.

Idempotenz

Bedeutung: mehrfache Ausführung ohne falsche Nebenwirkung.

Typischer Fehler: Events doppelt verarbeiten und Rechnungen doppelt erzeugen.

Nachweis: Idempotency-Key oder Processed-Event-Tabelle.

Frontend API Client

Bedeutung: zentraler Zugriffspunkt für Backend-Aufrufe.

Typischer Fehler: Fetch-Aufrufe überall im UI verteilen.

Nachweis: api-client.ts und Fehlerbehandlung.

OIDC Frontend Flow

Bedeutung: Login und Token-Nutzung im Browser.

Typischer Fehler: Secrets im Frontend speichern.

Nachweis: OIDC-Konfiguration ohne Client Secret.

Dockerfile

Bedeutung: Bauanweisung für Container Images.

Typischer Fehler: Root-Image mit unnötigen Tools verwenden.

Nachweis: Dockerfile-Review.

Multi-Stage Build

Bedeutung: getrennter Build- und Runtime-Container.

Typischer Fehler: Build-Werkzeuge ins Runtime-Image übernehmen.

Nachweis: Dockerfile.

Testcontainers

Bedeutung: Integrationstests mit echten Abhängigkeiten in Containern.

Typischer Fehler: nur Mocks testen und Datenbankfehler übersehen.

Nachweis: Integrationstest.

Daten, Messaging und Integration · 18 Begriffe

PostgreSQL

Bedeutung: relationale Datenbank für Order und Billing.

Typischer Fehler: mehrere Services schreiben in dieselben Tabellen.

Nachweis: Schema-Ownership und Migrationen.

Flyway

Bedeutung: versionierte Datenbankmigration.

Typischer Fehler: manuelle DB-Änderungen in Produktion durchführen.

Nachweis: Migration-Dateien.

Liquibase

Bedeutung: alternatives Migrationswerkzeug.

Typischer Fehler: Migrationen ohne Review ausrollen.

Nachweis: ChangeLog und Pipeline.

Transaktion

Bedeutung: atomare Einheit von Änderungen.

Typischer Fehler: Datenbankänderung und Event getrennt ohne Absicherung ausführen.

Nachweis: Transactional Use Case.

Outbox Pattern

Bedeutung: Event wird in derselben DB-Transaktion gespeichert.

Typischer Fehler: Event direkt nach DB-Save senden und bei Brokerfehler verlieren.

Nachweis: Outbox-Tabelle und Publisher.

Kafka

Bedeutung: verteilter Event-Log für Streaming und Events.

Typischer Fehler: Kafka als synchrone Request/Response-API verwenden.

Nachweis: Topic-Konzept.

RabbitMQ

Bedeutung: Message Broker für Queues und Routing.

Typischer Fehler: Queues ohne DLQ und Retry-Plan betreiben.

Nachweis: Exchange/Queue-Konfiguration.

Topic

Bedeutung: Kafka-Kanal für Events.

Typischer Fehler: alle Events in ein generisches Topic schreiben.

Nachweis: Topic-Namenskonvention.

Consumer Group

Bedeutung: Gruppe von Kafka-Consumern mit Lastverteilung.

Typischer Fehler: Consumer-Gruppen unbewusst ändern und Events neu lesen.

Nachweis: Consumer-Konfiguration.

Dead Letter Queue

Bedeutung: Ablage für nicht verarbeitbare Nachrichten.

Typischer Fehler: fehlerhafte Nachrichten endlos retryen.

Nachweis: DLQ-Runbook.

Retry

Bedeutung: erneuter Verarbeitungsversuch nach temporärem Fehler.

Typischer Fehler: Retry-Storm erzeugen.

Nachweis: Retry-Policy.

Backoff

Bedeutung: Wartezeit zwischen Retries.

Typischer Fehler: sofortige Wiederholungen gegen überlastetes Ziel senden.

Nachweis: Retry-Konfiguration.

Event Schema

Bedeutung: vertragliche Struktur eines Events.

Typischer Fehler: Payload ohne Versionierung ändern.

Nachweis: Schema-Dokumentation.

Schema Evolution

Bedeutung: kontrollierte Weiterentwicklung von Nachrichten.

Typischer Fehler: Breaking Change ohne Consumer-Plan ausrollen.

Nachweis: Kompatibilitätsprüfung.

CDC

Bedeutung: Change Data Capture aus Datenbankänderungen.

Typischer Fehler: CDC als Ersatz für fachliche Events missverstehen.

Nachweis: CDC-ADR.

Anti-Corruption Layer

Bedeutung: Schutz vor Legacy-Modellen.

Typischer Fehler: Legacy-Felder direkt in neue Domäne übernehmen.

Nachweis: Adapter-Schicht.

Object Storage Bucket

Bedeutung: Ablagebereich für Dateien oder Exporte.

Typischer Fehler: Dateinamen ohne Klassifizierung und Lifecycle verwenden.

Nachweis: Bucket-Policy.

Datenklassifizierung

Bedeutung: Einstufung nach Schutzbedarf.

Typischer Fehler: Produktionsdaten in Testumgebungen kopieren.

Nachweis: Data-Classification-Label.

Observability und Betrieb · 18 Begriffe

Observability

Bedeutung: Fähigkeit, Systemzustand aus Signalen zu verstehen.

Typischer Fehler: nur CPU-Metriken sammeln.

Nachweis: Logs, Metriken, Traces, Dashboards.

Monitoring

Bedeutung: laufende Überwachung definierter Zustände.

Typischer Fehler: Monitoring mit Observability gleichsetzen.

Nachweis: Alert-Regeln.

Log

Bedeutung: textliches Ereignis einer Anwendung.

Typischer Fehler: unstrukturierte Logs ohne Trace-ID erzeugen.

Nachweis: JSON-Log-Konfiguration.

Metrik

Bedeutung: numerischer Messwert über Zeit.

Typischer Fehler: nur technische Metriken ohne fachliche Metriken erfassen.

Nachweis: Prometheus-Metriken.

Trace

Bedeutung: zusammenhängender Ablauf über Services.

Typischer Fehler: Trace-ID nicht über Servicegrenzen weitergeben.

Nachweis: OpenTelemetry-Konfiguration.

Correlation ID

Bedeutung: Kennung zur Verbindung von Logs und Requests.

Typischer Fehler: pro Service neue IDs erzeugen.

Nachweis: HTTP-Header und Logfeld.

Prometheus

Bedeutung: Metrik-Sammlung und Abfrage.

Typischer Fehler: zu viele hochkardinale Labels erzeugen.

Nachweis: ServiceMonitor und Regeln.

Grafana

Bedeutung: Visualisierung von Metriken und Logs.

Typischer Fehler: Dashboard ohne konkrete SLO-Fragen bauen.

Nachweis: Dashboard JSON.

Loki

Bedeutung: Log-Aggregation.

Typischer Fehler: Logs als Ersatz für Metriken verwenden.

Nachweis: Promtail/Loki-Konfiguration.

Tempo

Bedeutung: Tracing Backend.

Typischer Fehler: Traces sammeln, aber nicht mit Logs verbinden.

Nachweis: OTel Pipeline.

ServiceMonitor

Bedeutung: OpenShift/Prometheus-Objekt zum Scrapen.

Typischer Fehler: Metriken-Endpunkt nicht freigeben.

Nachweis: ServiceMonitor-Manifest.

PrometheusRule

Bedeutung: Alert- und Recording-Regeln.

Typischer Fehler: Alerts ohne Runbook auslösen.

Nachweis: PrometheusRule und Runbook-Link.

SLI

Bedeutung: messbarer Service Level Indicator.

Typischer Fehler: Verfügbarkeit nur subjektiv bewerten.

Nachweis: SLI-Definition.

SLO

Bedeutung: Zielwert für Servicequalität.

Typischer Fehler: 100 Prozent als Standard versprechen.

Nachweis: SLO-Dokument.

Error Budget

Bedeutung: erlaubter Fehleranteil innerhalb eines Zeitraums.

Typischer Fehler: jede Störung als gleich kritisch behandeln.

Nachweis: SLO-Bericht.

Incident

Bedeutung: ungeplante Störung mit Auswirkung.

Typischer Fehler: nur Symptom beheben und Ursache ignorieren.

Nachweis: Incident-Ticket und Timeline.

Postmortem

Bedeutung: nachträgliche Analyse einer Störung.

Typischer Fehler: Schuldige suchen statt Systemverbesserung.

Nachweis: Postmortem-Dokument.

Runbook

Bedeutung: konkrete Anleitung für Betriebssituationen.

Typischer Fehler: nur allgemeine Hinweise ohne Befehle notieren.

Nachweis: Runbook-Datei.

Backup, Restore und Resilienz · 16 Begriffe

Backup

Bedeutung: Sicherung von Daten oder Konfiguration.

Typischer Fehler: Backup für Restore halten.

Nachweis: Backup-Job und Prüfsumme.

Restore

Bedeutung: Wiederherstellung aus Backup.

Typischer Fehler: Restore nie testen.

Nachweis: Restore-Protokoll.

Disaster Recovery

Bedeutung: Wiederanlauf nach großem Ausfall.

Typischer Fehler: nur einzelne Systeme sichern, aber Reihenfolge vergessen.

Nachweis: DR-Runbook.

RPO

Bedeutung: maximal akzeptierter Datenverlust.

Typischer Fehler: RPO nicht fachlich klären.

Nachweis: RPO-Definition pro System.

RTO

Bedeutung: maximal akzeptierte Wiederanlaufzeit.

Typischer Fehler: RTO ohne technische Tests versprechen.

Nachweis: DR-Testbericht.

WAL Archivierung

Bedeutung: PostgreSQL-Mechanismus für Point-in-Time-Recovery.

Typischer Fehler: WALs nicht sichern und PITR erwarten.

Nachweis: PostgreSQL-Konfiguration.

Point-in-Time-Recovery

Bedeutung: Wiederherstellung auf einen Zeitpunkt.

Typischer Fehler: Zeitpunkt ohne Fachprüfung wählen.

Nachweis: Restore-Entscheidung.

VolumeSnapshot

Bedeutung: Snapshot eines persistenten Volumes.

Typischer Fehler: Snapshot als vollständiges Applikationsbackup betrachten.

Nachweis: Snapshot und App-Konsistenzprüfung.

Chaos Übung

Bedeutung: kontrollierter Ausfalltest.

Typischer Fehler: Chaos ohne Abbruchkriterien durchführen.

Nachweis: Übungsplan und Ergebnis.

Timeout

Bedeutung: maximale Wartezeit für Aufrufe.

Typischer Fehler: unendliche Requests erlauben.

Nachweis: Client-Konfiguration.

Circuit Breaker

Bedeutung: Schutz vor wiederholten Aufrufen defekter Abhängigkeiten.

Typischer Fehler: Breaker als Fehlerversteck verwenden.

Nachweis: Resilience-Konfiguration.

Bulkhead

Bedeutung: Isolation von Ressourcenbereichen.

Typischer Fehler: ein Threadpool für alles verwenden.

Nachweis: Threadpool- und Pool-Konzept.

Backpressure

Bedeutung: kontrollierte Reaktion auf Überlast.

Typischer Fehler: mehr Arbeit annehmen als verarbeitet werden kann.

Nachweis: Queue- und Rate-Limit-Metriken.

Rate Limiting

Bedeutung: Begrenzung von Requests.

Typischer Fehler: alle Clients unbegrenzt senden lassen.

Nachweis: Gateway-Regel.

Lasttest

Bedeutung: geplante Belastungsprüfung.

Typischer Fehler: erst in Produktion Lastverhalten lernen.

Nachweis: k6-Bericht.

Kapazitätsplanung

Bedeutung: Abschätzung von Ressourcenbedarf.

Typischer Fehler: Requests und Limits raten.

Nachweis: Lasttest und Sizing-Dokument.

DevOps, GitOps und AI · 16 Begriffe

CI

Bedeutung: automatisierte Prüfung und Build nach Codeänderung.

Typischer Fehler: Build lokal machen und Artefakte manuell hochladen.

Nachweis: Pipeline-Konfiguration.

CD

Bedeutung: automatisierte Auslieferung oder Bereitstellung.

Typischer Fehler: Deployment ohne Freigaberegeln automatisieren.

Nachweis: Release-Prozess.

GitOps

Bedeutung: Clusterzustand aus Git ableiten.

Typischer Fehler: manuelle Clusteränderungen dauerhaft behalten.

Nachweis: Argo CD Application.

Argo CD

Bedeutung: GitOps-Controller für Kubernetes/OpenShift.

Typischer Fehler: falsches Git-Repo oder Overlay synchronisieren.

Nachweis: Argo-CD-App und Sync-History.

Kustomize

Bedeutung: Overlay-basierte Kubernetes-Konfiguration.

Typischer Fehler: YAML für jede Umgebung kopieren.

Nachweis: base/overlays-Struktur.

Helm

Bedeutung: Templating und Paketierung für Kubernetes.

Typischer Fehler: Helm-Charts ohne nachvollziehbare Werte betreiben.

Nachweis: Chart und values.yaml.

Environment Promotion

Bedeutung: kontrollierter Weg von dev nach test nach prod.

Typischer Fehler: Image in Prod anders bauen als in Test.

Nachweis: Image Tag und Overlay-PR.

Rollback

Bedeutung: Rückkehr auf vorherigen Zustand.

Typischer Fehler: Rollback ohne Datenbankkompatibilität durchführen.

Nachweis: Rollback-Runbook.

Rollforward

Bedeutung: Fehler durch neue korrigierende Version beheben.

Typischer Fehler: bei Datenänderungen blind zurückrollen.

Nachweis: Hotfix-Plan.

Feature Flag

Bedeutung: Funktion getrennt vom Deployment aktivieren.

Typischer Fehler: Flags nie entfernen.

Nachweis: Flag-Inventar.

Release Notes

Bedeutung: Beschreibung der Änderung und Auswirkungen.

Typischer Fehler: Release ohne Betriebs- und Fachhinweise.

Nachweis: CHANGELOG und Release-Dokument.

Codex

Bedeutung: AI-Unterstützung für Code, Tests und konkrete Dateien.

Typischer Fehler: AI-Code ungeprüft übernehmen.

Nachweis: PR-Review und Tests.

Claude Code

Bedeutung: AI-Unterstützung für größere Refactorings und Projektkontext.

Typischer Fehler: AI ohne klare Grenzen auf Repo loslassen.

Nachweis: Arbeitsplan und Diff-Review.

AI-Fülltext

Bedeutung: plausibler, aber austauschbarer Inhalt.

Typischer Fehler: Umfang statt Qualität erzeugen.

Nachweis: Wiederholungsprüfung.

Prompt-Regel

Bedeutung: verbindliche Vorgabe für AI-Ausgaben.

Typischer Fehler: Prompt ohne Qualitätsgrenzen verwenden.

Nachweis: Projektregel und Review.

Diff Review

Bedeutung: Prüfung konkreter Änderungen.

Typischer Fehler: nur Zusammenfassung lesen.

Nachweis: Pull Request Diff.

Anhang K: Build-, Abnahme- und Pflegeprozess

Anhang K: Build-, Abnahme- und Pflegeprozess

PlanZielbildBuildUmsetzungRunBetriebAnhang K: Build-, Abnahme- und Pflegeprozess: Architektur, Umsetzung und Betrieb im Zusammenhang

Dieser Anhang enthält technische Regeln, Prüfprozesse und Delivery-Hinweise. Er ist bewusst nicht Teil des fachlichen Hauptlernwegs.

Qualitätsprüfung, Konsolidierung und HTML-Vorbereitung

1. Warum dieser Teil notwendig ist

Nachdem die fachlichen Teile 1 bis 15 entstanden sind, beginnt eine andere Art von Arbeit. Jetzt geht es nicht darum, noch mehr Text zu erzeugen. Es geht darum, den vorhandenen Inhalt gegen die Fehler zu schützen, die in den vorherigen HTML-Fassungen sichtbar wurden: wiederholte Absätze, künstliche Tabellen, generische Überschriften, angehängte Praxisblöcke und Navigationseinträge, die nicht fachlich benannt waren.

Dieser Teil ist deshalb kein normales Fachkapitel, sondern eine Qualitätsstufe. Er beschreibt, wie das Lernprojekt vor der HTML-Erzeugung konsolidiert wird. Die spätere Single-File-HTML darf nicht einfach den Markdown-Inhalt blind in eine Oberfläche kopieren. Sie muss die Struktur erhalten, Navigation sauber abbilden, Code lesbar darstellen und dabei verhindern, dass technische Verpackung den Inhalt wieder verschlechtert.

Die wichtigste Regel lautet:

Vorlage / Textauszug

Qualität entsteht nicht durch Dateigröße.

Qualität entsteht durch fachlich unterschiedliche Kapitel, klare Struktur, konkrete Beispiele und prüfbare Artefakte.

Eine HTML-Datei über 2 MB ist nur sinnvoll, wenn sie durch echten Inhalt groß wird: Erklärungen, Szenarien, Code, Runbooks, Übungen, Musterlösungen und Diagramme. Sie ist nicht sinnvoll, wenn sie durch wiederholte Sätze, künstliche Fülltabellen oder leere Referenzkarten wächst.

---

2. Die bisherigen Fehler als Prüfkriterien

Aus den früheren Versuchen ergeben sich klare Negativregeln. Diese Regeln werden vor der HTML-Erzeugung geprüft.

Nicht erlaubt sind Überschriften wie:

Vorlage / Textauszug

Integrierte Vertiefung 3

Praxisdurchlauf 7

Anhang 16

Referenzkarte 22

Entscheidung 2

Risiko 3

Solche Titel sagen nichts über den Inhalt aus. Sie zeigen, dass ein Generator nummerierte Blöcke erzeugt hat, aber kein Fachbuch geschrieben wurde.

Nicht erlaubt sind Absätze, die nur einen Begriff austauschen, aber sonst identisch bleiben. Ein Beispiel für ein schlechtes Muster wäre:

Vorlage / Textauszug

Bei Security beginnt die Arbeit mit einer konkreten Fragestellung.

Der Schwerpunkt RBAC wird dabei mit realen Artefakten verbunden.

...

Bei Security beginnt die Arbeit mit einer konkreten Fragestellung.

Der Schwerpunkt Hardening wird dabei mit realen Artefakten verbunden.

...

Solche Wiederholungen wirken beim ersten Überfliegen vielleicht umfangreich, aber beim Lesen merkt man sofort, dass kein echter Lernwert entsteht.

Nicht erlaubt sind Tabellen, die nur scheinbar fachliche Inhalte tragen:

Skizze / Textauszug
Entscheidung 2 | Warum das wichtig ist | Risiko 2 | Nachweis
Entscheidung 3 | Warum das wichtig ist | Risiko 3 | Nachweis

Eine Tabelle ist nur dann sinnvoll, wenn jede Zeile einen eigenen fachlichen Zweck hat.

---

3. Positivregeln für jedes Kapitel

Jedes Kapitel muss einen eigenen Beitrag leisten. Ein Kapitel gilt nur dann als ausreichend, wenn es mindestens diese Elemente enthält:

Vorlage / Textauszug
  1. Ziel des Kapitels
  2. fachliche Erklärung
  3. Enterprise-Szenario
  4. technische Umsetzung
  5. Code- oder Konfigurationsbeispiel
  6. typischer Fehler
  7. Betriebs- oder Security-Bezug
  8. Übung oder Kontrollfrage
  9. Musterlösung oder Beispielantwort

Nicht jedes Kapitel muss gleich lang sein. Aber jedes Kapitel muss inhaltlich unterscheidbar sein.

Ein Kapitel über NetworkPolicy darf nicht wie ein Kapitel über RBAC klingen. NetworkPolicy behandelt Netzwerkkommunikation zwischen Pods und Namespaces. RBAC behandelt Benutzer, Gruppen, Rollen und Berechtigungen auf API-Ebene. Beide gehören zu Security, aber sie lösen völlig verschiedene Probleme.

Ein Kapitel über Backup darf nicht wie ein Kapitel über Monitoring klingen. Backup beantwortet die Frage, wie Daten wiederhergestellt werden. Monitoring beantwortet die Frage, wie Probleme sichtbar werden. Beide gehören zum Betrieb, aber ihre Artefakte, Fehlerbilder und Prüfmethoden sind unterschiedlich.

---

4. Konsolidierungsmodell

Die vorhandenen Markdown-Dateien werden nicht als lose Sammlung behandelt. Sie werden zu einem Buch zusammengeführt.

Die Konsolidierung folgt dieser Reihenfolge:

Vorlage / Textauszug
  1. Dateien sortieren
  2. Kapitelüberschriften normalisieren
  3. doppelte oder ähnliche Absätze erkennen
  4. generische Titel entfernen
  5. Praxisabschnitte in Fachkapitel integrieren
  6. Begriffserklärungen im Kontext belassen
  7. Codeblöcke mit Sprache kennzeichnen
  8. Mermaid-Diagramme oder SVG-Platzhalter markieren
  9. interne Links vorbereiten
  10. HTML-Struktur erzeugen

Wichtig ist: Konsolidierung bedeutet nicht Kürzung um jeden Preis. Manche Wiederholungen sind pädagogisch sinnvoll, wenn sie an anderer Stelle mit neuer Bedeutung auftreten. Ein Begriff wie GitOps darf in mehreren Kapiteln vorkommen, aber nicht mit demselben Absatz. In Teil 5 wird GitOps aus OpenShift-Sicht erklärt. In Teil 11 wird GitOps aus Release-Management-Sicht erklärt. Das ist erlaubt.

---

5. Qualitätsprüfung gegen Wiederholungen

Eine einfache Wiederholungsprüfung kann nach Satzähnlichkeit suchen. Dabei werden Absätze normalisiert: Groß-/Kleinschreibung, Sonderzeichen und einzelne Fachbegriffe werden reduziert. Danach wird geprüft, ob mehrere Absätze fast identisch sind.

Ein mögliches Prüfschema:

python
from pathlib import Path
from difflib import SequenceMatcher

root = Path(class="str">"mhepl_md_steps")
paragraphs = []

for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    for block in text.split(class="str">"\n\n"):
        normalized = class="str">" ".join(block.lower().split())
        if len(normalized) > 240:
            paragraphs.append((file.name, normalized, block))

for i, left in enumerate(paragraphs):
    for right in paragraphs[i + 1:]:
        score = SequenceMatcher(None, left[1], right[1]).ratio()
        if score > 0.92:
            print(class="str">"Ähnlicher Absatz:", score)
            print(left[0])
            print(right[0])
            print()

Diese Prüfung ist nicht perfekt. Sie erkennt nicht jede Wiederholung und kann manchmal legitime Ähnlichkeit melden. Aber sie verhindert, dass große Mengen nahezu gleicher Absätze unbemerkt in die HTML-Datei gelangen.

Zusätzlich kann man verbotene Muster direkt suchen:

python
forbidden = [
    class="str">"integrierte vertiefung",
    class="str">"praxisdurchlauf",
    class="str">"anhang 16",
    class="str">"entscheidung 2",
    class="str">"entscheidung 3",
    class="str">"risiko 2",
    class="str">"risiko 3",
    class="str">"warum das im lab wichtig ist",
]

for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8").lower()
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"Verbotenes Muster gefunden: {pattern} in {file.name}")

Diese Prüfung wird vor jeder finalen HTML-Erzeugung ausgeführt.

---

6. Umgang mit Tabellen

Tabellen sind nicht verboten. Aber Tabellen dürfen nicht das Buch ersetzen.

Eine gute Tabelle fasst eine fachliche Entscheidung knapp zusammen:

Skizze / Textauszug
| Workload | Geeignete Plattform | Begründung |
|---|---|---|
| Order Service | OpenShift | moderner Service, skalierbar, GitOps-fähig |
| große PostgreSQL-Instanz | Bare Metal oder Managed DB | hoher I/O-Bedarf, klare Backup-Strategie |
| Legacy Reporting | VM | bestehende Treiber und Betriebsvorgaben |

Eine schlechte Tabelle erzeugt künstliche Masse:

Skizze / Textauszug
| Entscheidung 1 | Begründung 1 | Risiko 1 |
| Entscheidung 2 | Begründung 2 | Risiko 2 |
| Entscheidung 3 | Begründung 3 | Risiko 3 |

Für die spätere HTML-Version gilt deshalb:

Vorlage / Textauszug

Tabellen sind erlaubt, wenn sie eine echte Übersicht liefern.

Tabellen sind nicht erlaubt, wenn sie nur Platz füllen.

Im Zweifel wird eine Tabelle in Fließtext umgewandelt. Fließtext zwingt stärker dazu, Zusammenhänge zu erklären.

---

7. Umgang mit Glossar und Begriffen

Ein separates Glossar kann hilfreich sein. In diesem Projekt soll es aber nicht der Hauptinhalt sein. Begriffe werden zuerst im Kontext erklärt.

Beispiel:

Vorlage / Textauszug

Ein ServiceMonitor ist eine Custom Resource, mit der Prometheus Operator erkennt, welche Services Metriken liefern. Im Order Service wird der ServiceMonitor verwendet, damit Prometheus den Actuator-Endpunkt /actuator/prometheus regelmäßig abfragt.

Das ist besser als:

Vorlage / Textauszug

ServiceMonitor: Kubernetes-Ressource für Monitoring.

Der kurze Glossarstil ist nur als Index erlaubt. Die Erklärung muss im Fachkapitel stehen.

---

8. Struktur der späteren HTML-Datei

Die spätere HTML-Datei soll weiterhin eine einzige Datei sein. Sie darf keine externen Bibliotheken, keine CDN-Links und keine separaten Assets benötigen.

Die Grundstruktur:

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>
    /* komplettes CSS inline */
  </style>
</head>
<body>
  <header>...</header>
  <aside>...</aside>
  <main>...</main>
  <script>
    /* kleines optionales Komfort-JavaScript */
  </script>
</body>
</html>

Die Navigation soll robust bleiben. Deshalb werden native HTML-Elemente bevorzugt:

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5-kubernetes-und-openshift">Überblick</a>
  <a href=class="str">"#namespace-strategie">Namespace-Strategie</a>
  <a href=class="str">"#networkpolicy">NetworkPolicy</a>
</details>

Content-Kategorien sollen ebenfalls klappbar sein und standardmäßig geschlossen:

html
<details class=class="str">"content-section" id=class="str">"teil-5-kubernetes-und-openshift">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <section>
    <h2>Warum Kubernetes nicht nur Deployment ist</h2>
    <p>...</p>
  </section>
</details>

Der Vorteil: Auch ohne JavaScript funktioniert das Öffnen und Schließen.

---

9. Navigation ohne kaputte Logik

Die Navigation darf nicht von komplexem JavaScript abhängen. In früheren Fassungen war genau das ein Problem: Buttons, dynamische DOM-Änderungen und verschachtelte Steuerungen konnten auf mobilen Geräten oder lokalen HTML-Dateien unzuverlässig funktionieren.

Die neue Regel lautet:

Vorlage / Textauszug

Navigation muss mit HTML allein funktionieren.

JavaScript darf nur Zusatzkomfort liefern.

Zusatzkomfort kann sein:

javascript
document.querySelectorAll(class="str">'a[href^="#"]').forEach(link => {
  link.addEventListener(class="str">'click', () => {
    const id = link.getAttribute(class="str">'href').slice(1);
    const target = document.getElementById(id);
    if (!target) return;
    const parentDetails = target.closest(class="str">'details');
    if (parentDetails) parentDetails.open = true;
  });
});

Wenn dieses JavaScript nicht läuft, bleiben die Links trotzdem normale Ankerlinks. Das ist der entscheidende Unterschied.

---

10. Codeblöcke und Syntax-Hervorhebung

Die Codeblöcke sollen farbig und lesbar sein, aber ohne externe Highlighting-Bibliothek. Das bedeutet: Die HTML-Erzeugung muss Code in einfache Klassen einteilen oder wenigstens unterschiedliche Sprachen sauber kennzeichnen.

Ein Codeblock bekommt eine Kopfzeile:

html
<figure class=class="str">"code-card">
  <figcaption>Java · OrderApplicationService.java</figcaption>
  <pre><code class=class="str">"language-java">...</code></pre>
</figure>

Die CSS-Regeln können dann für Lesbarkeit sorgen:

css
.code-card {
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--code-bg);
}

.code-card figcaption {
  padding: .75rem 1rem;
  font-weight: 700;
  border-bottom: 1px solid var(--border);
}

pre {
  margin: 0;
  padding: 1rem;
  overflow-x: auto;
  line-height: 1.55;
}

Für manuelle Syntax-Hervorhebung können Klassen wie kw, str, num, comment, anno, tag, key verwendet werden. Wichtig ist aber: Die Farbe darf nicht wichtiger werden als Lesbarkeit.

---

11. Diagramme und SVGs

Die spätere HTML-Datei soll Diagramme enthalten, aber ohne externe Bilder. Es gibt zwei robuste Möglichkeiten.

Erstens: Inline-SVG. Das ist ideal für Architekturdiagramme, weil die Grafik direkt in der HTML-Datei steckt.

Zweitens: Mermaid-Code als sichtbarer Codeblock. Ohne externe Mermaid-Bibliothek wird daraus kein automatisch gerendertes Diagramm, aber der Leser sieht die Struktur. Für eine echte Offline-Single-File ohne externe Bibliotheken ist Inline-SVG zuverlässiger.

Die Regel lautet:

Vorlage / Textauszug

Wichtige Diagramme werden als Inline-SVG dargestellt.

Mermaid darf ergänzend als lesbares Architekturmodell enthalten sein.

Für die finale HTML-Version sollten mindestens diese SVGs entstehen:

Vorlage / Textauszug
  1. Gesamtarchitektur
  2. Netzwerkzonen und DMZ
  3. OpenShift-Zielplattform
  4. Identity-Flow mit Keycloak und LDAP
  5. Outbox- und Messaging-Flow
  6. Observability-Flow
  7. Backup- und Disaster-Recovery-Flow
  8. GitOps-Release-Flow
  9. VM-zu-OpenShift-Migrationspfad
  10. AI-Unterstützung im Entwicklungsprozess

---

12. HTML-Größe über 2 MB ohne Fülltext

Die Anforderung „HTML über 2 MB“ bleibt bestehen. Aber die Größe darf nicht durch Fülltext entstehen.

Legitime Quellen für Größe sind:

Vorlage / Textauszug
  • ausführlicher Fließtext
  • viele echte Codebeispiele
  • Runbooks
  • Übungen und Musterlösungen
  • Inline-SVGs
  • CSS für modernes Layout
  • fachliche Navigation
  • eingebettete Prüflisten

Nicht legitime Quellen sind:

Vorlage / Textauszug
  • wiederholte Absätze
  • künstliche Referenzkarten
  • nummerierte Fülltabellen
  • zufällige Glossarlisten
  • lange Lorem-Ipsum-ähnliche Texte

Wenn die HTML-Datei unter 2 MB bleibt, wird nicht einfach Text dupliziert. Stattdessen werden fehlende echte Inhalte ergänzt, zum Beispiel zusätzliche Runbooks, konkrete Codebeispiele oder bessere Diagramme.

---

13. Prüfbericht vor finaler Ausgabe

Jede finale Ausgabe soll einen kurzen Prüfbericht enthalten.

Beispiel:

Vorlage / Textauszug

Prüfbericht:

  • ZIP fehlerfrei: ja
  • HTML über 2 MB: ja
  • externe Assets: nein
  • CDN-Links: nein
  • Navigation nativ klappbar: ja
  • Content-Kategorien standardmäßig geschlossen: ja
  • verbotene Titel gefunden: nein
  • künstliche Tabellenmuster gefunden: nein
  • auffällige Absatzwiederholungen: keine kritischen
  • Codeblöcke vorhanden: ja
  • Inline-SVGs vorhanden: ja

Dieser Prüfbericht ersetzt nicht das Lesen, aber er verhindert offensichtliche technische Fehler.

---

14. Konsolidierter Master-Index

Für die spätere HTML-Version wird ein Master-Index vorbereitet.

Vorlage / Textauszug

Teil 1: Unternehmenskontext und Zielbild

Teil 2: Infrastruktur-Fundament

Teil 3: Cloud-Architektur

Teil 4: VM- und Bare-Metal-Betrieb

Teil 5: Kubernetes- und OpenShift-Plattform

Teil 6: Identity, Security und Governance

Teil 7: Moderne Anwendungen

Teil 8: Daten, Messaging und Integration

Teil 9: Observability und Betrieb

Teil 10: Backup, Restore und Disaster Recovery

Teil 11: DevOps, GitOps und Release Management

Teil 12: Codex und Claude Code im Enterprise-Projekt

Teil 13: Komplettes Beispielprojekt

Teil 14: Übungen, Runbooks und Musterlösungen

Teil 15: Integrierte Begriffserklärungen und Fachindex

Teil 16: Qualitätsprüfung, Konsolidierung und HTML-Vorbereitung

Dieser Index darf in der HTML-Navigation erscheinen. Er ist fachlich benannt und enthält keine künstlichen Seriennamen.

---

15. Übung: Qualitätsprüfung planen

Erstelle für dein eigenes Lernprojekt eine Qualitätsprüfung mit mindestens zehn Regeln.

Die Regeln sollen prüfen:

Vorlage / Textauszug
  1. verbotene Titel
  2. wiederholte Absätze
  3. Tabellenanteil
  4. Codeblock-Anzahl
  5. Diagramm-Anzahl
  6. externe Assets
  7. Navigation
  8. mobile Nutzbarkeit
  9. Fachlichkeit der Überschriften
  10. ZIP-Inhalt

Beschreibe zusätzlich, welche Fehler automatisch geprüft werden können und welche Fehler ein Mensch lesen muss.

---

16. Musterlösung zur Übung

Eine mögliche Qualitätsprüfung lautet:

Vorlage / Textauszug
  1. Suche nach „integrierte Vertiefung“, „Praxisdurchlauf“, „Anhang 16“ und ähnlichen generischen Titeln.
  2. Vergleiche lange Absätze mit Ähnlichkeitswerten über 0,92.
  3. Zähle Tabellen und prüfe, ob sie mehr als 20 Prozent des Inhalts ausmachen.
  4. Zähle Codeblöcke und prüfe, ob wichtige Kapitel technische Beispiele enthalten.
  5. Prüfe, ob mindestens acht Inline-SVGs enthalten sind.
  6. Suche nach http://, https://, cdn, script src und link rel stylesheet.
  7. Prüfe, ob Navigation mit details/summary umgesetzt ist.
  8. Prüfe, ob Content-Kategorien standardmäßig geschlossen sind.
  9. Suche nach Überschriften mit reinen Nummern oder Platzhaltern.
  10. Öffne das ZIP testweise und prüfe, ob README, Markdown und HTML enthalten sind.

Automatisch prüfbar sind Muster, Dateigrößen, ZIP-Struktur, Links, verbotene Wörter und Codeblock-Anzahlen. Menschlich geprüft werden müssen fachliche Tiefe, Verständlichkeit, didaktischer Aufbau und die Frage, ob ein Kapitel wirklich einen eigenen Lernwert hat.

---

17. Merksätze

Vorlage / Textauszug

Eine große HTML-Datei ist nur dann wertvoll, wenn sie aus echtem Inhalt besteht.

Native HTML-Navigation ist robuster als komplexe lokale JavaScript-Logik.

Tabellen und Glossar dürfen unterstützen, aber nicht das Fachbuch ersetzen.

Praxis gehört in das passende Fachkapitel, nicht in angehängte Serienblöcke.

Qualitätsprüfung ist kein Nachtrag, sondern Teil der Erzeugung.

Wenn ein Generator denselben Absatz mehrfach ausgibt, muss der Prozess geändert werden, nicht nur der Absatz repariert.

Finale Markdown-Konsolidierung und Masterbuch-Aufbau

1. Ziel dieses Teils

Nach den ersten sechzehn Teilen liegt das Projekt nicht mehr als lose Chat-Ausgabe vor, sondern als wachsendes Markdown-Buch. Dieser Teil beschreibt, wie daraus ein sauberes Masterbuch entsteht. Der Schwerpunkt liegt nicht auf neuer Technik, sondern auf Ordnung, Lesbarkeit, Konsistenz und Wiederverwendbarkeit.

Das Ziel ist eine konsolidierte Markdown-Fassung, die später ohne Qualitätsverlust in HTML, PDF oder ein Repository-Paket umgewandelt werden kann. Dabei darf kein Inhalt künstlich aufgeblasen werden. Wiederholungen, generische Platzhalter und angehängte Erweiterungsblöcke müssen vor der HTML-Erzeugung entfernt oder fachlich integriert werden.

Ein Masterbuch ist mehr als die Summe einzelner Kapitel. Es braucht eine klare Leserführung. Der Leser soll verstehen, warum das Projekt existiert, wie die Infrastruktur aufgebaut ist, wie moderne Anwendungen betrieben werden, wie Security und Observability integriert sind und wie am Ende ein vollständiges Enterprise-Lab entsteht.

---

2. Warum Konsolidierung notwendig ist

Beim schrittweisen Schreiben entstehen häufig kleine Unterschiede im Stil. Ein Kapitel verwendet vielleicht den Begriff OpenShift Route, ein anderes Kapitel spricht von Ingress, ein weiteres erwähnt API Gateway. Alle Begriffe können korrekt sein, aber ohne Konsolidierung wirkt das Buch uneinheitlich.

Auch Codebeispiele können auseinanderlaufen. In einem Kapitel heißt der Namespace order-prod, in einem anderen orders-prod. In einem Abschnitt heißt der Service order-service, in einem anderen orders-api. Solche Kleinigkeiten wirken harmlos, führen aber beim Lernen zu Verwirrung.

Die Konsolidierung hat deshalb drei Aufgaben:

Vorlage / Textauszug
  1. Fachliche Begriffe vereinheitlichen
  2. Technische Namen konsistent machen
  3. Kapitel so verbinden, dass ein zusammenhängendes Lernbuch entsteht

Wichtig ist: Konsolidierung bedeutet nicht, Inhalte zu kürzen, bis nur noch eine Zusammenfassung übrig bleibt. Konsolidierung bedeutet, die vorhandenen Inhalte zu ordnen, Dopplungen zu entfernen und fehlende Übergänge zu ergänzen.

---

3. Zielstruktur des Masterbuchs

Das finale Markdown-Masterbuch soll eine klare Hauptstruktur erhalten:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md

  1. Vorwort und Lernziel
  2. Unternehmenskontext und Zielbild
  3. Infrastruktur-Fundament
  4. Cloud-Architektur
  5. VM- und Bare-Metal-Betrieb
  6. Kubernetes- und OpenShift-Plattform
  7. Identity, Security und Governance
  8. Moderne Anwendungen
  9. Daten, Messaging und Integration
  10. Observability und Betrieb
  11. Backup, Restore und Disaster Recovery
  12. DevOps, GitOps und Release Management
  13. Codex und Claude Code im Enterprise-Projekt
  14. Komplettes Beispielprojekt
  15. Übungen, Runbooks und Musterlösungen
  16. Integrierte Begriffserklärungen und Fachindex
  17. Qualitätsprüfung und HTML-Vorbereitung
  18. Konsolidierte Abschlussstruktur

Die Dateiteile bleiben zusätzlich erhalten. Das Masterbuch ist also nicht der Ersatz für die Einzeldateien, sondern die zusammengeführte Lesefassung.

---

4. Regeln für den Masterbuch-Aufbau

Die Zusammenführung muss nach festen Regeln erfolgen. Sonst entstehen wieder die alten Probleme: Anhänge ohne Kontext, generische Überschriften, wiederholte Absätze oder künstliche Tabellen.

Regel 1: Keine künstlichen Serientitel

Nicht erlaubt:

Vorlage / Textauszug

Praxisdurchlauf 1

Praxisdurchlauf 2

Integrierte Vertiefung 3

Anhang 16

Referenzkarte 7

Erlaubt:

Vorlage / Textauszug

Outbox-Backlog im Betrieb analysieren

Keycloak-Rollenmodell für Admin Portal umsetzen

OpenShift Deployment mit Readiness und Resource Limits absichern

PostgreSQL-Restore fachlich validieren

Der Titel muss sagen, worum es fachlich geht.

Regel 2: Praxis direkt integrieren

Praxisabschnitte werden nicht an das Ende eines Kapitels geklebt. Sie stehen dort, wo der Leser sie braucht.

Beispiel: Das Runbook für einen Outbox-Backlog gehört in den Bereich Daten, Messaging und Integration oder in das Beispielprojekt. Es gehört nicht in einen losen Anhang mit dem Titel Praxisdurchlauf.

Regel 3: Begriffe im Kontext erklären

Ein Begriff wie NetworkPolicy wird dort erklärt, wo er eingesetzt wird: im Kubernetes/OpenShift-Kapitel und später im Security-Kontext. Der Fachindex darf auf den Begriff verweisen, aber er ersetzt nicht die Erklärung im Kapitel.

Regel 4: Tabellen nur als Werkzeug

Tabellen dürfen Entscheidungen zusammenfassen, aber sie dürfen nicht der Hauptinhalt sein. Ein Kapitel muss zuerst erklären, warum ein Thema wichtig ist, wie es funktioniert und wie es im Lab angewendet wird. Erst danach kann eine Tabelle helfen.

Regel 5: Code muss erklärend eingebettet sein

Ein Codeblock ohne Erklärung ist wenig wert. Jeder größere Codeblock braucht:

Vorlage / Textauszug
  • Zweck
  • Kontext
  • wichtige Stellen
  • typische Fehler
  • Bezug zum Lab

---

5. Konsistenzregeln für Namen

Für das gesamte Buch gelten feste Namen. Diese Namen werden in allen Kapiteln gleich verwendet.

Vorlage / Textauszug

Unternehmen: Alpine Retail & Services Group

Plattform: Enterprise Platform Lab

Kernservice: order-service

Abrechnungsservice: billing-service

Frontend: customer-portal

Internes Frontend: admin-portal

Reporting: reporting-service

Dateiverarbeitung: file-processing-service

Namespace Produktion Order: order-prod

Namespace Produktion Billing: billing-prod

Namespace GitOps: platform-gitops

Identity Provider: Keycloak

Verzeichnisdienst: LDAP / Active Directory

Broker: Kafka oder RabbitMQ

Datenbank: PostgreSQL

Object Storage: S3-kompatibel oder MinIO

Diese Namen wirken vielleicht klein, aber sie verhindern sehr viele spätere Inkonsistenzen. Wenn die HTML-Datei später eine Sidebar und Ankerlinks bekommt, sind konsistente Namen besonders wichtig.

---

6. Konsistenzregeln für technische Beispiele

Alle Java-Beispiele verwenden die Paketstruktur:

Vorlage / Textauszug

com.seb4u.demo.spring

com.seb4u.demo.quarkus

com.seb4u.demo.micronaut

Für den Hauptpfad wird Spring Boot verwendet, weil es im Enterprise-Umfeld breit eingesetzt wird und sich gut für ausführliche Beispiele eignet. Quarkus und Micronaut werden ergänzend betrachtet, aber nicht in jedem Kapitel parallel wiederholt.

Alle Kubernetes- und OpenShift-Beispiele verwenden:

Vorlage / Textauszug

apiVersion: apps/v1

kind: Deployment

metadata.name: order-service

namespace: order-prod

Alle CI/CD-Beispiele verwenden nachvollziehbare Schritte:

Vorlage / Textauszug

checkout

setup java

build

test

container image build

security scan

push image

update gitops repo

---

7. Master-README

Neben dem Masterbuch braucht das ZIP eine README. Diese README ist nicht der Inhalt des Lernbuchs, sondern eine Bedienungsanleitung für die Dateien.

Eine gute README enthält:

Vorlage / Textauszug

Enterprise Platform Lab

Dieses Paket enthält die Markdown-Schritte des Lernbuchs.

Inhalt
  • Einzelkapitel als Markdown-Dateien
  • README zur Orientierung
  • später: konsolidiertes Master-Markdown
  • später: Single-File-HTML
Lesereihenfolge

Beginne mit Teil 1 und arbeite dich chronologisch bis zum letzten Teil vor.

Qualitätsregeln

Diese Fassung vermeidet künstliche Generator-Wiederholungen. Praxis, Begriffe und Beispiele werden in den jeweiligen Fachkapiteln integriert.

Nächste Ausbaustufe

Nach der Markdown-Konsolidierung wird eine Single-File-HTML mit klappbarer Navigation, inline SVGs und farbigen Codeblöcken erzeugt.

---

8. Vorbereitung einer konsolidierten Markdown-Datei

Die finale Master-Markdown-Datei kann automatisiert aus den Einzeldateien erzeugt werden. Dabei sollte aber nicht blind alles aneinandergehängt werden. Es braucht einen Vorspann, ein Inhaltsverzeichnis und klare Übergänge.

Beispielskript:

python
from pathlib import Path

source_dir = Path(class="str">"mhepl_md_steps")
output_file = source_dir / class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md"

parts = sorted(source_dir.glob(class="str">"[0-9][0-9]_Teil_*.md"))

header = class="str">""class="str">"# Enterprise Platform Lab\n\n"
header += class="str">"## Konsolidiertes Masterbuch\n\n"
header += class="str">"Dieses Dokument fasst alle fachlich geschriebenen Teile des Lernbuchs zusammen.\n\n"

with output_file.open(class="str">"w", encoding=class="str">"utf-8") as out:
    out.write(header)
    out.write(class="str">"## Inhaltsverzeichnis\n\n")
    for part in parts:
        title = part.stem.replace(class="str">"_", class="str">" ")
        out.write(fclass="str">"- {title}\n")
    out.write(class="str">"\n---\n\n")

    for part in parts:
        out.write(part.read_text(encoding=class="str">"utf-8"))
        out.write(class="str">"\n\n---\n\n")

Dieses Skript ist bewusst einfach. In einer späteren Stufe kann es erweitert werden, um automatisch Anker, HTML-IDs, Codeblock-Klassen und Kapitelnummern zu erzeugen.

---

9. Qualitätsprüfung vor der HTML-Erzeugung

Vor der HTML-Erzeugung wird die Markdown-Basis geprüft. Die Prüfung sucht nicht nur technische Fehler, sondern auch typische Qualitätsprobleme.

Gesucht werden:

Vorlage / Textauszug

Praxisdurchlauf

integrierte Vertiefung

Anhang 16

Entscheidung 2

Risiko 2

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Bei Security, Hardening und Audit beginnt die Arbeit

Wenn solche Muster auftauchen, wird nicht versucht, sie kosmetisch umzubenennen. Der betroffene Abschnitt muss fachlich neu geschrieben werden.

Beispielprüfung:

python
from pathlib import Path

forbidden = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 2",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
    class="str">"Bei Security, Hardening und Audit beginnt die Arbeit",
]

for path in Path(class="str">"mhepl_md_steps").glob(class="str">"*.md"):
    text = path.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"FEHLER: {pattern} in {path.name}")

Diese Prüfung ist keine perfekte Qualitätskontrolle, aber sie verhindert die bekannten Fehler aus den früheren Versionen.

---

10. Inhaltliche Übergänge zwischen den Teilen

Ein Masterbuch braucht Übergänge. Ohne Übergänge wirkt es wie eine Sammlung einzelner Notizen.

Beispielübergang von Infrastruktur zu Cloud:

Vorlage / Textauszug

Nachdem die lokalen Infrastrukturzonen verstanden sind, erweitert das nächste Kapitel diese Sicht in Richtung Cloud. Die Cloud ersetzt die lokale Infrastruktur nicht vollständig, sondern ergänzt sie um flexible Dienste wie Object Storage, temporäre Umgebungen und optional Managed Databases.

Beispielübergang von OpenShift zu Security:

Vorlage / Textauszug

Nachdem Namespaces, Deployments, Routes und NetworkPolicies eingeführt wurden, stellt sich die nächste Frage: Wer darf diese Ressourcen sehen, ändern und betreiben? Damit beginnt der Übergang von Plattformtechnik zu Identity, Security und Governance.

Solche Übergänge helfen dem Leser, den roten Faden zu behalten.

---

11. Vorbereitung der späteren HTML-Struktur

Die HTML-Datei soll aus der konsolidierten Markdown-Basis entstehen. Sie bekommt keine externen Assets. Alles muss in einer Datei funktionieren.

Die Zielstruktur:

Skizze / Textauszug
HTML
├── Header
├── Sidebar-Navigation
│   └── native details/summary
├── Hauptinhalt
│   └── klappbare Kategorien
├── inline CSS
├── minimales optionales JavaScript
├── inline SVG-Diagramme
└── farbige Codeblöcke ohne CDN

Wichtige Regel: Die Datei muss auch ohne JavaScript lesbar bleiben. JavaScript darf nur Komfort liefern, zum Beispiel Suche oder automatisches Öffnen eines Zielabschnitts.

---

12. Warum das Masterbuch vor HTML kommt

HTML zu früh zu erzeugen war einer der Hauptfehler der früheren Fassungen. Wenn der Inhalt schwach ist, macht eine schöne HTML-Oberfläche ihn nicht besser. Navigation, Farben und Codeblöcke können nur helfen, wenn der Text fachlich gut ist.

Die Reihenfolge ist deshalb:

Vorlage / Textauszug
  1. Fachlich gutes Markdown
  2. Konsolidiertes Master-Markdown
  3. Qualitätsprüfung
  4. HTML-Erzeugung
  5. ZIP mit HTML, Markdown und README

Nicht:

Vorlage / Textauszug
  1. Große HTML-Datei erzwingen
  2. Inhalt künstlich auffüllen
  3. Fehler nachträglich reparieren

Diese neue Reihenfolge verhindert, dass wieder eine 2-MB-Datei entsteht, die zwar groß ist, aber inhaltlich schwach wirkt.

---

13. Übung: Konsolidierungsprüfung durchführen

Prüfe ein Kapitel deiner Wahl mit folgenden Fragen:

Vorlage / Textauszug
  1. Gibt es generische Überschriften?
  2. Wiederholt sich ein Absatz fast identisch?
  3. Gibt es Tabellen, die nur Inhalt vortäuschen?
  4. Sind Codeblöcke erklärt?
  5. Gibt es einen fachlichen Übergang zum nächsten Kapitel?
  6. Sind Begriffe im Kontext erklärt?
  7. Ist klar, welchen Nutzen das Kapitel im Lab hat?

Schreibe danach drei konkrete Verbesserungen auf.

---

14. Musterlösung zur Übung

Beispielprüfung für ein Security-Kapitel:

Vorlage / Textauszug

Problem 1:

Der Abschnitt erklärt RBAC, erwähnt aber nicht den Unterschied zwischen Kubernetes RBAC und fachlichen Rollen im Admin Portal.

Verbesserung:

Einen eigenen Abschnitt ergänzen, der Kubernetes RoleBinding, Keycloak Realm Roles und Spring Security Authorities sauber trennt.

Problem 2:

Ein Codeblock zeigt eine RoleBinding, aber erklärt nicht, warum diese Berechtigung notwendig ist.

Verbesserung:

Vor dem YAML erklären, welche Person oder Pipeline diese Rechte benötigt und warum keine ClusterRole verwendet wird.

Problem 3:

Das Kapitel endet abrupt.

Verbesserung:

Übergang zu Policy as Code ergänzen, weil Governance nicht nur aus Rollen besteht, sondern auch aus überprüfbaren Regeln.

Diese Musterlösung zeigt: Qualitätsprüfung bedeutet nicht nur Rechtschreibung oder Format. Es geht um fachliche Klarheit.

---

15. Merksätze

Vorlage / Textauszug

Ein Masterbuch entsteht nicht durch Aneinanderkleben, sondern durch Konsolidierung.

Konsistente Namen sind ein Qualitätsmerkmal.

HTML darf erst entstehen, wenn die Markdown-Basis fachlich tragfähig ist.

Praxis gehört in das passende Fachkapitel, nicht in künstliche Anhänge.

Ein Begriff ist erst verstanden, wenn er im Projektkontext erklärt wird.

Große Dateien sind nur dann wertvoll, wenn der Inhalt fachlich unterschiedlich und nützlich ist.

Finale HTML-Erzeugung aus dem geprüften Markdown-Material

1. Ziel dieses Teils

Nachdem die fachlichen Teile als Markdown-Schritte aufgebaut wurden, darf die HTML-Erzeugung nicht wieder ein eigener Generator werden, der Inhalte künstlich verlängert. Die HTML-Datei soll das vorhandene Markdown-Material sauber darstellen. Sie ist also nicht der Ort, an dem neue Fülltexte entstehen, sondern der Ausgabecontainer für das bereits geschriebene Fachbuch.

Das Ziel lautet:

Vorlage / Textauszug

Markdown ist die Quelle.

HTML ist die lesbare Ausgabe.

ZIP ist das saubere Übergabeformat.

Damit bleibt nachvollziehbar, was fachlicher Inhalt ist und was nur Darstellung ist. Wenn später ein Kapitel verbessert wird, wird zuerst Markdown angepasst. Danach wird HTML neu gebaut. So entsteht kein Durcheinander aus reparierten HTML-Dateien, halben Versionen und unsichtbaren Altlasten.

---

2. Warum die HTML-Erzeugung gefährlich werden kann

Die bisherigen fehlerhaften Fassungen hatten ein typisches Problem: Die HTML sollte groß wirken. Dadurch wurden generische Textmuster, wiederholte Tabellen, künstliche Praxisdurchläufe oder nummerierte Anhänge erzeugt. Das Ergebnis sah umfangreich aus, war aber fachlich schwach.

Eine gute HTML-Erzeugung macht genau das Gegenteil. Sie bewahrt den Inhalt, verändert aber nicht seine fachliche Aussage. Sie ergänzt nur Struktur, Navigation, Lesbarkeit, Code-Darstellung und mobile Nutzbarkeit.

Nicht erlaubt sind deshalb:

Vorlage / Textauszug
  • zusätzliche Platzhalterkapitel
  • automatisch erzeugte Wiederholungsabsätze
  • künstliche Tabellen zum Aufblasen
  • generische Überschriften wie Praxisdurchlauf 7
  • generische Überschriften wie integrierte Vertiefung 4
  • Navigationseinträge wie Anhang 16
  • Glossarlisten als Ersatz für echte Kapitel

Erlaubt sind:

Vorlage / Textauszug
  • saubere Kapitelstruktur
  • klappbare Navigation
  • klappbare Content-Kategorien
  • farbige Codeblöcke
  • responsive Layouts
  • inline SVG-Diagramme
  • Suchfunktion als Komfort
  • Druck-/PDF-taugliche Struktur

Der Unterschied ist wichtig: Darstellung darf verbessert werden, Inhalt darf nicht künstlich ersetzt werden.

---

3. Zielstruktur der finalen HTML-Datei

Die finale HTML-Datei soll als Single-File funktionieren. Das bedeutet: CSS, JavaScript und SVGs werden direkt eingebettet. Es gibt keine externen CDN-Abhängigkeiten und keine fehlenden Asset-Ordner.

Die Struktur sieht so aus:

Skizze / Textauszug
lab.html
├── HTML Head
│   ├── Meta Charset
│   ├── Viewport
│   ├── Titel
│   └── Inline CSS
├── Body
│   ├── Kopfbereich
│   ├── Layout Container
│   │   ├── Sidebar Navigation
│   │   └── Main Content
│   ├── Inline SVGs
│   ├── Codeblöcke
│   └── optionales Komfort-JavaScript

Die Datei muss offline funktionieren. Wenn sie lokal geöffnet wird, soll der Inhalt lesbar bleiben. JavaScript darf Komfort liefern, aber nicht notwendig sein.

---

4. Robuste Navigation

Die Navigation wird mit nativen HTML-Elementen gebaut:

html
<nav class=class="str">"sidebar">
  <details>
    <summary>Teil 1: Unternehmenskontext und Zielbild</summary>
    <a href=class="str">"#teil-1-ziel">Ziel des Teils</a>
    <a href=class="str">"#teil-1-unternehmen">Beispielunternehmen</a>
    <a href=class="str">"#teil-1-architektur">Zielarchitektur</a>
  </details>
</nav>

Der Vorteil von details und summary ist Stabilität. Diese Elemente funktionieren auch ohne eigenes JavaScript. Auf iPhone, iPad, Safari, Chrome und Desktop-Browsern sind sie deutlich robuster als komplizierte Button-Logik mit dynamischem DOM-Umbau.

Die Navigation ist standardmäßig geschlossen. Der Leser öffnet nur den Bereich, den er braucht. Bei sehr großen Lernbüchern ist das angenehmer als eine kilometerlange Seitenleiste.

---

5. Klappbare Content-Kategorien

Auch die Hauptinhalte werden mit details strukturiert. Jede große Kategorie ist standardmäßig geschlossen.

html
<main>
  <details class=class="str">"content-section" id=class="str">"teil-5">
    <summary>Teil 5: Kubernetes- und OpenShift-Plattform</summary>
    <article>
      <h2>Warum OpenShift im Enterprise-Lab wichtig ist</h2>
      <p>...</p>
    </article>
  </details>
</main>

Wichtig ist: Der Inhalt ist vorhanden. Er wird nicht per JavaScript nachgeladen. Wenn JavaScript deaktiviert ist, kann der Leser die Bereiche trotzdem öffnen.

Das verhindert genau jene Probleme, die bei lokalen HTML-Dateien auf mobilen Geräten häufig auftreten.

---

6. Suchfunktion nur als Komfort

Eine Suchfunktion ist hilfreich, aber sie darf nicht die einzige Möglichkeit sein, Inhalte zu erreichen. Die HTML-Datei muss auch ohne Suche nutzbar bleiben.

Die Suche darf daher nur Folgendes tun:

Vorlage / Textauszug
  • Treffer markieren
  • passende Content-Kategorie öffnen
  • optional zum Treffer springen

Sie darf nicht:

Vorlage / Textauszug
  • Inhalte verstecken
  • Navigation neu erzeugen
  • Kapitel dynamisch löschen
  • Hash-Routing erzwingen
  • localStorage voraussetzen

Ein einfaches Suchfeld ist ausreichend:

html
<input id=class="str">"search" type=class="str">"search" placeholder=class="str">"Im Lernbuch suchen...">

Die eigentliche Nutzbarkeit kommt von der sauberen Navigation und den Ankern.

---

7. Codeblöcke im JetBrains-Dark-Stil

Codeblöcke sind ein Kernbestandteil dieses Projekts. Sie müssen lesbar sein, horizontal scrollen können und auf hellem wie dunklem Hintergrund funktionieren.

Die HTML nutzt einen einheitlichen dunklen Stil:

css
pre.codeblock {
  background: #1e1f22;
  color: #dcdfe4;
  border-radius: 14px;
  padding: 1rem;
  overflow-x: auto;
  border: 1px solid #34373f;
}

pre.codeblock code {
  font-family: class="str">"JetBrains Mono", class="str">"Consolas", monospace;
  font-size: 0.92rem;
  line-height: 1.55;
}

.token-keyword { color: #cf8efa; }
.token-string  { color: #6aab73; }
.token-number  { color: #2aacb8; }
.token-comment { color: #7a7e85; }
.token-type    { color: #56a8f5; }

Da keine CDN-Bibliothek verwendet wird, wird das Highlighting entweder einfach CSS-basiert umgesetzt oder durch beim Build erzeugte span-Elemente vorbereitet. Wichtig ist nicht perfekte IDE-Genauigkeit, sondern stabile Lesbarkeit.

---

8. Copy-Button ohne Pflichtfunktion

Ein Copy-Button ist nützlich, aber nicht kritisch. Wenn der Button auf einem mobilen Browser nicht funktioniert, muss der Code trotzdem manuell markierbar und kopierbar bleiben.

Die Regel lautet:

Vorlage / Textauszug

Copy-Button ist Komfort.

Codeblock selbst ist die Wahrheit.

Beispiel:

html
<div class=class="str">"code-wrap">
  <button class=class="str">"copy" type=class="str">"button">Kopieren</button>
  <pre class=class="str">"codeblock"><code>oc get pods -n order-prod</code></pre>
</div>

Wenn JavaScript scheitert, bleibt der Code sichtbar.

---

9. Inline SVG statt externe Bilder

Alle wichtigen Diagramme sollen direkt in die HTML eingebettet werden. Externe Dateien führen bei lokalen HTML-Ausgaben oft zu fehlenden Bildern, besonders wenn ZIPs entpackt, Dateien verschoben oder auf iPhones geöffnet werden.

Ein Inline-SVG sieht beispielsweise so aus:

html
class="str">"
0

0

900

360
" role=
class
="str">"img" aria-label=
class
="str">"Hybrid Enterprise Plattform">

class="str">"
20
" y=
class
="str">"
40
" width=
class
="str">"
180
" height=
class
="str">"
80
" rx=
class
="str">"
14
">


class="str">"
110
" y=
class
="str">"
85
" text-anchor=
class
="str">"middle">Customer Portal

Für die finale HTML sind mehrere Diagrammtypen vorgesehen:

Vorlage / Textauszug
  • Zielarchitektur
  • Netzwerkzonen
  • Cloud/VPC-Struktur
  • OpenShift Deployment Flow
  • Event Flow mit Outbox
  • Observability-Pipeline
  • Backup/Restore-Fluss
  • GitOps Release Flow

Die Diagramme sollen nicht dekorativ sein. Sie sollen fachliche Zusammenhänge zeigen.

---

10. Markdown-zu-HTML-Konsolidierung

Die Markdown-Dateien werden in numerischer Reihenfolge gelesen:

Vorlage / Textauszug

01_Teil_1_Unternehmenskontext_und_Zielbild.md

02_Teil_2_Infrastruktur_Fundament.md

03_Teil_3_Cloud_Architektur.md

...

18_Teil_18_Finale_HTML_Erzeugung_aus_Markdown.md

Daraus wird ein Master-Markdown erzeugt:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md

Danach wird daraus HTML erzeugt:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_FINAL_SINGLE.html

Der Build-Prozess muss prüfen:

Vorlage / Textauszug
  • Sind alle Teile vorhanden?
  • Sind Dateinamen eindeutig?
  • Gibt es verbotene Titelmuster?
  • Gibt es wiederholte Absatzblöcke?
  • Sind Codeblöcke geschlossen?
  • Sind Überschriften hierarchisch sinnvoll?
  • Gibt es externe Asset-Links?

---

11. Prüfskript gegen Wiederholungen

Ein einfaches Python-Prüfskript kann typische Wiederholungen finden. Es ersetzt keine fachliche Prüfung, hilft aber gegen offensichtliche Generatorfehler.

python
from pathlib import Path
from collections import Counter
import re

source_dir = Path(class="str">"mhepl_md_steps")
texts = []

for file in sorted(source_dir.glob(class="str">"*.md")):
    if file.name == class="str">"README.md":
        continue
    text = file.read_text(encoding=class="str">"utf-8")
    paragraphs = [p.strip() for p in re.split(rclass="str">"\n\s*\n", text) if len(p.strip()) > 120]
    texts.extend(paragraphs)

counter = Counter(texts)
repeated = [(p, n) for p, n in counter.items() if n > 1]

if repeated:
    print(class="str">"Wiederholte längere Absätze gefunden:")
    for paragraph, count in repeated[:20]:
        print(class="str">"---")
        print(class="str">"Anzahl:", count)
        print(paragraph[:500])
else:
    print(class="str">"Keine identischen längeren Absätze gefunden.")

Dieses Skript findet identische Absätze. Zusätzlich sollte später eine semantische Prüfung erfolgen, bei der auch fast gleiche Absätze auffallen.

---

12. Prüfskript gegen verbotene Muster

python
from pathlib import Path

forbidden = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

source_dir = Path(class="str">"mhepl_md_steps")
failed = False

for file in sorted(source_dir.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"Verbotenes Muster gefunden: {pattern} in {file.name}")
            failed = True

if failed:
    raise SystemExit(1)

print(class="str">"Keine verbotenen Muster gefunden.")

Dieses Skript bildet die Erfahrungen aus den fehlerhaften HTML-Versionen ab. Es verhindert, dass dieselben Muster erneut in die finale Ausgabe gelangen.

---

13. HTML-Build-Skizze

Die spätere Build-Logik kann so aussehen:

python
from pathlib import Path
import html

source_dir = Path(class="str">"mhepl_md_steps")
out_dir = Path(class="str">"dist")
out_dir.mkdir(exist_ok=True)

files = [p for p in sorted(source_dir.glob(class="str">"*.md")) if p.name != class="str">"README.md"]

master = []
for file in files:
    master.append(fclass="str">"\n\n<!-- SOURCE: {file.name} -->\n\n")
    master.append(file.read_text(encoding=class="str">"utf-8"))

master_md = class="str">"\n".join(master)
(out_dir / class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md").write_text(master_md, encoding=class="str">"utf-8")

# Danach: Markdown parser, Codeblock-Renderer, Navigation-Builder, SVG-Inline-Blöcke

In der echten Umsetzung wird danach ein Markdown-Parser oder eine eigene kontrollierte Konvertierung verwendet. Wichtig ist, dass der Build reproduzierbar bleibt.

---

14. Mobile Regeln

Die HTML muss auf Desktop und Mobilgeräten lesbar sein. Besonders wichtig ist iPhone/Safari, weil lokale HTML-Dateien dort oft eingeschränkt geöffnet werden.

Regeln:

Vorlage / Textauszug
  • keine Pflichtfunktion nur per JavaScript
  • Navigation mit details/summary
  • Content mit details/summary
  • Codeblöcke horizontal scrollbar
  • Tabellen nur sparsam und scrollbar
  • keine externen Fonts
  • keine externen CDN-Skripte
  • keine komplexe Hash-Routing-Logik
  • keine Pflichtspeicherung in localStorage

CSS-Beispiel:

css
@media (max-width: 900px) {
  .layout {
    display: block;
  }

  .sidebar {
    position: static;
    max-height: none;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }

  main {
    padding: 1rem;
  }

  pre.codeblock {
    font-size: 0.85rem;
    max-width: 100%;
  }
}

---

15. Qualitätskriterium für die finale HTML

Die finale HTML ist erst akzeptiert, wenn diese Punkte erfüllt sind:

Vorlage / Textauszug
  1. Alle Markdown-Teile wurden übernommen.
  2. Die Navigation enthält fachliche Namen.
  3. Die Navigation ist standardmäßig geschlossen.
  4. Die Content-Kategorien sind standardmäßig geschlossen.
  5. Codeblöcke sind dunkel, farbig und scrollbar.
  6. SVG-Diagramme sind inline eingebettet.
  7. Es gibt keine externen Assets.
  8. Es gibt keine künstlichen Fülltabellen.
  9. Es gibt keine separaten Praxisdurchlauf-Blöcke.
  10. Es gibt keine generischen Anhang-Navigationseinträge.
  11. Begriffe sind kontextnah erklärt.
  12. Die HTML funktioniert offline.
  13. Das ZIP enthält HTML, Master-Markdown, einzelne Markdown-Teile und README.

---

16. Ziel-ZIP für die finale Ausgabe

Das finale ZIP soll später so aussehen:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_FINAL_PACKAGE.zip
├── README.md
├── Modern_Hybrid_Enterprise_Platform_Lab_FINAL_SINGLE.html
├── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md
├── md_steps/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   ├── ...
│   └── 18_Teil_18_Finale_HTML_Erzeugung_aus_Markdown.md
└── checks/
    ├── quality_report.md
    └── build_notes.md

Dadurch kann die HTML genutzt werden, ohne die Quellen zu verlieren. Gleichzeitig bleiben alle Zwischenstände nachvollziehbar.

---

17. Übung

Erstelle eine kurze Qualitätsdefinition für eine HTML-Ausgabe dieses Projekts.

Sie soll beantworten:

Vorlage / Textauszug
  1. Was darf die HTML-Erzeugung verändern?
  2. Was darf sie nicht verändern?
  3. Welche Funktionen müssen ohne JavaScript funktionieren?
  4. Welche Dateien müssen im ZIP enthalten sein?
  5. Welche Muster müssen verboten werden?

---

18. Musterlösung

Eine mögliche Antwort:

Vorlage / Textauszug

Die HTML-Erzeugung darf Layout, Navigation, Codeblock-Darstellung, Anker, Inhaltsverzeichnis und Inline-Diagramme ergänzen. Sie darf den fachlichen Inhalt nicht durch künstliche Fülltexte erweitern.

Ohne JavaScript müssen Navigation, Content-Kategorien, Lesen der Kapitel und Zugriff auf Codeblöcke funktionieren.

Das ZIP muss die Single-File-HTML, das Master-Markdown, alle einzelnen Markdown-Schritte und eine README enthalten.

Verboten sind generische Muster wie Praxisdurchlauf X, integrierte Vertiefung X, Anhang 16, künstliche Entscheidungstabellen, wiederholte Standardabsätze und Glossarlisten als Ersatz für Fachkapitel.

---

19. Merksätze

Vorlage / Textauszug

Die HTML ist Ausgabe, nicht Inhaltserzeuger.

Markdown bleibt die fachliche Quelle.

Navigation muss robust sein, nicht spektakulär.

JavaScript darf helfen, aber nicht tragen.

Eine große Datei ist nur dann wertvoll, wenn der Inhalt echt ist.

Qualität entsteht vor der HTML-Erzeugung, nicht durch nachträgliches Aufblasen.

Finale Delivery-Struktur, ZIP-Paket und Abnahmeprüfung

1. Warum Delivery ein eigenes Fachkapitel braucht

Ein Lernbuch oder Enterprise-Lab ist nicht fertig, nur weil Inhalte geschrieben wurden. Fertig ist es erst, wenn die Ausgabe reproduzierbar, prüfbar und für den Leser einfach nutzbar ist. Gerade bei großen HTML-, Markdown- und ZIP-Paketen entstehen sonst schnell Probleme: Dateien fehlen, Links zeigen ins Leere, Codeblöcke sind schlecht lesbar, Navigation funktioniert nur auf einem Gerät, Bilder liegen in einem separaten Ordner und werden auf dem iPhone nicht geladen, oder das ZIP enthält alte Zwischenversionen mit Namen wie final_fix_neu_v2.

Delivery bedeutet in diesem Projekt: Aus dem fachlichen Markdown-Material wird ein sauberes, nachvollziehbares Paket. Dieses Paket enthält die Master-Markdown-Datei, die einzelnen Kapiteldateien, eine Single-File-HTML, eine README, optional spätere PDF-Ausgaben und Prüfberichte. Die Struktur muss so einfach sein, dass man sie herunterladen, entpacken und sofort verwenden kann.

Der wichtigste Grundsatz lautet:

Vorlage / Textauszug

Ein Delivery-Paket ist kein Dateiablageplatz.

Es ist das fertige, überprüfte Ergebnis einer Lern- und Projektarbeit.

Deshalb bekommt das Projekt eine klare Delivery-Struktur.

---

2. Zielstruktur des finalen ZIP-Pakets

Das finale ZIP soll keine unübersichtliche Sammlung zufälliger Dateien sein. Es soll klar zeigen, welche Datei wofür gedacht ist.

Empfohlene Struktur:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY/
├── README.md
├── CHANGELOG.md
├── MASTERBOOK.md
├── web/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_SINGLE.html
├── md/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   ├── 03_Teil_3_Cloud_Architektur.md
│   ├── 04_Teil_4_VM_und_Bare_Metal_Betrieb.md
│   ├── 05_Teil_5_Kubernetes_und_OpenShift_Plattform.md
│   ├── 06_Teil_6_Identity_Security_und_Governance.md
│   ├── 07_Teil_7_Moderne_Anwendungen.md
│   ├── 08_Teil_8_Daten_Messaging_und_Integration.md
│   ├── 09_Teil_9_Observability_und_Betrieb.md
│   ├── 10_Teil_10_Backup_Restore_und_Disaster_Recovery.md
│   ├── 11_Teil_11_DevOps_GitOps_und_Release_Management.md
│   ├── 12_Teil_12_Codex_und_Claude_Code_im_Enterprise_Projekt.md
│   ├── 13_Teil_13_Komplettes_Beispielprojekt.md
│   ├── 14_Teil_14_Uebungen_Runbooks_und_Musterloesungen.md
│   ├── 15_Teil_15_Integrierte_Begriffserklaerungen_und_Fachindex.md
│   ├── 16_Teil_16_Qualitaetspruefung_Konsolidierung_und_HTML_Vorbereitung.md
│   ├── 17_Teil_17_Finale_Markdown_Konsolidierung_und_Masterbuch_Aufbau.md
│   ├── 18_Teil_18_Finale_HTML_Erzeugung_aus_Markdown.md
│   └── 19_Teil_19_Finale_Delivery_Struktur_ZIP_und_Abnahmepruefung.md
├── checks/
│   ├── quality-report.md
│   ├── forbidden-patterns.txt
│   └── link-check-report.md
└── examples/
    ├── adr/
    ├── runbooks/
    ├── manifests/
    └── code-snippets/

Diese Struktur trennt die fertigen Ausgaben von den Quellen. Die Markdown-Dateien bleiben nachvollziehbar. Die HTML-Datei ist als Endnutzer-Ausgabe klar erkennbar. Prüfberichte liegen unter checks. Wiederverwendbare Beispiele liegen unter examples.

---

3. Warum keine unklaren Dateinamen erlaubt sind

In früheren Iterationen entstehen bei Reparaturversuchen oft Dateinamen wie:

Vorlage / Textauszug

final.html

final_neu.html

final_fix.html

final_fix_2.html

wirklich_final.html

neu_final_final.html

Solche Namen sind gefährlich. Niemand erkennt später, welche Datei gültig ist. Deshalb nutzt dieses Projekt sprechende Namen.

Gute Namen:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_SINGLE.html

Modern_Hybrid_Enterprise_Platform_Lab_MASTERBOOK.md

Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY.zip

Modern_Hybrid_Enterprise_Platform_Lab_QUALITY_REPORT.md

Wenn eine neue Version entsteht, bekommt sie eine klare Versionsnummer:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_v1.0.0_DELIVERY.zip

Modern_Hybrid_Enterprise_Platform_Lab_v1.1.0_DELIVERY.zip

Modern_Hybrid_Enterprise_Platform_Lab_v2.0.0_DELIVERY.zip

Nicht jede kleine Korrektur braucht einen neuen chaotischen Namen. Versionierung muss lesbar bleiben.

---

4. README als Einstiegspunkt

Die README ist die erste Datei, die ein Leser öffnen soll. Sie erklärt, was im Paket enthalten ist und welche Datei zuerst genutzt wird.

Eine gute README enthält:

Vorlage / Textauszug
  1. Projekttitel
  2. Zweck des Pakets
  3. empfohlene Startdatei
  4. Inhalt des ZIPs
  5. Hinweise zur lokalen Nutzung
  6. Hinweise zu iPhone/Safari
  7. Hinweise zu Codeblöcken
  8. bekannte Grenzen
  9. Versionsinformation
  10. Prüfstatus

Beispiel:

Vorlage / Textauszug

Enterprise Platform Lab

Start

Für die normale Nutzung öffne:

`web/Modern_Hybrid_Enterprise_Platform_Lab_SINGLE.html`

Für Bearbeitung und Wiederverwendung nutze:

`MASTERBOOK.md`

Inhalt
  • vollständiges Lernbuch als Markdown
  • einzelne Kapiteldateien
  • Single-File-HTML ohne externe Assets
  • Prüfberichte
  • Beispiele für ADRs, Runbooks und Manifeste
Hinweise

Die HTML-Datei ist offlinefähig. SVGs, CSS und Codeblock-Stile sind inline eingebettet. Wichtige Navigation basiert auf nativen HTML-Elementen und ist nicht zwingend von JavaScript abhängig.

Die README darf nicht nur Platzhalter enthalten. Sie muss dem Nutzer wirklich helfen.

---

5. CHANGELOG für nachvollziehbare Entwicklung

Ein CHANGELOG dokumentiert, was sich zwischen Versionen geändert hat. Für ein Lernbuch ist das hilfreich, weil später klar ist, wann Inhalte ergänzt, entfernt oder umstrukturiert wurden.

Beispiel:

Vorlage / Textauszug

CHANGELOG

1.0.0
  • erste vollständige Markdown-Fassung mit Teilen 1 bis 19
  • Qualitätsregeln gegen Wiederholungen eingeführt
  • Delivery-Struktur definiert
  • HTML-Erzeugungsregeln vorbereitet
0.9.0
  • Teile 1 bis 18 einzeln als Markdown geschrieben
  • Zwischen-ZIP mit allen Markdown-Dateien erstellt
0.5.0
  • Neustart nach Entfernung künstlicher Generator-Wiederholungen

Das CHANGELOG ist besonders wichtig, wenn HTML, Markdown und später PDF-Ausgaben parallel entstehen.

---

6. Abnahmeprüfung vor HTML-Erzeugung

Bevor die finale HTML-Datei erzeugt wird, muss das Markdown geprüft werden. Sonst werden Fehler nur schöner verpackt.

Die Abnahmeprüfung beantwortet:

Vorlage / Textauszug

Gibt es künstliche Wiederholungen?

Gibt es generische Titel?

Gibt es leere Kapitel?

Gibt es zu viele Tabellen?

Gibt es Codeblöcke ohne Sprache?

Gibt es defekte interne Links?

Gibt es fehlende Überschriften?

Gibt es Kapitel ohne Praxisbezug?

Gibt es Begriffe ohne Kontext?

Gibt es alte Reparaturreste?

Diese Prüfung ist wichtiger als die optische Gestaltung. Eine schöne HTML-Datei mit schlechtem Inhalt bleibt schlechtes Material.

---

7. Verbotene Muster

Das Projekt verwendet eine Liste verbotener Muster. Diese Liste wird vor jeder finalen Ausgabe geprüft.

Beispiele:

Vorlage / Textauszug

integrierte Vertiefung

Praxisdurchlauf 1

Praxisdurchlauf 2

Praxisdurchlauf 3

Anhang 16

Entscheidung 2

Entscheidung 3

Risiko 2

Risiko 3

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Bei Security, Hardening und Audit beginnt die Arbeit

Ein Treffer bedeutet nicht automatisch, dass die Datei falsch ist. Aber jeder Treffer muss geprüft werden. In diesem Projekt sollen solche Muster nicht mehr vorkommen, weil sie auf künstlich erzeugte Inhalte hindeuten.

---

8. Python-Prüfung für verbotene Muster

python
from pathlib import Path

root = Path(class="str">"markdown")
forbidden = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf 1",
    class="str">"Praxisdurchlauf 2",
    class="str">"Praxisdurchlauf 3",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
    class="str">"Bei Security, Hardening und Audit beginnt die Arbeit",
]

hits = []

for md_file in root.glob(class="str">"*.md"):
    text = md_file.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            hits.append((md_file.name, pattern))

if hits:
    print(class="str">"Verbotene Muster gefunden:")
    for file_name, pattern in hits:
        print(fclass="str">"- {file_name}: {pattern}")
    raise SystemExit(1)

print(class="str">"Keine verbotenen Muster gefunden.")

Diese Prüfung ist einfach, aber wirkungsvoll. Sie verhindert, dass bekannte Fehler erneut in die finale Ausgabe gelangen.

---

9. Prüfung gegen zu ähnliche Absätze

Eine zweite Prüfung erkennt Absätze, die fast identisch sind. Das ist wichtig, weil Wiederholungen nicht immer über exakt gleiche Wörter entstehen.

Beispiel für problematische Struktur:

Vorlage / Textauszug

Bei X beginnt die Arbeit mit einer konkreten Fragestellung...

Bei Y beginnt die Arbeit mit einer konkreten Fragestellung...

Bei Z beginnt die Arbeit mit einer konkreten Fragestellung...

Eine einfache Heuristik kann Absätze normalisieren und Wiederholungen zählen.

python
import re
from pathlib import Path
from collections import Counter

root = Path(class="str">"markdown")
paragraphs = []

for md_file in root.glob(class="str">"*.md"):
    text = md_file.read_text(encoding=class="str">"utf-8")
    for paragraph in re.split(rclass="str">"\n\s*\n", text):
        clean = paragraph.strip()
        if len(clean) < 180:
            continue
        normalized = re.sub(rclass="str">"\b\d+\b", class="str">"<NUM>", clean.lower())
        normalized = re.sub(rclass="str">"[a-z0-9_.+-]+@[a-z0-9_.+-]+", class="str">"<EMAIL>", normalized)
        normalized = re.sub(rclass="str">"\s+", class="str">" ", normalized)
        paragraphs.append(normalized[:280])

counts = Counter(paragraphs)

for snippet, count in counts.most_common(20):
    if count > 1:
        print(class="str">"Wiederholung gefunden:", count)
        print(snippet)

Diese Prüfung ersetzt kein menschliches Lesen. Sie ist aber ein guter Schutz gegen künstliche Wiederholungen.

---

10. Linkprüfung

In der späteren HTML-Datei müssen interne Links funktionieren. Wenn Navigationseinträge auf IDs zeigen, die nicht existieren, wirkt die Datei kaputt.

Die Linkprüfung kontrolliert:

Vorlage / Textauszug

Jeder Navigationslink zeigt auf eine vorhandene ID.

Jede Kategorie hat eine eindeutige ID.

Jede Überschrift erzeugt einen stabilen Anker.

Es gibt keine doppelten IDs.

Es gibt keine leeren href=class="str">"#"-Links.

Ein einfaches Prüfskript für HTML:

python
from pathlib import Path
import re

html = Path(class="str">"web/Modern_Hybrid_Enterprise_Platform_Lab_SINGLE.html").read_text(encoding=class="str">"utf-8")

ids = set(re.findall(rclass="str">'id="([^"]+)"', html))
hrefs = re.findall(rclass="str">'href="#([^"]+)"', html)

missing = [href for href in hrefs if href not in ids]

duplicate_ids = []
for value in ids:
    if html.count(fclass="str">'id="{value}"') > 1:
        duplicate_ids.append(value)

if missing:
    print(class="str">"Fehlende Ziel-IDs:")
    for item in missing:
        print(class="str">"-", item)

if duplicate_ids:
    print(class="str">"Doppelte IDs:")
    for item in duplicate_ids:
        print(class="str">"-", item)

if missing or duplicate_ids:
    raise SystemExit(1)

print(class="str">"Interne Links sind in Ordnung.")

---

11. Prüfung der Codeblöcke

Codeblöcke müssen lesbar und konsistent sein. In diesem Projekt gilt das JetBrains-Dark-Prinzip: dunkler Hintergrund, klare Kontraste, farbige Syntax, horizontales Scrollen und keine wechselnden zufälligen Designs.

Zu prüfen ist:

Vorlage / Textauszug

Jeder Codeblock hat eine Sprache.

Codeblöcke sind scrollbar.

Copy-Button ist Komfort, aber keine Pflichtfunktion.

Farben sind kontrastreich.

Java, YAML, SQL, HTML, CSS, JavaScript und XML sind unterscheidbar.

Ein Markdown-Codeblock ohne Sprache ist problematisch:

Vorlage / Textauszug

public class Demo {}

Vorlage / Textauszug

Besser:

Vorlage / Textauszug

public class Demo {}

Vorlage / Textauszug

Die Sprache hilft später beim Syntax-Highlighting.

---

12. Abnahme der Single-File-HTML

Die finale HTML-Datei muss offline funktionieren. Deshalb werden keine externen CDN-Bibliotheken verwendet.

Pflichtpunkte:

Vorlage / Textauszug

CSS inline

SVG inline

kein externes JavaScript

keine externen Bilder

keine externen Fonts

Navigation mit details/summary

Content-Kategorien mit details/summary

standardmäßig geschlossen

Codebereiche scrollbar

Tabellen scrollbar, falls Tabellen vorkommen

Suchfunktion optional

Copy-Buttons optional

Wichtig: Die Datei muss auch ohne JavaScript nutzbar sein. JavaScript darf Komfort bieten, aber nicht die Grundlage der Nutzbarkeit sein.

---

13. iPhone- und Safari-Abnahme

Lokale HTML-Dateien auf iPhone/Safari sind besonders empfindlich. Manche JavaScript-Funktionen funktionieren dort unzuverlässig, besonders wenn die Datei aus der Dateien-App, aus Messenger-Apps oder direkt lokal geöffnet wird.

Deshalb gelten folgende Regeln:

Vorlage / Textauszug

Navigation nicht nur per Button-JavaScript öffnen.

Kapitel nicht per JavaScript verstecken.

Lösungen nicht nur per onclick anzeigen.

Dark Mode nicht zwingend per localStorage steuern.

Wichtige Inhalte nicht nachträglich per JavaScript erzeugen.

Native HTML-Elemente sind stabiler:

html
<details>
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <p>Inhalt...</p>
</details>

Das ist nicht so spektakulär wie eine komplexe App-Navigation, aber deutlich robuster für ein offlinefähiges Lernbuch.

---

14. Abnahmebericht

Ein Abnahmebericht dokumentiert die wichtigsten Prüfergebnisse.

Vorlage:

Vorlage / Textauszug

Quality Report

Datei

Modern_Hybrid_Enterprise_Platform_Lab_SINGLE.html

Inhalt
  • Markdown-Teile: 19
  • Masterbook vorhanden: ja
  • README vorhanden: ja
  • HTML vorhanden: ja
Prüfungen
  • verbotene Muster: bestanden
  • doppelte Navigations-IDs: bestanden
  • interne Links: bestanden
  • Codeblöcke mit Sprache: bestanden
  • externe Assets: keine gefunden
  • ZIP-Test: bestanden
Bekannte Grenzen
  • Suchfunktion ist Komfortfunktion
  • lokale iPhone-Anzeige hängt teilweise von der App ab, mit der die HTML geöffnet wird
  • PDF-Ausgabe muss separat geprüft werden

Ein solcher Bericht ist nicht Bürokratie. Er schützt davor, dieselben Fehler wieder einzubauen.

---

15. ZIP-Erzeugung

Das ZIP sollte reproduzierbar erstellt werden. Nicht per Drag-and-drop mit zufälliger Ordnerstruktur, sondern über ein klares Skript.

Beispiel:

python
from pathlib import Path
import zipfile

root = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY")
zip_path = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_v1.0.0_DELIVERY.zip")

with zipfile.ZipFile(zip_path, class="str">"w", compression=zipfile.ZIP_DEFLATED) as zf:
    for file in root.rglob(class="str">"*"):
        if file.is_file():
            zf.write(file, file.relative_to(root.parent))

print(zip_path)

Danach wird das ZIP geprüft:

bash
unzip -t Modern_Hybrid_Enterprise_Platform_Lab_v1.0.0_DELIVERY.zip

Wenn unzip -t Fehler meldet, darf die Datei nicht ausgeliefert werden.

---

16. Google-Drive-Ablage

Wenn das ZIP nach Google Drive kopiert wird, sollte der Zielordner klar sein. Für dieses Projekt ist ein Ordner wie chatgpt sinnvoll, weil dort die Projektartefakte gesammelt werden.

Wichtig ist:

Vorlage / Textauszug

Dateiname bleibt eindeutig.

Upload wird bestätigt.

Drive-Datei-ID wird dokumentiert.

Falls möglich: Link wird gespeichert.

Lokale Datei bleibt zusätzlich verfügbar.

Ein Upload ohne Bestätigung reicht nicht. Erst wenn Drive eine Datei-ID zurückgibt oder die Datei im Ordner sichtbar ist, gilt der Upload als abgeschlossen.

---

17. Was nicht ins finale ZIP gehört

Nicht ins finale ZIP gehören:

Vorlage / Textauszug

alte Reparaturversionen

kaputte HTML-Prototypen

Generator-Skripte mit Zwischenfehlern

leere Ordner

Testdateien ohne Nutzen

Screenshots von Fehlern

Dateien mit Namen final_fix_2

Falls Skripte mitgeliefert werden, dann nur bewusst und sauber unter einem Ordner wie tools/ oder scripts/.

---

18. Abnahmekriterien für Version 1.0

Version 1.0 ist erreicht, wenn folgende Punkte erfüllt sind:

Vorlage / Textauszug
  1. Alle Hauptteile liegen als Markdown vor.
  2. MASTERBOOK.md ist konsolidiert.
  3. Single-File-HTML ist erzeugt.
  4. Navigation funktioniert mit nativen Details-Elementen.
  5. Content-Kategorien sind standardmäßig geschlossen.
  6. Codeblöcke sind gut lesbar.
  7. SVGs sind inline eingebettet.
  8. Keine externen Assets werden benötigt.
  9. Verbotene Muster sind nicht vorhanden.
  10. Das ZIP ist geprüft.
  11. README erklärt die Nutzung.
  12. Quality Report dokumentiert die Prüfung.

Erst dann sollte das Paket als finale Version bezeichnet werden.

---

19. Übung

Erstelle eine Abnahmecheckliste für das finale Delivery-Paket.

Die Checkliste soll mindestens enthalten:

Vorlage / Textauszug
  1. Dateistruktur
  2. README
  3. Markdown-Dateien
  4. HTML-Datei
  5. Navigation
  6. Codeblöcke
  7. SVGs
  8. externe Assets
  9. verbotene Muster
  10. ZIP-Prüfung
  11. Google-Drive-Ablage

---

20. Musterlösung

Vorlage / Textauszug

Abnahmecheckliste Delivery-Paket

Dateistruktur
  • [ ] ZIP enthält genau einen klaren Hauptordner
  • [ ] README.md liegt im Hauptordner
  • [ ] MASTERBOOK.md liegt im Hauptordner
  • [ ] einzelne Markdown-Dateien liegen unter md/
  • [ ] HTML-Datei liegt unter web/
  • [ ] Prüfberichte liegen unter checks/
Inhalt
  • [ ] alle Kapitel sind vorhanden
  • [ ] keine Kapitel bestehen nur aus Tabellen
  • [ ] Begriffe sind im Kontext erklärt
  • [ ] Praxis ist in Fachkapitel integriert
  • [ ] keine künstlichen Erweiterungsblöcke vorhanden
HTML
  • [ ] Single-File-HTML vorhanden
  • [ ] Navigation ist klappbar
  • [ ] Content-Kategorien sind klappbar
  • [ ] Kategorien sind standardmäßig geschlossen
  • [ ] interne Links funktionieren
  • [ ] Codeblöcke sind scrollbar und lesbar
  • [ ] SVGs sind inline sichtbar
  • [ ] keine externen CDN-Links vorhanden
Qualität
  • [ ] verbotene Muster geprüft
  • [ ] Wiederholungen geprüft
  • [ ] ZIP mit unzip -t geprüft
  • [ ] Quality Report erstellt
Ablage
  • [ ] ZIP lokal vorhanden
  • [ ] ZIP optional in Google Drive hochgeladen
  • [ ] Drive-Datei-ID dokumentiert

Diese Checkliste ist die Brücke zwischen Inhaltserstellung und stabiler Auslieferung.

---

21. Merksätze

Vorlage / Textauszug

Delivery ist Teil der Qualität.

Ein gutes ZIP ist einfach, sauber und eindeutig.

Eine schöne HTML-Datei ersetzt keine Inhaltsprüfung.

Verbotene Muster müssen vor der finalen Ausgabe geprüft werden.

Navigation muss robust sein, nicht nur optisch modern.

Offlinefähigkeit ist bei Lernmaterial ein echtes Qualitätsmerkmal.

Eine Datei ist erst ausgeliefert, wenn sie geprüft und auffindbar ist.

Versionierung, Pflegeplan und Weiterentwicklung

1. Warum ein Pflegeplan nötig ist

Ein großes Lernbuch und ein Enterprise-Lab sind nicht fertig, sobald die erste Version erzeugt wurde. Gerade ein Projekt, das Cloud, VM, Bare Metal, OpenShift, moderne Java-Services, Frontends, Security, Observability, Backup, GitOps und AI-Werkzeuge verbindet, verändert sich laufend. Bibliotheken bekommen neue Versionen, Kubernetes-APIs werden abgelöst, Security-Empfehlungen ändern sich, interne Standards wachsen und manche Architekturentscheidungen erweisen sich nach praktischer Nutzung als zu streng oder zu schwach.

Deshalb braucht das Enterprise Platform Lab einen Pflegeplan. Der Pflegeplan beschreibt, wie Inhalte aktualisiert werden, wie Fehler korrigiert werden, wie neue Kapitel aufgenommen werden und wie verhindert wird, dass spätere Erweiterungen wieder in künstliche Wiederholungen oder unklare Anhänge abrutschen.

Die wichtigste Regel lautet:

Vorlage / Textauszug

Eine neue Version darf nicht einfach mehr Text enthalten.

Eine neue Version muss fachlich besser, konsistenter und überprüfbarer sein.

Umfang allein ist kein Qualitätsmerkmal. Ein 2-MB-HTML kann schlecht sein, wenn es Wiederholungen enthält. Eine kleinere Markdown-Datei kann wertvoller sein, wenn sie sauber erklärt, konkrete Beispiele enthält und echte Entscheidungen nachvollziehbar macht. Für dieses Projekt zählt daher nicht nur Dateigröße, sondern vor allem fachliche Dichte.

---

2. Versionsmodell für das Lernbuch

Das Projekt verwendet ein einfaches semantisches Versionsmodell.

Vorlage / Textauszug

Version 1.0.x = Korrekturen ohne Strukturänderung

Version 1.x.0 = neue Kapitel oder größere Ergänzungen

Version 2.0.0 = neue Grundstruktur oder neues Zielbild

Eine Patch-Version wird verwendet, wenn Tippfehler, kaputte Links, kleine Codefehler oder fehlerhafte Formulierungen korrigiert werden.

Eine Minor-Version wird verwendet, wenn ein neues Kapitel hinzukommt, ein bestehendes Kapitel deutlich erweitert wird oder zusätzliche Übungen und Runbooks ergänzt werden.

Eine Major-Version wird verwendet, wenn die Buchstruktur selbst geändert wird. Das kann passieren, wenn später aus dem Lernbuch ein vollständiges Repository-Lab mit lauffähigen Services, Docker Compose, OpenShift-Manifests und CI/CD-Pipelines entsteht.

Beispiel:

Skizze / Textauszug
1.0.0  Erste fachlich konsolidierte Markdown-Version
1.0.1  Rechtschreibung, Links und kleinere Codekorrekturen
1.1.0  Ergänzung: Quarkus Billing Service
1.2.0  Ergänzung: vollständiges Terraform/OpenTofu Cloud-Lab
2.0.0  Umstellung auf echtes Multi-Repository-Projekt mit lauffähigem Code

Dieses Modell hilft, Änderungen nachvollziehbar zu machen. Es verhindert außerdem, dass Dateien Namen wie final_neu_fix_v2_korrigiert_letzte_version bekommen.

---

3. Saubere Dateinamen

Die bisherige Arbeit hat gezeigt, dass chaotische Dateinamen schnell unübersichtlich werden. Für die weitere Pflege gelten daher klare Namensregeln.

Erlaubt:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_MD_STEPS_01_20.zip

Modern_Hybrid_Enterprise_Platform_Lab_MASTERBOOK_v1.0.0.md

Modern_Hybrid_Enterprise_Platform_Lab_SINGLE_HTML_v1.0.0.html

Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY_v1.0.0.zip

Nicht erlaubt:

Vorlage / Textauszug

final.html

final_neu.html

final_final.html

neu_fix.html

richtig_final_v3.html

bereinigt_letzte_wirklich.html

Der Dateiname muss drei Dinge zeigen:

Vorlage / Textauszug
  1. Projektname
  2. Artefakttyp
  3. Version oder Schrittstand

Dadurch kann man später eindeutig erkennen, welche Datei welche Rolle hat.

---

4. Changelog-Regeln

Jede Version bekommt einen Changelog-Eintrag. Der Changelog beschreibt nicht jeden Tippfehler, sondern relevante Änderungen.

Beispiel:

Vorlage / Textauszug

CHANGELOG

1.0.0
Erstellt
  • Fachbuch-Struktur mit 20 Teilen aufgebaut
  • Markdown-Schritte getrennt gespeichert
  • Qualitätsregeln gegen Wiederholungen eingeführt
  • Delivery-Struktur vorbereitet
Enthalten
  • Unternehmenskontext
  • Infrastruktur-Fundament
  • Cloud-Architektur
  • VM- und Bare-Metal-Betrieb
  • Kubernetes/OpenShift
  • Identity, Security und Governance
  • moderne Anwendungen
  • Daten, Messaging und Integration
  • Observability und Betrieb
  • Backup, Restore und Disaster Recovery
  • DevOps, GitOps und Release Management
  • Codex und Claude Code
  • komplettes Beispielprojekt
  • Übungen, Runbooks und Musterlösungen
  • integrierte Begriffserklärungen
  • Qualitätsprüfung und HTML-Vorbereitung
  • finale Delivery-Struktur
  • Pflegeplan
Bekannte Grenzen
  • lauffähiger Quellcode ist noch nicht als Repository erzeugt
  • finale Single-File-HTML wird in einem separaten Schritt gebaut
  • PDF-Ausgabe wird erst nach HTML-Abnahme erzeugt

Ein guter Changelog ist sachlich. Er behauptet nicht, dass alles perfekt ist. Er sagt klar, was enthalten ist und was noch fehlt.

---

5. Pflegeprozess für neue Inhalte

Neue Inhalte werden nicht direkt irgendwo angehängt. Sie werden nach Verantwortung einsortiert.

Wenn ein neues Thema zu Security gehört, kommt es in Teil 6. Wenn es um OpenShift-Manifests geht, kommt es in Teil 5. Wenn es um Deployments, Releases oder Rollbacks geht, kommt es in Teil 11. Wenn es um konkrete Projektdateien geht, kommt es in Teil 13.

Der Pflegeprozess lautet:

Vorlage / Textauszug
  1. Thema bestimmen
  2. passenden Teil auswählen
  3. fachlichen Abschnitt schreiben
  4. Beispiel oder Code ergänzen
  5. typische Fehler ergänzen
  6. Betrieb/Security-Bezug ergänzen
  7. Übung oder Runbook ergänzen, wenn sinnvoll
  8. Wiederholungsprüfung ausführen
  9. README und Changelog aktualisieren
  10. neues ZIP erzeugen

Diese Reihenfolge verhindert, dass neue Inhalte als lose Erweiterung am Ende landen.

---

6. Regel gegen künstliche Erweiterungen

Ein späterer Abschnitt darf nicht so heißen:

Vorlage / Textauszug

Praxisdurchlauf 1

Praxisdurchlauf 2

Integrierte Vertiefung 3

Anhang 16

Erweiterung 7

Zusatz 4

Solche Titel zeigen meistens, dass der Inhalt nicht sauber eingeordnet wurde.

Stattdessen muss der Titel fachlich sein:

Vorlage / Textauszug

Outbox-Backlog im Order Service analysieren

Keycloak-Rollenmodell für interne Mitarbeiter

OpenShift NetworkPolicy für Billing-Kommunikation

Restore-Test für PostgreSQL mit WAL-Archiv

Argo-CD-Rollback nach fehlerhaftem Deployment

File Processing auf Bare Metal mit Object Storage

Ein fachlicher Titel zwingt dazu, den Inhalt konkret zu machen.

---

7. Pflege von Codebeispielen

Codebeispiele altern schnell. Deshalb müssen Codebeispiele eigene Pflegekriterien erfüllen.

Ein Codeblock darf nicht nur dekorativ sein. Er muss einen Zweck haben.

Guter Codeblock:

java
package com.seb4u.demo.spring.order.application;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class CancelOrderService {

    private final OrderRepository orderRepository;
    private final OutboxEventWriter outboxEventWriter;

    public CancelOrderService(
            OrderRepository orderRepository,
            OutboxEventWriter outboxEventWriter
    ) {
        this.orderRepository = orderRepository;
        this.outboxEventWriter = outboxEventWriter;
    }

    @Transactional
    public void cancelOrder(String orderId, String reason) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));

        order.cancel(reason);
        orderRepository.save(order);

        outboxEventWriter.write(
                class="str">"Order",
                order.id(),
                class="str">"OrderCancelled",
                new OrderCancelledPayload(order.id(), reason)
        );
    }
}

Dieser Code zeigt Transaktion, Domänenaktion, Repository und Outbox. Er passt zum Lernziel.

Schlechter Codeblock:

java
public class Example {
    public void run() {
        System.out.println(class="str">"Hello");
    }
}

Dieser Code ist für ein Enterprise-Lab wertlos, wenn er keinen fachlichen Zusammenhang hat.

---

8. Pflege von YAML und Infrastrukturbeispielen

YAML-Beispiele müssen vollständig genug sein, um ihren Zweck zu zeigen. Gleichzeitig dürfen sie nicht so tun, als wären sie direkt produktionsfertig, wenn wichtige Teile fehlen.

Ein gutes Kubernetes-Beispiel erklärt:

Vorlage / Textauszug
  • welches Objekt erzeugt wird
  • in welchem Namespace es liegt
  • welche Labels wichtig sind
  • welche Ports genutzt werden
  • welche Secrets oder ConfigMaps verwendet werden
  • welche Sicherheitsgrenze betroffen ist

Beispiel:

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-billing-to-kafka
  namespace: billing-prod
spec:
  podSelector:
    matchLabels:
      app: billing-service
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              platform.alpine.example.com/name: messaging
          podSelector:
            matchLabels:
              app: kafka
      ports:
        - protocol: TCP
          port: 9092

Dieser Ausschnitt gehört fachlich in Security, Kubernetes oder Messaging. Er darf nicht isoliert in einem zufälligen Anhang stehen.

---

9. Pflege von Übungen

Übungen sollen nicht nur Wissen abfragen. Sie sollen Denken trainieren.

Eine schwache Übung wäre:

Vorlage / Textauszug

Was ist Kubernetes?

Eine bessere Übung wäre:

Vorlage / Textauszug

Der Order Service läuft auf OpenShift. Er kann Kafka erreichen, aber nicht PostgreSQL.

Beschreibe eine systematische Fehlersuche über DNS, Secret, NetworkPolicy, Firewall und Datenbankstatus.

Eine gute Übung enthält:

Vorlage / Textauszug
  • Ausgangssituation
  • konkrete Symptome
  • erwartete Analyse
  • technische Artefakte
  • Musterlösung

Dadurch wird aus einem Lernbuch ein Arbeitsbuch.

---

10. Pflege von Runbooks

Runbooks müssen ausführbar sein. Ein Runbook darf nicht nur sagen: „Problem prüfen“. Es muss konkrete Schritte enthalten.

Ein gutes Runbook enthält:

Vorlage / Textauszug
  1. Symptom
  2. Auswirkung
  3. Sofortprüfung
  4. technische Prüfkommandos
  5. häufige Ursachen
  6. Entscheidungspunkte
  7. Eskalation
  8. Wiederherstellung
  9. Nachbereitung

Beispiel für einen Entscheidungspunkt:

Vorlage / Textauszug

Wenn nur ein Pod betroffen ist:

  • Pod-Logs prüfen
  • Readiness Probe prüfen
  • Rollout-Historie prüfen

Wenn alle Pods betroffen sind:

  • ConfigMap prüfen
  • Secret prüfen
  • externe Abhängigkeit prüfen
  • letzte Deployment-Version prüfen

Wenn mehrere Services betroffen sind:

  • Cluster-DNS prüfen
  • Netzwerk prüfen
  • Plattformkomponenten prüfen

Diese Struktur hilft im echten Betrieb.

---

11. Pflege von Architekturentscheidungen

ADRs werden nicht gelöscht, nur weil sich eine Entscheidung ändert. Eine neue Entscheidung bekommt einen neuen ADR, der den alten ersetzt oder ergänzt.

Beispiel:

Vorlage / Textauszug

ADR 001: Hybrid Platform statt Single Platform

ADR 014: PostgreSQL bleibt zunächst auf Bare Metal

ADR 021: Reporting wird schrittweise nach OpenShift migriert

ADR 029: Kafka ersetzt RabbitMQ für fachliche Domain Events

Ein ADR muss nicht lang sein. Er muss klar sein.

Mindeststruktur:

Vorlage / Textauszug

ADR XXX: Titel

Status

Vorgeschlagen / Akzeptiert / Ersetzt / Abgelehnt

Kontext

Welche Situation oder welches Problem gibt es?

Entscheidung

Was wird entschieden?

Konsequenzen

Was wird dadurch besser?

Was wird dadurch schwieriger?

Welche Nachweise sind nötig?

---

12. Qualitätsprüfung vor jeder neuen Version

Vor jeder neuen Version wird geprüft:

Vorlage / Textauszug
  • Gibt es generische Titel?
  • Gibt es wiederholte Absätze?
  • Besteht ein Kapitel hauptsächlich aus Tabellen?
  • Gibt es künstliche Nummern wie Entscheidung 2 oder Risiko 3?
  • Sind Praxisabschnitte fachlich integriert?
  • Sind Begriffe im Kontext erklärt?
  • Gibt es genug Fließtext?
  • Gibt es echte Beispiele?
  • Gibt es Betrieb/Security-Bezug?
  • Sind Dateinamen sauber?

Ein einfaches Prüfskript kann verbotene Muster finden:

python
from pathlib import Path

forbidden = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

for path in Path(class="str">".").glob(class="str">"*.md"):
    text = path.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"Verbotenes Muster gefunden: {pattern} in {path}")

Diese Prüfung ersetzt keine fachliche Prüfung, aber sie verhindert bekannte Fehler.

---

13. Qualitätsprüfung gegen Wiederholungen

Wiederholungen sind schwieriger zu erkennen als verbotene Wörter. Deshalb braucht es zusätzlich eine Absatzprüfung.

python
from pathlib import Path
from collections import Counter
import re

paragraphs = []

for path in Path(class="str">".").glob(class="str">"*.md"):
    text = path.read_text(encoding=class="str">"utf-8")
    for paragraph in re.split(rclass="str">"\n\s*\n", text):
        cleaned = class="str">" ".join(paragraph.split())
        if len(cleaned) > 180:
            paragraphs.append((cleaned, path.name))

counter = Counter(p for p, _ in paragraphs)

for paragraph, count in counter.items():
    if count > 1:
        print(class="str">"Wiederholter Absatz:", count)
        print(paragraph[:300])
        print()

Wenn ein Absatz mehrfach vorkommt, muss geprüft werden, ob es sich um eine legitime Wiederholung oder um Fülltext handelt.

---

14. Pflege der HTML-Version

Die spätere HTML-Version wird aus Markdown erzeugt. Dabei gelten feste Regeln.

Die Navigation bleibt robust:

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5">Überblick</a>
  <a href=class="str">"#teil-5-networkpolicy">NetworkPolicy</a>
</details>

Der Inhalt bleibt ebenfalls robust:

html
<details class=class="str">"chapter" id=class="str">"teil-5">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <section>
    <h2>Warum OpenShift Teil der Plattform ist</h2>
    <p>...</p>
  </section>
</details>

Wichtige Inhalte dürfen nicht nur durch JavaScript sichtbar werden. JavaScript darf Komfort bieten, aber nicht die Grundfunktion tragen.

---

15. iPhone- und Safari-Regeln

Für lokale HTML-Dateien auf iPhone oder Safari gelten besonders vorsichtige Regeln:

Vorlage / Textauszug
  • keine Pflichtnavigation nur über JavaScript
  • keine wichtigen Inhalte nur durch onclick
  • details/summary für Klappbereiche
  • normale Ankerlinks
  • Tabellen und Codeblöcke horizontal scrollbar
  • keine externen CDN-Abhängigkeiten
  • keine externen Bilder
  • SVG inline einbetten
  • Copy-Button nur optional

Das bedeutet: Auch wenn der Copy-Button nicht funktioniert, muss der Code lesbar sein. Auch wenn die Suche nicht funktioniert, muss die Navigation nutzbar sein. Auch wenn Dark Mode nicht umschaltet, muss der Inhalt lesbar bleiben.

---

16. Geplanter nächster Ausbau

Nach Teil 20 gibt es drei sinnvolle Wege.

Weg A: Master-Markdown erstellen

Alle bisherigen Teile werden in eine große Markdown-Datei zusammengeführt.

Ergebnis:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_MASTERBOOK_v1.0.0.md

Dieser Weg ist sinnvoll, wenn zuerst die Textqualität geprüft werden soll.

Weg B: Single-File-HTML erstellen

Aus allen Markdown-Dateien wird eine große HTML-Datei erzeugt.

Ergebnis:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_SINGLE_HTML_v1.0.0.html

Dieser Weg ist sinnvoll, wenn das Lernbuch interaktiv genutzt werden soll.

Weg C: Repository-Skeleton erstellen

Aus dem Beispielprojekt wird eine echte Ordnerstruktur mit Code-Dateien, YAML-Dateien, ADRs, Runbooks und README-Dateien erzeugt.

Ergebnis:

Skizze / Textauszug
modern-hybrid-enterprise-platform-runtime-lab/
├── apps/
├── platform/
├── infrastructure/
├── security/
├── observability/
├── docs/
└── README.md

Dieser Weg ist sinnvoll, wenn aus dem Lernbuch ein praktisches Lab werden soll.

---

17. Empfehlung für die nächste Phase

Die beste nächste Phase ist nicht sofort PDF. Zuerst sollte die Markdown-Konsolidierung erfolgen.

Empfohlene Reihenfolge:

Vorlage / Textauszug
  1. Master-Markdown erzeugen
  2. Wiederholungsprüfung ausführen
  3. verbotene Muster prüfen
  4. Kapitelübergänge glätten
  5. HTML erzeugen
  6. HTML auf Navigation prüfen
  7. ZIP mit Markdown, HTML und README erzeugen
  8. optional PDF erzeugen

PDF sollte erst nach HTML-Abnahme entstehen, weil PDF-Korrekturen aufwendiger sind.

---

18. Abnahmekriterien für Version 1.0

Version 1.0 ist fertig, wenn folgende Punkte erfüllt sind:

Vorlage / Textauszug
  • alle 20 Teile liegen als Markdown vor
  • Master-Markdown ist erzeugt
  • keine verbotenen Muster vorhanden
  • keine auffälligen Copy/Paste-Absätze vorhanden
  • Codeblöcke sind sprachlich markiert
  • Kapitelstruktur ist konsistent
  • README erklärt die Nutzung
  • ZIP enthält saubere Dateinamen
  • HTML ist erzeugt und lokal nutzbar
  • Navigation ist standardmäßig geschlossen
  • Content-Kategorien sind standardmäßig geschlossen
  • keine externen Assets nötig

Diese Kriterien sind strenger als „Datei wurde erzeugt“. Genau das ist wichtig, damit das Projekt nicht wieder in fehlerhafte Generatorausgaben abrutscht.

---

19. Übung: Pflegeentscheidung treffen

Ein Team möchte einen neuen Abschnitt hinzufügen:

Vorlage / Textauszug

Praxisdurchlauf 7: Keycloak testen

Der Abschnitt enthält eine kurze Beschreibung, drei Befehle und eine Tabelle mit generischen Risiken.

Entscheide:

Vorlage / Textauszug
  1. Darf dieser Abschnitt so aufgenommen werden?
  2. Wo müsste der Inhalt fachlich einsortiert werden?
  3. Wie sollte der Titel lauten?
  4. Welche zusätzlichen Inhalte fehlen?

---

20. Musterlösung

Der Abschnitt darf so nicht aufgenommen werden. Der Titel Praxisdurchlauf 7 ist generisch und zeigt keine fachliche Einordnung.

Der Inhalt gehört wahrscheinlich in Teil 6: Identity, Security und Governance. Wenn es um die technische Nutzung in einer Anwendung geht, kann zusätzlich ein Bezug zu Teil 7: Moderne Anwendungen entstehen.

Ein besserer Titel wäre:

Vorlage / Textauszug

Keycloak-OIDC-Login für das Admin Portal überprüfen

Der Abschnitt sollte enthalten:

Vorlage / Textauszug
  • Ausgangssituation
  • betroffene Anwendung
  • Realm und Client
  • Redirect-URI
  • Rollen und Gruppen
  • Token-Claims
  • Spring-Security-Prüfung
  • typische Fehler
  • konkrete Prüfkommandos
  • Runbook-Schritte
  • Musterlösung

Damit wird aus einem generischen Praxisdurchlauf ein echter fachlicher Abschnitt.

---

21. Merksätze

Vorlage / Textauszug

Ein Pflegeplan schützt ein großes Lernbuch vor Chaos.

Versionierung ist wichtiger als Dateinamen mit final, neu und fix.

Neue Inhalte werden fachlich integriert, nicht angehängt.

Codebeispiele müssen einen Zweck haben.

Runbooks müssen ausführbar sein.

HTML muss ohne JavaScript grundsätzlich nutzbar bleiben.

PDF kommt erst nach HTML-Abnahme.

Qualität bedeutet: weniger Wiederholung, mehr fachlicher Zusammenhang.

Übergang von Markdown zum finalen HTML-Masterbuch

1. Ziel dieses Teils

Nach den bisherigen Teilen liegt das Lernbuch nicht mehr als künstlich aufgeblasene HTML-Datei vor, sondern als Sammlung einzelner Markdown-Kapitel. Das ist Absicht. Markdown ist in dieser Phase besser geeignet, weil Inhalte fachlich geprüft, ergänzt, verschoben und konsolidiert werden können, ohne dass Layout, JavaScript oder CSS die Qualitätsarbeit verdecken.

Dieser Teil beschreibt den kontrollierten Übergang vom Markdown-Material zum finalen HTML-Masterbuch. Das Ziel ist nicht, sofort wieder eine große Datei zu erzeugen. Das Ziel ist, aus dem vorhandenen Material eine stabile, lesbare und iPhone-taugliche Single-File-HTML zu bauen, die fachlich aus echten Kapiteln besteht.

Die zentrale Regel lautet:

Vorlage / Textauszug

HTML ist nur das Ausgabeformat.

Die Qualität entsteht vorher im Markdown.

Wenn das Markdown wiederholend oder schlecht strukturiert ist, wird auch die schönste HTML-Datei schlecht. Wenn das Markdown sauber ist, kann die HTML-Datei robust, übersichtlich und langlebig werden.

---

2. Warum nicht direkt HTML schreiben?

HTML eignet sich sehr gut für die finale Darstellung, aber weniger gut für inhaltliche Facharbeit. Sobald Sidebar, Dark Mode, Copy-Buttons, Suchfeld, Code-Styling, mobile Darstellung und klappbare Kapitel eingebaut sind, wird jede inhaltliche Änderung technischer.

Markdown trennt Inhalt und Darstellung. Ein Kapitel kann erst fachlich geprüft werden, bevor es in HTML übertragen wird. Dadurch entsteht ein besserer Arbeitsfluss:

Skizze / Textauszug
Fachinhalt schreiben
↓
Inhalt prüfen
↓
Wiederholungen entfernen
↓
Kapitel logisch ordnen
↓
Codeblöcke normalisieren
↓
Diagramme vorbereiten
↓
HTML erzeugen
↓
Navigation prüfen
↓
ZIP liefern

Dieser Ablauf verhindert, dass eine optisch große Datei entsteht, deren Inhalt aus Wiederholungen besteht. Genau dieser Fehler wurde vorher sichtbar: gleichartige Absätze wurden mehrfach erzeugt, nur einzelne Wörter wurden ausgetauscht. Das darf in der finalen HTML nicht mehr passieren.

---

3. Zielbild der finalen HTML

Die finale HTML ist eine einzige Datei. Sie benötigt keine externen Assets, keine CDN-Bibliotheken und keine zusätzlichen Bilderordner. Alles, was angezeigt werden soll, ist in der Datei enthalten.

Die Datei soll enthalten:

Vorlage / Textauszug
  • vollständiges Lernbuch
  • Sidebar mit Kapitelstruktur
  • klappbare Navigation
  • klappbare Inhaltskategorien
  • standardmäßig geschlossene Hauptkategorien
  • farbige Codeblöcke
  • Copy-Button als Komfortfunktion
  • inline SVG-Diagramme
  • responsive Darstellung für iPhone, iPad und Desktop
  • Suchfeld als Komfortfunktion
  • Inhaltsstruktur auch ohne JavaScript nutzbar

Die Datei soll nicht enthalten:

Vorlage / Textauszug
  • externe CSS-Dateien
  • externe JavaScript-Dateien
  • externe Bilddateien
  • CDN-Abhängigkeiten
  • künstliche Füllkapitel
  • generische Titel wie class="str">"Praxisdurchlauf 3"
  • generische Titel wie class="str">"integrierte Vertiefung 4"
  • generische Titel wie class="str">"Anhang 16"
  • Tabellen als Hauptinhalt
  • ein separates Glossar als Ersatz für Fachtext

---

4. HTML-Struktur

Die finale HTML bekommt eine einfache, robuste Struktur. Sie soll nicht von komplizierten JavaScript-Zuständen abhängig sein.

Empfohlene Grundstruktur:

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>
    /* zentrales CSS */
  </style>
</head>
<body>
  <header class=class="str">"topbar">
    <h1>Enterprise Platform Lab</h1>
    <p>Enterprise-Lernbuch für Cloud, VM, Bare Metal, OpenShift und moderne Anwendungen.</p>
  </header>

  <div class=class="str">"layout">
    <aside class=class="str">"sidebar">
      <nav aria-label=class="str">"Kapitel Navigation">
        <!-- details/summary Navigation -->
      </nav>
    </aside>

    <main class=class="str">"content">
      <!-- details/summary Inhaltskapitel -->
    </main>
  </div>

  <script>
    // nur Komfortfunktionen, keine Pflichtlogik
  </script>
</body>
</html>

Diese Struktur ist bewusst einfach. Die Navigation ist semantisch ein nav. Der Hauptinhalt liegt in main. Klappbare Bereiche werden mit nativen HTML-Elementen details und summary gebaut. Dadurch funktioniert das Grundverhalten auch dann, wenn JavaScript auf einem iPhone oder in einer lokalen Datei nicht zuverlässig ausgeführt wird.

---

5. Klappbare Navigation

Die Navigation soll nicht aus verschachtelten Buttons bestehen. Buttons brauchen JavaScript und können auf lokalen iPhone-Dateien unzuverlässig sein. Stattdessen wird details/summary genutzt.

Beispiel:

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5">Überblick</a>
  <a href=class="str">"#teil-5-namespaces">Namespaces</a>
  <a href=class="str">"#teil-5-rbac">RBAC</a>
  <a href=class="str">"#teil-5-networkpolicy">NetworkPolicy</a>
</details>

Die Gruppen sind standardmäßig geschlossen. Der Nutzer kann öffnen, was er braucht. Die Links sind echte Ankerlinks. Wenn JavaScript aktiv ist, kann ein kleines Skript den Zielbereich automatisch öffnen. Wenn JavaScript nicht aktiv ist, bleibt der Inhalt trotzdem erreichbar, weil der Link zur Stelle springt.

---

6. Klappbarer Content

Auch der Content nutzt details/summary. Jede Hauptkategorie ist standardmäßig geschlossen. Das vermeidet eine extrem lange Seite, die auf dem iPhone schwer zu überblicken wäre.

Beispiel:

html
<details class=class="str">"chapter" id=class="str">"teil-5">
  <summary>
    <span class=class="str">"chapter-number">Teil 5</span>
    <span class=class="str">"chapter-title">Kubernetes und OpenShift-Plattform</span>
  </summary>

  <section id=class="str">"teil-5-namespaces">
    <h3>Namespaces als Betriebs- und Sicherheitsgrenze</h3>
    <p>...</p>
  </section>
</details>

Wichtig: Die Überschrift im summary muss fachlich sein. Nicht erlaubt sind generische Titel wie „Kapitel 5“, wenn darunter keine fachliche Orientierung steht. Die Zahl darf helfen, aber der Name muss den Inhalt tragen.

---

7. Codeblock-Regeln

Die Codeblöcke sollen einheitlich wirken. Der Stil orientiert sich am JetBrains-Dark-Prinzip: dunkler Hintergrund, hoher Kontrast, farbliche Unterscheidung für Schlüsselwörter, Strings, Zahlen, Kommentare und Annotationen.

Die finale HTML darf dafür keine externe Highlighting-Bibliothek laden. Stattdessen gibt es zwei mögliche Wege:

Vorlage / Textauszug

Weg 1: Markdown-Codeblöcke mit CSS-Klassen darstellen, ohne feines Token-Highlighting.

Weg 2: Beim HTML-Build einfache Tokens in span-Elemente umwandeln.

Weg 2 ist schöner, aber aufwendiger. Wichtig ist, dass die Lesbarkeit stimmt. Ein Codeblock darf nicht hellgraue Schrift auf hellgrauem Hintergrund haben.

Beispiel-Zielstruktur:

html
<div class=class="str">"code-card">
  <div class=class="str">"code-head">
    <span>java</span>
    <button type=class="str">"button" class=class="str">"copy-btn">Kopieren</button>
  </div>
  <pre><code class=class="str">"language-java">...</code></pre>
</div>

Der Copy-Button ist Komfort. Der Code muss auch ohne Button lesbar und kopierbar bleiben.

---

8. Mobile Regeln

Die HTML muss besonders für iPhone und Safari robust sein. Lokale HTML-Dateien auf iOS können bei JavaScript, Dateizugriff und komplexer DOM-Logik empfindlich sein.

Deshalb gelten folgende Regeln:

Vorlage / Textauszug
  • Wichtige Inhalte nicht per JavaScript nachladen.
  • Navigation nicht ausschließlich per JavaScript öffnen.
  • Keine externen Assets.
  • Keine CDN-Skripte.
  • Kein Pflicht-localStorage.
  • Kein Pflicht-hashchange-System.
  • Keine komplexe SPA-Logik.
  • Codeblöcke horizontal scrollbar machen.
  • Tabellen nur sparsam und scrollbar.
  • große SVGs responsive skalieren.

CSS für Codeblöcke:

css
.code-card pre {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  white-space: pre;
}

CSS für Diagramme:

css
.svg-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.svg-wrap svg {
  max-width: 100%;
  height: auto;
}

---

9. Suchfunktion als Komfort, nicht als Pflicht

Eine Suchfunktion ist hilfreich, aber sie darf die Navigation nicht ersetzen. Wenn JavaScript nicht funktioniert, muss der Nutzer weiterhin über die Sidebar und die Kapitelstruktur arbeiten können.

Die Suche kann so funktionieren:

Skizze / Textauszug
Eingabe im Suchfeld
↓
Skript sucht Überschriften und Absätze
↓
passende Kapitel werden geöffnet
↓
Treffer werden kurz markiert

Nicht erlaubt ist:

Vorlage / Textauszug

Ohne Suche ist der Inhalt nicht zugänglich.

Die Suchfunktion ist also Zusatzkomfort, nicht Fundament.

---

10. Diagramme als inline SVG

Diagramme sollen nicht als externe Bilddateien eingebunden werden. Externe Bilder waren in früheren Varianten fehleranfällig. Deshalb werden wichtige Diagramme direkt als SVG in die HTML geschrieben.

Geeignete Diagrammtypen:

Vorlage / Textauszug
  • Zielarchitektur
  • Netzwerkzonen
  • Cloud-VPC
  • OpenShift-Namespace-Modell
  • Eventfluss mit Outbox
  • Backup- und Restore-Fluss
  • GitOps-Deployment-Fluss
  • Observability-Pipeline

Ein SVG muss nicht künstlerisch perfekt sein. Es muss verständlich sein. Gute Beschriftung ist wichtiger als Dekoration.

Beispiel für ein einfaches Ziel-SVG:

html
class="str">"
0

0

900

420
" role=
class
="str">"img" aria-label=
class
="str">"Hybrid Enterprise Zielarchitektur">

class="str">"
40
" y=
class
="str">"
40
" width=
class
="str">"
180
" height=
class
="str">"
80
" rx=
class
="str">"
14
">


class="str">"
130
" y=
class
="str">"
85
" text-anchor=
class
="str">"middle">Customer Portal


class="str">"
320
" y=
class
="str">"
40
" width=
class
="str">"
180
" height=
class
="str">"
80
" rx=
class
="str">"
14
">


class="str">"
410
" y=
class
="str">"
85
" text-anchor=
class
="str">"middle">Order Service


class="str">"
220
" y1=
class
="str">"
80
" x2=
class
="str">"
320
" y2=
class
="str">"
80
">

---

11. Konsolidierung der Markdown-Dateien

Vor der HTML-Erzeugung werden alle Markdown-Dateien in eine konsolidierte Reihenfolge gebracht. Die Dateinamen geben bereits die Ordnung vor:

Vorlage / Textauszug

01_Teil_1_Unternehmenskontext_und_Zielbild.md

02_Teil_2_Infrastruktur_Fundament.md

...

21_Teil_21_Uebergang_von_Markdown_zum_finalen_HTML_Masterbuch.md

Der Build-Prozess liest diese Dateien sortiert ein. Dabei wird jeder Teil als eigener HTML-details-Block erzeugt.

Wichtig: Die Markdown-Dateien bleiben erhalten. Die HTML ist ein Ausgabeprodukt, nicht die einzige Quelle.

---

12. Qualitätsprüfung vor der HTML-Erzeugung

Vor dem Build wird eine Prüfung ausgeführt. Sie sucht verbotene Muster und auffällige Wiederholungen.

Beispielhafte verbotene Muster:

Vorlage / Textauszug

integrierte Vertiefung

Praxisdurchlauf

Anhang 16

Entscheidung 2

Risiko 2

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Bei Security, Hardening und Audit beginnt die Arbeit

Diese Muster sind nicht deshalb verboten, weil die Wörter grundsätzlich falsch wären. Sie sind verboten, weil sie in den vorherigen Fassungen Anzeichen für künstliche Füllgenerierung waren.

Ein Prüfskript kann so aussehen:

python
from pathlib import Path

root = Path(class="str">"mhepl_md_steps")
forbidden = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 2",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

failed = False

for path in sorted(root.glob(class="str">"*.md")):
    text = path.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"VERBOTENES MUSTER in {path.name}: {pattern}")
            failed = True

if failed:
    raise SystemExit(class="str">"Qualitätsprüfung fehlgeschlagen")

print(class="str">"Qualitätsprüfung bestanden")

---

13. Wiederholungsprüfung

Zusätzlich zur Musterprüfung braucht es eine Wiederholungsprüfung. Nicht jede Wiederholung ist schlecht. Begriffe wie OpenShift, Keycloak oder PostgreSQL kommen natürlich oft vor. Schlecht sind fast identische Absätze.

Eine einfache Heuristik:

Vorlage / Textauszug
  • Absätze normalisieren
  • sehr kurze Absätze ignorieren
  • identische Absätze zählen
  • fast identische Absätze mit Ähnlichkeit prüfen
  • auffällige Blöcke melden

Beispiel:

python
from pathlib import Path
from collections import Counter
import re

root = Path(class="str">"mhepl_md_steps")
paragraphs = []

for path in sorted(root.glob(class="str">"*.md")):
    text = path.read_text(encoding=class="str">"utf-8")
    for block in re.split(rclass="str">"\n\s*\n", text):
        normalized = re.sub(rclass="str">"\s+", class="str">" ", block.strip())
        if len(normalized) > 220:
            paragraphs.append((path.name, normalized))

counter = Counter(p for _, p in paragraphs)

for paragraph, count in counter.items():
    if count > 1:
        print(class="str">"WIEDERHOLUNG", count, paragraph[:180])

Diese Prüfung ersetzt keine menschliche Kontrolle, aber sie verhindert die schlimmsten Generatorfehler.

---

14. HTML-Build-Schritte

Der finale Build folgt diesen Schritten:

Vorlage / Textauszug
  1. Markdown-Dateien sortieren
  2. README und Metadaten lesen
  3. Kapitelüberschriften extrahieren
  4. Navigation erzeugen
  5. Markdown in HTML umwandeln
  6. Codeblöcke in Code-Karten verpacken
  7. SVG-Blöcke einfügen oder erhalten
  8. Content-Kapitel als details/summary erzeugen
  9. zentrale CSS-Datei inline einbetten
  10. kleines Komfort-JavaScript inline einbetten
  11. Qualitätsprüfung auf HTML ausführen
  12. ZIP mit HTML, Markdown, README und Prüfbericht erzeugen

Der Build soll reproduzierbar sein. Das bedeutet: Wenn dieselben Markdown-Dateien eingegeben werden, entsteht dieselbe HTML-Struktur.

---

15. Abnahme der HTML

Die HTML ist erst fertig, wenn sie fachlich und technisch geprüft wurde.

Fachliche Prüfung:

Vorlage / Textauszug
  • Kapitel haben unterschiedliche Inhalte.
  • Praxis ist in Fachkapitel integriert.
  • Begriffe sind im Kontext erklärt.
  • Codebeispiele passen zum Thema.
  • Runbooks sind konkret.
  • Übungen haben Musterlösungen.

Technische Prüfung:

Vorlage / Textauszug
  • Datei öffnet lokal im Browser.
  • Navigation ist nicht kaputt.
  • Hauptkategorien sind geschlossen.
  • Anklicken von Links öffnet Zielbereich oder springt korrekt.
  • Codeblöcke sind lesbar.
  • Codeblöcke scrollen horizontal.
  • SVGs werden angezeigt.
  • ZIP enthält alle relevanten Dateien.

Mobile Prüfung:

Vorlage / Textauszug
  • iPhone zeigt die Datei ohne kaputtes Layout.
  • Sidebar bleibt nutzbar.
  • Content ist nicht abgeschnitten.
  • Code ist scrollbar.
  • Details/summary funktioniert ohne Speziallogik.

---

16. Ziel-ZIP der finalen Ausgabe

Das finale ZIP soll nicht aus zufälligen Dateien bestehen. Es bekommt eine saubere Struktur:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY/
├── web/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html
├── md/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   └── ...
├── source/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md
├── quality-reports/
│   └── quality-report.md
└── README.md

Dadurch ist klar:

Vorlage / Textauszug

web/ enthält die Nutzdatei.

md/ enthält die einzelnen Kapitel.

source/ enthält eine zusammengeführte Markdown-Fassung.

quality-reports/ enthält Prüfergebnisse.

README.md erklärt Start und Struktur.

---

17. Warum diese Struktur besser ist

Frühere Pakete hatten mehrere Probleme: zu viele Reparaturversionen, unklare Dateinamen, künstliche Inhalte, kaputte Navigation oder eine HTML, die zwar groß war, aber fachlich schwach. Die neue Struktur trennt diese Probleme.

Der Inhalt wächst in Markdown. Die finale HTML wird erst erzeugt, wenn das Material stabil genug ist. Das ZIP enthält sowohl das Ergebnis als auch die Quellen. Dadurch kann später gezielt verbessert werden, ohne alles neu zu verlieren.

Das ist besonders wichtig für große Lernbücher. Eine 2-MB-HTML ist nur dann wertvoll, wenn sie aus echten Inhalten besteht. Größe allein ist kein Qualitätsmerkmal.

---

18. Übung

Erstelle eine Abnahmeliste für die finale HTML. Die Liste soll mindestens zehn Punkte enthalten und zwischen fachlicher Prüfung, technischer Prüfung und mobiler Prüfung unterscheiden.

---

19. Musterlösung

Eine mögliche Abnahmeliste:

Vorlage / Textauszug

Fachliche Prüfung:

  1. Jedes Kapitel hat einen eigenen fachlichen Schwerpunkt.
  2. Keine Kapitel bestehen hauptsächlich aus Tabellen.
  3. Praxisbeispiele sind in passende Fachkapitel integriert.
  4. Begriffe werden direkt im Kontext erklärt.
  5. Codebeispiele passen zum jeweiligen Thema.

Technische Prüfung:

  1. Navigation verwendet native details/summary.
  2. Alle Ankerlinks funktionieren.
  3. Codeblöcke sind farbig und horizontal scrollbar.
  4. SVG-Diagramme sind inline eingebettet und sichtbar.
  5. ZIP enthält HTML, Markdown, README und Qualitätsbericht.

Mobile Prüfung:

  1. Die Datei ist auf iPhone/Safari lesbar.
  2. Kategorien sind standardmäßig geschlossen.
  3. Inhalte sind auch ohne JavaScript erreichbar.
  4. Copy-Buttons sind Komfort, aber nicht notwendig.
  5. Keine externen Assets werden geladen.

---

20. Merksätze

Vorlage / Textauszug

Die finale HTML ist ein Ausgabeprodukt, nicht der Ort für ungeprüfte Inhaltserzeugung.

Markdown bleibt die Quelle der Wahrheit.

Navigation muss robust sein, nicht spektakulär.

Eine große HTML-Datei ist nur dann gut, wenn der Inhalt nicht künstlich wiederholt wird.

Details/summary ist für lokale, mobile und robuste Lernbücher oft besser als komplexe JavaScript-Navigation.

Das ZIP muss Quellen, Ergebnis und Prüfbericht enthalten.

Finale HTML-Build-Vorlage und Generator-Regeln

1. Ziel dieses Teils

Nach den bisherigen Markdown-Kapiteln ist klar: Die finale HTML-Datei darf nicht wieder aus automatisch aufgeblasenen Wiederholungen entstehen. Sie soll aus den vorhandenen, kapitelweise geschriebenen Markdown-Dateien gebaut werden. Dieser Teil beschreibt deshalb die Build-Regeln für die spätere Single-File-HTML.

Das Ziel ist eine robuste, offline-fähige HTML-Datei, die auf Desktop, iPad und iPhone gut lesbar bleibt. Die Datei soll keine externen Assets benötigen, keine CDN-Bibliotheken laden und keine kritischen Funktionen von JavaScript abhängig machen. JavaScript darf Komfortfunktionen ergänzen, aber Navigation, Lesen und Aufklappen müssen auch ohne JavaScript funktionieren.

Die HTML-Erzeugung ist also nicht nur ein technischer Export. Sie ist ein kontrollierter Build-Schritt mit Qualitätsprüfung.

---

2. Warum ein Build-Prozess nötig ist

Eine große Lernbuch-HTML kann schnell unübersichtlich werden. Wenn jedes Kapitel direkt per Copy/Paste eingebaut wird, entstehen leicht doppelte Überschriften, defekte Anker, kaputte Codeblöcke oder nicht funktionierende Navigation.

Ein sauberer Build-Prozess löst mehrere Aufgaben:

Vorlage / Textauszug
  1. Markdown-Dateien in richtiger Reihenfolge einsammeln
  2. Überschriften in stabile HTML-Anker umwandeln
  3. Navigation aus den echten Kapitelüberschriften erzeugen
  4. Codeblöcke einheitlich darstellen
  5. SVGs inline einfügen
  6. Kategorien standardmäßig geschlossen darstellen
  7. verbotene Muster prüfen
  8. Wiederholungen erkennen
  9. finale HTML-Datei schreiben
  10. ZIP-Paket mit HTML, Markdown und README erzeugen

Damit wird verhindert, dass spätere Reparaturen wieder neue Fehler erzeugen.

---

3. Zielstruktur der finalen HTML

Die HTML-Datei soll grob so aufgebaut sein:

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>
    /* zentrales Theme */
  </style>
</head>
<body>
  <header class=class="str">"book-header">
    <h1>Enterprise Platform Lab</h1>
    <p>Enterprise-Lernbuch für Cloud, VM, Bare Metal, OpenShift und moderne Anwendungen.</p>
  </header>

  <div class=class="str">"layout">
    <aside class=class="str">"sidebar">
      <nav aria-label=class="str">"Inhaltsverzeichnis">
        <!-- details/summary Navigation -->
      </nav>
    </aside>

    <main class=class="str">"content">
      <!-- details/summary Kapitel -->
    </main>
  </div>

  <script>
    /* nur Komfortfunktionen */
  </script>
</body>
</html>

Wichtig ist: Navigation und Inhalt verwenden native HTML-Elemente. Dadurch bleibt die Datei auch auf iPhone/Safari stabiler.

---

4. Navigation mit details und summary

Die Navigation soll nicht aus komplexen Buttons bestehen. Buttons, die über JavaScript Inhalte ein- und ausblenden, waren in früheren Versionen fehleranfällig. Deshalb verwendet die finale Fassung native HTML-Elemente.

Beispiel:

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5-kubernetes-und-openshift-plattform">Kapitelanfang</a>
  <a href=class="str">"#namespace-strategie">Namespace-Strategie</a>
  <a href=class="str">"#deployment-service-und-route">Deployment, Service und Route</a>
</details>

Die Gruppen sind standardmäßig geschlossen. Der Nutzer kann sie manuell öffnen. Beim Klick auf einen Anker darf ein kleines JavaScript den Zielbereich automatisch öffnen, aber der Link selbst muss auch ohne JavaScript funktionieren.

---

5. Content-Kategorien standardmäßig geschlossen

Auch der Inhalt selbst wird pro Teil klappbar dargestellt. Dadurch ist die große Datei auf mobilen Geräten besser nutzbar.

Beispiel:

html
<details class=class="str">"chapter" id=class="str">"teil-5-kubernetes-und-openshift-plattform">
  <summary>
    <span class=class="str">"chapter-number">Teil 5</span>
    <span class=class="str">"chapter-title">Kubernetes- und OpenShift-Plattform</span>
  </summary>

  <section class=class="str">"chapter-body">
    <h2>Kubernetes-Grundidee</h2>
    <p>...</p>
  </section>
</details>

Kein Kapitel bekommt automatisch open. So bleiben alle Kapitel beim Start geschlossen.

---

6. Codeblock-Regeln

Codeblöcke sind ein zentrales Element des Lernbuchs. Sie müssen auf Desktop und Mobilgerät lesbar sein.

Pflichtregeln:

Vorlage / Textauszug
  • dunkler Hintergrund
  • ausreichender Kontrast
  • horizontales Scrollen
  • keine hellgraue Schrift auf hellem Hintergrund
  • einheitliche Farben für alle Kapitel
  • keine wechselnden Highlight-Themes
  • Copy-Button nur als Komfortfunktion
  • Code bleibt auch ohne Copy-Button sichtbar

Beispiel-CSS:

css
.code-card {
  background: #1e1f22;
  border: 1px solid #3c3f44;
  border-radius: 14px;
  overflow: hidden;
  margin: 1.25rem 0;
}

.code-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #2b2d31;
  color: #d7dae0;
  padding: .55rem .8rem;
  font-size: .85rem;
}

pre {
  margin: 0;
  padding: 1rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: #dcdfe4;
  font-size: .92rem;
  line-height: 1.55;
}

.tok-keyword { color: #cf8ef4; }
.tok-string  { color: #6aab73; }
.tok-number  { color: #2aacb8; }
.tok-comment { color: #7a7f8b; }
.tok-type    { color: #56a8f5; }
.tok-anno    { color: #b3ae60; }

Das ist kein vollständiger Parser. Für eine offline-fähige Single-HTML reicht ein kontrolliertes, einfaches Highlighting aus.

---

7. Inline-SVG-Regeln

Diagramme sollen nicht als separate Bilddateien eingebunden werden. Frühere Versionen hatten Probleme mit fehlenden Assets. Deshalb gilt:

Vorlage / Textauszug

SVGs werden inline in die HTML geschrieben.

Keine externen PNGs.

Keine externen SVG-Dateien.

Keine CDN-Diagrammbibliotheken.

Ein Diagramm soll fachlich passend sein. Es darf nicht nur dekorativ sein.

Gute SVG-Typen für dieses Projekt:

Vorlage / Textauszug
  • Zielarchitektur
  • Netzwerkzonen
  • Cloud-VPC
  • OpenShift Namespace-Struktur
  • OIDC Login Flow
  • Outbox Flow
  • GitOps Deployment Flow
  • Backup/Restore Flow
  • Observability Pipeline
  • VM zu OpenShift Migration

---

8. Suchfunktion als Komfort, nicht als Pflicht

Eine Suche ist hilfreich, aber sie darf die Datei nicht abhängig von JavaScript machen. Wenn JavaScript deaktiviert ist oder auf iPhone/Safari unzuverlässig läuft, muss das Lernbuch weiterhin lesbar sein.

Die Suche darf:

Vorlage / Textauszug
  • Treffer markieren
  • passende Kapitel öffnen
  • zum ersten Treffer springen

Die Suche darf nicht:

Vorlage / Textauszug
  • Inhalte dauerhaft aus dem DOM entfernen
  • Navigation ersetzen
  • Kapitel nur noch über JavaScript anzeigen
  • localStorage zwingend benötigen

---

9. Qualitätsprüfung vor dem HTML-Build

Vor dem HTML-Build wird geprüft, ob verbotene Muster existieren.

Beispiele für verbotene Texte:

Vorlage / Textauszug

Praxisdurchlauf 1

Praxisdurchlauf 2

integrierte Vertiefung

Anhang 16

Entscheidung 2

Risiko 2

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Die Prüfung soll den Build abbrechen, wenn solche Muster gefunden werden.

Beispiel:

python
from pathlib import Path

forbidden = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 2",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

root = Path(class="str">"mhepl_md_steps")
errors = []

for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            errors.append((file.name, pattern))

if errors:
    for filename, pattern in errors:
        print(fclass="str">"FEHLER: {filename} enthält verbotenes Muster: {pattern}")
    raise SystemExit(1)

print(class="str">"OK: keine verbotenen Muster gefunden")

---

10. Wiederholungsprüfung

Nicht jede Wiederholung ist falsch. Fachbegriffe dürfen wieder vorkommen. Aber ganze Absätze dürfen nicht mehrfach mit minimalen Wortänderungen erscheinen.

Eine einfache Prüfung kann Absätze normalisieren und Duplikate erkennen.

python
from pathlib import Path
import re
from collections import defaultdict

root = Path(class="str">"mhepl_md_steps")
seen = defaultdict(list)

for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    paragraphs = re.split(rclass="str">"\n\s*\n", text)
    for idx, paragraph in enumerate(paragraphs, start=1):
        clean = re.sub(rclass="str">"\s+", class="str">" ", paragraph.strip())
        if len(clean) < 240:
            continue
        key = clean.lower()
        seen[key].append((file.name, idx))

duplicates = {k: v for k, v in seen.items() if len(v) > 1}

if duplicates:
    for text, locations in duplicates.items():
        print(class="str">"DOPPELTER ABSATZ:")
        print(text[:240], class="str">"...")
        print(locations)
        print()
    raise SystemExit(1)

print(class="str">"OK: keine langen identischen Absätze gefunden")

Diese Prüfung ersetzt keine menschliche Kontrolle, aber sie verhindert die schlimmsten Generatorfehler.

---

11. Generator-Regel: Inhalt nicht künstlich aufblasen

Die HTML-Datei darf über 2 MB groß sein. Aber die Größe muss aus echtem Inhalt entstehen.

Nicht erlaubt:

Vorlage / Textauszug
  • gleiche Absätze mit ausgetauschtem Begriff
  • lange künstliche Tabellen
  • nummerierte Füllzeilen
  • identische Praxisblöcke
  • sinnlose Referenzkarten
  • wiederholte Glossarlisten als Seitenfüller

Erlaubt:

Vorlage / Textauszug
  • zusätzliche echte Kapitel
  • längere Enterprise-Szenarien
  • echte Codebeispiele
  • konkrete Runbooks
  • konkrete ADRs
  • echte Übungen mit Musterlösungen
  • fachlich passende SVG-Diagramme

---

12. Finale Build-Ausgabe

Das finale Delivery-Paket soll mindestens enthalten:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_FINAL/
├── Modern_Hybrid_Enterprise_Platform_Lab_FINAL_SINGLE.html
├── Modern_Hybrid_Enterprise_Platform_Lab_FINAL_MASTER.md
├── markdown_steps/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   └── ...
├── quality/
│   ├── quality-report.md
│   └── checks.txt
└── README.md

Die HTML-Datei ist der bequeme Lesestand. Die Markdown-Dateien bleiben die fachliche Quelle. Der Quality-Report dokumentiert, welche Prüfungen durchgeführt wurden.

---

13. Abnahmekriterien für die HTML

Die finale HTML gilt nur als abgenommen, wenn folgende Punkte erfüllt sind:

Vorlage / Textauszug
  1. Datei öffnet lokal im Browser.
  2. Navigation ist sichtbar und klappbar.
  3. Navigation ist standardmäßig geschlossen.
  4. Content-Kapitel sind standardmäßig geschlossen.
  5. Ankerlinks springen zum richtigen Kapitel.
  6. Zielkapitel kann geöffnet werden.
  7. Codeblöcke sind lesbar.
  8. Codeblöcke scrollen horizontal auf iPhone.
  9. SVGs werden angezeigt.
  10. Keine externen Assets werden benötigt.
  11. Keine verbotenen Muster kommen vor.
  12. Keine langen identischen Absätze kommen vor.
  13. README erklärt Startdatei und Struktur.
  14. ZIP enthält HTML, Master-MD, Einzelschritte und Prüfbericht.

---

14. Übung

Erstelle eine eigene kurze Checkliste für den HTML-Build. Sie soll mindestens zehn Prüfpunkte enthalten und zwischen fachlicher Qualität und technischer Qualität unterscheiden.

---

15. Musterlösung

Eine mögliche Checkliste:

Vorlage / Textauszug

Fachliche Qualität:

  1. Jedes Kapitel hat eigenen fachlichen Zweck.
  2. Keine künstlich wiederholten Absätze.
  3. Praxis ist im Fachkapitel integriert.
  4. Begriffe werden im Kontext erklärt.
  5. Codebeispiele passen zum Kapitel.
  6. Runbooks enthalten echte Symptome und Prüfkommandos.

Technische Qualität:

  1. Single-File-HTML öffnet offline.
  2. Navigation verwendet details/summary.
  3. Content-Kapitel sind standardmäßig geschlossen.
  4. Codeblöcke sind dunkel und lesbar.
  5. Tabellen oder lange Zeilen scrollen horizontal.
  6. SVGs sind inline eingebettet.
  7. ZIP enthält README und Markdown-Quellen.
  8. Qualitätsprüfung wurde dokumentiert.

Diese Checkliste verhindert, dass die HTML-Erzeugung wieder nur als kosmetischer Export betrachtet wird.

Finale Abnahme vor der großen HTML-Erzeugung

1. Warum eine finale Abnahme nötig ist

Bevor aus den Markdown-Kapiteln ein großes HTML-Masterbuch erzeugt wird, braucht das Projekt eine klare Abnahme. Diese Abnahme ist nicht nur ein technischer Test. Sie prüft, ob das Lernbuch fachlich sinnvoll, strukturell sauber, mobil nutzbar und frei von künstlichen Wiederholungen ist.

In den früheren Fassungen ist ein typisches Problem sichtbar geworden: Inhalte wurden zu stark automatisch aufgefüllt. Dadurch entstanden Abschnitte, die zwar lang waren, aber inhaltlich kaum Neues sagten. Genau das darf in der finalen HTML-Erzeugung nicht wieder passieren.

Die finale Abnahme beantwortet deshalb vier Fragen:

Vorlage / Textauszug
  1. Ist der Inhalt fachlich unterschiedlich und wirklich lernbar?
  2. Ist die Struktur stabil genug für eine große Single-File-HTML?
  3. Funktioniert die Navigation ohne fragile JavaScript-Abhängigkeit?
  4. Sind Codeblöcke, SVGs, Runbooks und Übungen sauber eingebettet?

Erst wenn diese Fragen positiv beantwortet sind, sollte das finale HTML erzeugt werden.

---

2. Abnahmeziel

Das Ziel ist eine robuste Version 1.0 des Lernbuchs Enterprise Platform Lab. Diese Version soll später als Markdown, HTML und ZIP ausgeliefert werden können.

Die finale HTML-Datei soll kein Generator-Demonstrator sein, sondern ein nutzbares Lernbuch. Der Leser soll darin nicht nur Begriffe finden, sondern Zusammenhänge verstehen: Warum eine hybride Plattform entsteht, wie Anwendungen und Infrastruktur zusammenhängen, wie Security eingebaut wird, wie Betrieb funktioniert und wie Codex oder Claude Code im Enterprise-Projekt sinnvoll eingesetzt werden.

Die Abnahme prüft daher nicht nur formale Dinge wie Dateinamen. Sie prüft auch, ob der Inhalt als Lernmaterial funktioniert.

---

3. Inhaltliche Abnahmekriterien

Ein Kapitel gilt nur dann als ausreichend, wenn es mehrere unterschiedliche Inhaltsformen enthält.

Erwartet werden:

Vorlage / Textauszug
  • fachliche Erklärung
  • Unternehmenskontext
  • Architekturentscheidung
  • technisches Beispiel
  • Code oder Konfiguration
  • Betriebsbezug
  • Security-Bezug
  • typische Fehler
  • Übung
  • Musterlösung

Nicht jedes Kapitel muss gleich lang sein. Aber jedes Kapitel muss einen klaren Zweck haben.

Ein schlechtes Kapitel wäre:

Vorlage / Textauszug

Das Thema ist wichtig. Es braucht README, ADR, Manifest und Runbook. Der Nachweis erfolgt über Dokumentation.

Ein gutes Kapitel erklärt dagegen konkret:

Vorlage / Textauszug

Warum der Order Service das Outbox Pattern nutzt,

welche Datenbanktabellen dafür nötig sind,

wie ein Publisher fehlerhafte Events wiederholt,

welche Metrik einen Backlog sichtbar macht,

welches Runbook im Störfall genutzt wird,

und welche Fehler bei Transaktionen auftreten können.

Diese Unterscheidung ist entscheidend.

---

4. Verbotene Muster

Vor der HTML-Erzeugung müssen bestimmte Muster ausgeschlossen werden. Sie haben in früheren Fassungen zu künstlichem Inhalt geführt.

Nicht erlaubt sind:

Vorlage / Textauszug

integrierte Vertiefung 1

integrierte Vertiefung 2

Praxisdurchlauf 1

Praxisdurchlauf 2

Anhang 16

Entscheidung 2

Risiko 3

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit und reproduzierbare Umsetzung.

Solche Formulierungen zeigen meist, dass ein Generator nummerierte Blöcke erzeugt hat, statt echte Fachabschnitte zu schreiben.

Für die finale HTML-Version gilt:

Vorlage / Textauszug

Keine nummerierten Füllabschnitte.

Keine künstlichen Anhangsserien.

Keine Tabellen als Hauptinhalt.

Keine wiederholten Absatzschablonen.

Keine isolierten Praxisblöcke am Ende.

Praxis muss in die passenden Fachkapitel integriert werden.

---

5. Strukturprüfung der Markdown-Dateien

Vor dem HTML-Build müssen alle Markdown-Dateien geprüft werden.

Erwartete Dateien:

Vorlage / Textauszug

01_Teil_1_Unternehmenskontext_und_Zielbild.md

02_Teil_2_Infrastruktur_Fundament.md

03_Teil_3_Cloud_Architektur.md

04_Teil_4_VM_und_Bare_Metal_Betrieb.md

05_Teil_5_Kubernetes_und_OpenShift_Plattform.md

06_Teil_6_Identity_Security_und_Governance.md

07_Teil_7_Moderne_Anwendungen.md

08_Teil_8_Daten_Messaging_und_Integration.md

09_Teil_9_Observability_und_Betrieb.md

10_Teil_10_Backup_Restore_und_Disaster_Recovery.md

11_Teil_11_DevOps_GitOps_und_Release_Management.md

12_Teil_12_Codex_und_Claude_Code_im_Enterprise_Projekt.md

13_Teil_13_Komplettes_Beispielprojekt.md

14_Teil_14_Uebungen_Runbooks_und_Musterloesungen.md

15_Teil_15_Integrierte_Begriffserklaerungen_und_Fachindex.md

16_Teil_16_Qualitaetspruefung_Konsolidierung_und_HTML_Vorbereitung.md

17_Teil_17_Finale_Markdown_Konsolidierung_und_Masterbuch_Aufbau.md

18_Teil_18_Finale_HTML_Erzeugung_aus_Markdown.md

19_Teil_19_Finale_Delivery_Struktur_ZIP_und_Abnahmepruefung.md

20_Teil_20_Versionierung_Pflegeplan_und_Weiterentwicklung.md

21_Teil_21_Uebergang_von_Markdown_zum_finalen_HTML_Masterbuch.md

22_Teil_22_Finale_HTML_Build_Vorlage_und_Generator_Regeln.md

23_Teil_23_Finale_Abnahme_vor_der_HTML_Erzeugung.md

Diese Reihenfolge wird später für Navigation, Kapitelnummern und ZIP-Struktur verwendet.

---

6. Prüfung gegen Wiederholungen

Eine einfache Wiederholungsprüfung kann Absätze vergleichen. Der Zweck ist nicht, jeden ähnlichen Satz zu verbieten. Manche Begriffe müssen wiederkehren. Der Zweck ist, große kopierte Textblöcke zu finden.

Beispielskript:

python
from pathlib import Path
from collections import Counter
import re

base = Path(class="str">"mhepl_md_steps")
paragraphs = []

for file in sorted(base.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    raw_blocks = re.split(rclass="str">"\n\s*\n", text)
    for block in raw_blocks:
        normalized = re.sub(rclass="str">"\s+", class="str">" ", block.strip())
        if len(normalized) > 180:
            paragraphs.append((file.name, normalized))

counter = Counter(block for _, block in paragraphs)

for block, count in counter.most_common():
    if count > 1:
        print(class="str">"DUPLICATE", count, block[:240])

Wenn das Skript lange identische Absätze findet, muss vor dem HTML-Build nachgearbeitet werden.

---

7. Prüfung gegen verbotene Begriffe

Zusätzlich wird gegen bekannte Problemwörter geprüft.

python
from pathlib import Path

forbidden = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

base = Path(class="str">"mhepl_md_steps")
failed = False

for file in sorted(base.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            failed = True
            print(fclass="str">"FOUND: {pattern} in {file.name}")

if failed:
    raise SystemExit(class="str">"Abnahme fehlgeschlagen")

print(class="str">"Keine verbotenen Muster gefunden")

Diese Prüfung ist einfach, aber sehr nützlich. Sie verhindert, dass alte Generatorreste in die finale Version gelangen.

---

8. Codeblock-Abnahme

Codeblöcke sind im Lernbuch zentral. Sie dürfen nicht nur dekorativ sein.

Jeder wichtige Codeblock soll eine Funktion haben:

Vorlage / Textauszug
  • Java-Code erklärt Anwendungslogik oder Security.
  • YAML erklärt Kubernetes, OpenShift, GitOps oder CI/CD.
  • SQL erklärt Datenmodell, Migration oder Analyse.
  • Bash erklärt Diagnose, Betrieb oder Build-Schritte.
  • HCL erklärt Cloud- oder Infrastrukturressourcen.
  • Markdown erklärt ADRs, README oder Runbooks.

Für die finale HTML-Version gelten zusätzlich diese Regeln:

Vorlage / Textauszug
  • dunkler Codeblock-Hintergrund
  • gut lesbare Schrift
  • Syntaxfarben nach JetBrains-Dark-Prinzip
  • horizontales Scrollen auf iPhone, iPad und Desktop
  • Copy-Button nur als Komfortfunktion
  • Code bleibt auch ohne JavaScript lesbar

Ein Codeblock darf nie schwarze Schrift auf dunklem Hintergrund oder hellgraue Schrift auf hellem Hintergrund erzeugen.

---

9. SVG- und Diagramm-Abnahme

SVGs sollen nicht zufällig eingefügt werden. Jedes Diagramm muss ein Thema besser verständlich machen.

Geeignete Diagrammarten:

Vorlage / Textauszug
  • Zielarchitektur
  • Netzwerkzonen
  • Cloud-VPC
  • OpenShift-Namespaces
  • OIDC-Loginfluss
  • Outbox-Eventfluss
  • Backup-und-Restore-Fluss
  • GitOps-Deploymentfluss
  • Observability-Pipeline
  • VM-zu-OpenShift-Migration

Für die finale HTML gilt:

Vorlage / Textauszug
  • SVGs inline einbetten
  • keine externen Bilddateien voraussetzen
  • Diagramme mit Textlabels versehen
  • mobile Ansicht beachten
  • keine riesigen überbreiten SVGs ohne Scrollcontainer

Ein Diagramm ist gut, wenn der Leser nach dem Betrachten eine Beziehung schneller versteht als nur durch Text.

---

10. Navigationsabnahme

Die Navigation war in früheren HTML-Versionen fehleranfällig. Deshalb wird die finale Navigation bewusst einfach gehalten.

Erlaubt:

html
<details>
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5">Überblick</a>
  <a href=class="str">"#teil-5-namespaces">Namespaces</a>
</details>

Nicht empfohlen:

Vorlage / Textauszug
  • komplexe verschachtelte onclick-Handler
  • Navigation, die nur mit JavaScript sichtbar wird
  • Hash-Router
  • localStorage als Pflichtfunktion
  • dynamische Kapitelmanipulation als Grundlage

Die HTML-Datei soll auch dann nutzbar bleiben, wenn JavaScript nicht oder nur teilweise funktioniert.

---

11. Content-Kategorien standardmäßig geschlossen

Der Nutzer möchte klappbare Kategorien, die standardmäßig geschlossen sind. Das wird mit nativen details umgesetzt.

Beispiel:

html
<details class=class="str">"chapter" id=class="str">"teil-8">
  <summary>Teil 8: Daten, Messaging und Integration</summary>
  <section>
    <h2>Warum Datenbesitz wichtiger ist als gemeinsame Datenbanken</h2>
    <p>...</p>
  </section>
</details>

Wichtig: Die Inhalte dürfen nicht durch CSS display:none ohne zugänglichen Öffnungsmechanismus versteckt werden. Native Details-Elemente sind stabiler, besonders bei lokalen HTML-Dateien auf iPhone und iPad.

---

12. iPhone- und Safari-Abnahme

Lokale HTML-Dateien auf iPhone oder iPad können problematisch sein. Daher muss die finale HTML bewusst robust sein.

Prüfpunkte:

Vorlage / Textauszug
  • Datei öffnet ohne externe Internetverbindung.
  • Navigation ist mit Tippen bedienbar.
  • Kategorien lassen sich öffnen und schließen.
  • Codeblöcke scrollen horizontal.
  • Tabellen, falls vorhanden, sprengen nicht das Layout.
  • Copy-Button ist optional, nicht notwendig.
  • Suche ist Komfort, nicht Pflicht.
  • Inhalte bleiben auch ohne JavaScript erreichbar.

Falls eine Funktion auf iPhone nicht zuverlässig ist, darf sie nicht kritisch sein.

---

13. Qualitätsbericht

Vor dem finalen HTML-Build sollte ein kurzer Qualitätsbericht erzeugt werden.

Vorlage:

Vorlage / Textauszug

Quality Report

Dateien
  • Markdown-Teile: 23
  • README vorhanden: ja
  • ZIP geprüft: ja
Verbotene Muster
  • integrierte Vertiefung: nicht gefunden
  • Praxisdurchlauf: nicht gefunden
  • Anhang 16: nicht gefunden
  • Entscheidung 2/3: nicht gefunden
  • Risiko 2/3: nicht gefunden
Wiederholungsprüfung
  • lange identische Absätze: keine kritischen Treffer
  • künstliche Tabellenserien: keine kritischen Treffer
HTML-Vorbereitung
  • Navigation: details/summary
  • Content-Kategorien: geschlossen
  • Codeblöcke: JetBrains-Dark-Prinzip
  • SVGs: inline
  • externe Assets: keine
Ergebnis

Freigegeben für finale HTML-Erzeugung.

Dieser Bericht muss nicht lang sein. Er soll aber zeigen, dass die Datei nicht blind generiert wurde.

---

14. Entscheidung: Wann wird HTML erzeugt?

HTML sollte erst erzeugt werden, wenn die Markdown-Fassung inhaltlich stabil ist.

Noch nicht geeignet:

Vorlage / Textauszug
  • Kapitel fehlen
  • Wiederholungen sind sichtbar
  • Beispiele sind nur Platzhalter
  • Codeblöcke sind unvollständig
  • Navigation ist nicht geplant

Geeignet:

Vorlage / Textauszug
  • alle Hauptteile sind vorhanden
  • Code und Text sind sinnvoll verteilt
  • Übungen und Musterlösungen existieren
  • Qualitätsregeln sind dokumentiert
  • verbotene Muster wurden geprüft
  • HTML-Struktur ist festgelegt

Mit Teil 23 ist die fachliche Abnahme vorbereitet. Danach kann die HTML-Erzeugung als eigener Build-Schritt erfolgen.

---

15. Übung

Erstelle eine eigene Abnahmeliste für ein Lernbuch oder Projektpaket.

Die Liste soll prüfen:

Vorlage / Textauszug
  1. Inhaltliche Qualität
  2. Wiederholungen
  3. Dateistruktur
  4. Codeblöcke
  5. Diagramme
  6. Navigation
  7. mobile Nutzung
  8. ZIP-Paket

---

16. Musterlösung

Eine mögliche Abnahmeliste:

Vorlage / Textauszug

Inhalt:

Jedes Kapitel erklärt ein eigenes Thema und enthält keine sichtbaren Füllabsätze.

Wiederholungen:

Lange identische Absätze werden mit einem Skript geprüft.

Dateistruktur:

Alle Kapitel liegen als nummerierte Markdown-Dateien vor.

Codeblöcke:

Code ist fachlich sinnvoll, lesbar und mit Sprache gekennzeichnet.

Diagramme:

SVGs sind inline und erklären reale Architekturbeziehungen.

Navigation:

HTML nutzt details/summary und echte Ankerlinks.

Mobile Nutzung:

Codeblöcke scrollen, Kategorien sind tippbar, JavaScript ist nicht kritisch.

ZIP:

Das ZIP enthält Markdown, HTML, README und Quality Report. Es wird mit unzip -t geprüft.

Diese Musterlösung ist absichtlich praktisch. Sie kann direkt als Checkliste für die finale Version 1.0 verwendet werden.

---

17. Merksätze

Vorlage / Textauszug

Eine große HTML-Datei ist nur wertvoll, wenn der Inhalt nicht künstlich aufgeblasen ist.

Die beste Reparatur gegen schlechte Generatorausgaben ist eine klare Abnahme vor der Erzeugung.

Navigation muss robust sein, nicht beeindruckend kompliziert.

Codeblöcke und Diagramme müssen erklären, nicht dekorieren.

Mobile Nutzbarkeit entsteht durch einfache HTML-Struktur und nicht durch fragile Skripte.

Ein ZIP-Paket ist erst fertig, wenn Inhalt, Struktur und Prüfung nachvollziehbar sind.

Finale HTML-Erzeugung als Single-File-Masterbuch

1. Ziel dieses Teils

Nach den vorherigen Abnahmeregeln darf die finale HTML-Datei nicht mehr durch künstliches Aufblasen entstehen. Sie soll aus den vorhandenen Markdown-Teilen erzeugt werden und dabei die Fachstruktur bewahren. Der Zweck dieses Teils ist deshalb nicht, noch mehr Inhalt anzuhängen, sondern den sauberen Übergang vom geprüften Markdown-Material zum endgültigen HTML-Masterbuch zu beschreiben.

Die HTML-Erzeugung verfolgt vier Ziele:

Vorlage / Textauszug
  1. Der Inhalt bleibt fachlich und kapitelorientiert.
  2. Die Navigation ist robust und funktioniert auch ohne komplexes JavaScript.
  3. Codeblöcke sind gut lesbar, dunkel und farbig hervorgehoben.
  4. Die Datei ist als lokale Single-File-HTML auf PC, iPad und iPhone nutzbar.

Wichtig ist: Die HTML-Datei ist keine neue inhaltliche Version. Sie ist eine Ausgabeform des Masterbuchs. Inhaltliche Ergänzungen werden weiterhin zuerst in Markdown geschrieben und geprüft. Erst danach entsteht eine neue HTML-Version.

---

2. Warum Single-File-HTML

Bei lokalen HTML-Dateien entstehen oft Probleme, wenn Bilder, CSS-Dateien oder JavaScript-Dateien in separaten Ordnern liegen. Besonders auf iPhone und iPad werden lokale Pfade nicht immer zuverlässig aufgelöst. Auch Messenger-Apps, die Dateien weiterleiten, verlieren manchmal Begleitordner oder blockieren Skripte.

Deshalb wird für dieses Projekt eine Single-File-HTML verwendet.

Das bedeutet:

Vorlage / Textauszug
  • CSS liegt direkt im HTML.
  • JavaScript liegt direkt im HTML.
  • SVG-Diagramme liegen direkt im HTML.
  • Es gibt keine externen CDN-Links.
  • Es gibt keine separaten Asset-Ordner.
  • Codeblöcke brauchen keine externe Highlighting-Bibliothek.

Der Nachteil ist eine größere HTML-Datei. Der Vorteil ist eine deutlich robustere Nutzung. Für ein Lernbuch ist das wichtiger als eine elegante technische Trennung.

---

3. Grundstruktur der finalen HTML-Datei

Die finale HTML-Datei verwendet eine einfache Struktur:

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>
    /* gesamtes Layout, Navigation, Codeblöcke und responsive Regeln */
  </style>
</head>
<body>
  <header class=class="str">"topbar">
    <h1>Enterprise Platform Lab</h1>
  </header>

  <div class=class="str">"layout">
    <aside class=class="str">"sidebar">
      <nav>
        <!-- klappbare Navigation -->
      </nav>
    </aside>

    <main class=class="str">"content">
      <!-- klappbare Fachkapitel -->
    </main>
  </div>

  <script>
    // nur Komfortfunktionen, keine Pflichtfunktionen
  </script>
</body>
</html>

Diese Struktur ist bewusst konservativ. Sie vermeidet komplexe Frameworks und dynamische Seitenlogik. Das Buch soll zuerst lesbar sein. Komfortfunktionen kommen erst danach.

---

4. Navigation mit details und summary

Die Navigation wird nicht mit verschachtelten Buttons gebaut. Stattdessen verwendet sie native HTML-Elemente.

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5">Überblick</a>
  <a href=class="str">"#teil-5-namespaces">Namespaces</a>
  <a href=class="str">"#teil-5-rbac">RBAC</a>
  <a href=class="str">"#teil-5-networkpolicy">NetworkPolicy</a>
</details>

Diese Lösung hat mehrere Vorteile:

Vorlage / Textauszug
  • Sie funktioniert ohne JavaScript.
  • Sie ist auf iPhone/Safari stabiler als eigene Button-Logik.
  • Sie ist für Screenreader besser verständlich.
  • Sie lässt sich mit CSS gut gestalten.
  • Sie kann standardmäßig geschlossen sein.

Die Navigation ist standardmäßig geschlossen. Der Leser entscheidet, welchen Bereich er öffnet. Dadurch wirkt die linke Seite nicht überladen.

---

5. Content-Kategorien standardmäßig geschlossen

Auch im Hauptinhalt werden die großen Teile klappbar dargestellt.

html
<details class=class="str">"chapter" id=class="str">"teil-6">
  <summary>
    <span class=class="str">"chapter-number">Teil 6</span>
    <span class=class="str">"chapter-title">Identity, Security und Governance</span>
  </summary>

  <section class=class="str">"chapter-body">
    <h2>Identity, Security und Governance</h2>
    <p>...</p>
  </section>
</details>

Der Inhalt ist dadurch nicht verschwunden. Er ist nur eingeklappt. Das ist wichtig, weil eine sehr große HTML-Datei sonst auf mobilen Geräten unübersichtlich wird.

Eine gute Regel lautet:

Vorlage / Textauszug

Navigation geschlossen.

Content-Kategorien geschlossen.

Angeklickter Zielbereich darf automatisch geöffnet werden.

Das automatische Öffnen ist Komfort. Ohne JavaScript kann der Nutzer den Bereich manuell öffnen.

---

Jedes Kapitel bekommt eine stabile ID. Diese IDs dürfen später nicht zufällig geändert werden, weil sonst interne Links brechen.

Gute IDs:

Vorlage / Textauszug

teil-1-unternehmenskontext

teil-5-kubernetes-openshift

teil-8-outbox-pattern

teil-10-disaster-recovery

teil-13-komplettes-beispielprojekt

Schlechte IDs:

Vorlage / Textauszug

section-1

block-42

anhang-16

praxisdurchlauf-3

integrierte-vertiefung-7

Stabile IDs sind Teil der Buchqualität. Wenn der Nutzer eine Stelle wiederfinden möchte, muss der Link auch in späteren Versionen möglichst erhalten bleiben.

---

7. Codeblöcke im JetBrains-Dark-Prinzip

Codeblöcke sind in diesem Projekt besonders wichtig. Deshalb bekommen sie ein einheitliches dunkles Design.

Anforderungen:

Vorlage / Textauszug
  • dunkler Hintergrund
  • gut lesbare helle Schrift
  • farbige Tokens für Keywords, Strings, Kommentare, Zahlen und Annotationen
  • horizontales Scrollen auf kleinen Bildschirmen
  • Copy-Button rechts oben, wo JavaScript funktioniert
  • ohne JavaScript bleibt der Code lesbar

Beispielstruktur:

html
<div class=class="str">"code-card">
  <div class=class="str">"code-head">
    <span>Java</span>
    <button type=class="str">"button" class=class="str">"copy">Kopieren</button>
  </div>
  <pre><code class=class="str">"language-java">...</code></pre>
</div>

Das Syntax-Highlighting kann durch einfache Vorverarbeitung beim HTML-Build entstehen. Es muss nicht im Browser zur Laufzeit berechnet werden. Dadurch bleibt die Datei stabiler.

---

8. Beispiel für CSS der Codeblöcke

css
.code-card {
  background: #1e1f22;
  border: 1px solid #3c3f44;
  border-radius: 14px;
  overflow: hidden;
  margin: 1.2rem 0;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.22);
}

.code-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #2b2d31;
  color: #dfe1e5;
  padding: 0.55rem 0.8rem;
  font-size: 0.9rem;
}

.code-card pre {
  margin: 0;
  padding: 1rem;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.code-card code {
  color: #dfe1e5;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.92rem;
  line-height: 1.55;
}

.tok-keyword { color: #cf8ef4; }
.tok-string  { color: #6aab73; }
.tok-comment { color: #7a7f86; font-style: italic; }
.tok-number  { color: #2aacb8; }
.tok-anno    { color: #bbb529; }

Dieser Stil ist nicht identisch mit einer IDE, orientiert sich aber am JetBrains-Dark-Prinzip: dunkler Hintergrund, klare Kontraste und farbige syntaktische Signale.

---

9. Inline-SVG statt Bilddateien

Diagramme werden als Inline-SVG eingebettet. Dadurch gibt es keine fehlenden Bilddateien.

Beispiel:

html
class=
class
="str">"diagram" viewBox=
class
="str">"
0

0

900

420
" role=
class
="str">"img" aria-label=
class
="str">"Hybrid Enterprise Architektur">

class="str">"
40
" y=
class
="str">"
40
" width=
class
="str">"
190
" height=
class
="str">"
80
" rx=
class
="str">"
14
">


class="str">"
135
" y=
class
="str">"
85
" text-anchor=
class
="str">"middle">Customer Portal

SVGs müssen fachlich benannt werden. Ein Diagramm darf nicht nur dekorativ sein. Es soll einen Zusammenhang erklären, zum Beispiel:

Vorlage / Textauszug
  • Netzwerkzonen
  • OpenShift-Architektur
  • Outbox-Flow
  • Backup-und-Restore-Ablauf
  • GitOps-Promotion
  • Identity-Flow mit Keycloak

---

10. Suchfunktion als Komfortfunktion

Eine Suchfunktion ist hilfreich, aber sie darf nicht notwendig sein, um das Buch zu lesen.

Die Suchfunktion kann:

Vorlage / Textauszug
  • Text im Hauptbereich suchen
  • passende Kapitel öffnen
  • Treffer hervorheben
  • zur ersten Fundstelle springen

Aber ohne JavaScript muss das Buch weiterhin vollständig nutzbar sein. Deshalb darf kein Kapitel ausschließlich durch JavaScript geladen werden.

Nicht erlaubt:

Vorlage / Textauszug
  • Inhalte per JavaScript nachladen
  • Navigation nur mit onclick ohne href
  • Kapitel vollständig verstecken, wenn JS nicht läuft
  • localStorage als Pflicht für Grundfunktion

Erlaubt:

Vorlage / Textauszug
  • Copy-Button
  • Suchfeld
  • Zielkapitel automatisch öffnen
  • kleine Treffer-Hervorhebung

---

11. Build-Prozess

Der Build-Prozess besteht aus mehreren Schritten.

Vorlage / Textauszug
  1. Markdown-Dateien einsammeln
  2. Reihenfolge prüfen
  3. verbotene Muster prüfen
  4. doppelte Absätze prüfen
  5. Codeblöcke erkennen
  6. Codeblöcke in HTML-Karten umwandeln
  7. Überschriften in stabile IDs umwandeln
  8. Navigation erzeugen
  9. Content-Kapitel als details/summary erzeugen
  10. Inline-SVGs einbetten
  11. CSS und Komfort-JavaScript einfügen
  12. finale HTML schreiben
  13. ZIP mit HTML, Markdown-Quelle, README und Quality-Report erstellen

Der wichtigste Schritt ist nicht die HTML-Formatierung. Der wichtigste Schritt ist die Prüfung vor dem Build.

---

12. Beispiel: Python-Build-Skelett

python
from pathlib import Path
import html
import re

SOURCE_DIR = Path(class="str">"mhepl_md_steps")
OUT_DIR = Path(class="str">"dist")
OUT_DIR.mkdir(exist_ok=True)

FORBIDDEN = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 2",
]

def read_parts():
    files = sorted(SOURCE_DIR.glob(class="str">"*_Teil_*.md"))
    return [(path.name, path.read_text(encoding=class="str">"utf-8")) for path in files]

def check_forbidden(parts):
    errors = []
    for name, text in parts:
        for pattern in FORBIDDEN:
            if pattern.lower() in text.lower():
                errors.append(fclass="str">"{name}: verbotenes Muster gefunden: {pattern}")
    return errors

def slugify(title):
    value = title.lower()
    value = value.replace(class="str">"ä", class="str">"ae").replace(class="str">"ö", class="str">"oe").replace(class="str">"ü", class="str">"ue").replace(class="str">"ß", class="str">"ss")
    value = re.sub(rclass="str">"[^a-z0-9]+", class="str">"-", value).strip(class="str">"-")
    return value or class="str">"abschnitt"

parts = read_parts()
errors = check_forbidden(parts)
if errors:
    raise SystemExit(class="str">"\n".join(errors))

print(fclass="str">"OK: {len(parts)} Markdown-Dateien bereit für HTML-Build")

Dieses Skelett ersetzt keinen vollständigen Generator, zeigt aber die Richtung: Erst prüfen, dann bauen.

---

13. Qualitätsbericht als Pflichtdatei

Jede finale HTML-Ausgabe bekommt einen Quality-Report.

Der Report enthält:

Vorlage / Textauszug
  • Anzahl Markdown-Dateien
  • Anzahl Codeblöcke
  • Anzahl Diagramme
  • gefundene verbotene Muster
  • Prüfung doppelter Absätze
  • HTML-Dateigröße
  • ZIP-Prüfung
  • Datum der Erzeugung
  • bekannte Grenzen

Ein Beispiel:

Vorlage / Textauszug

Quality Report

Ergebnis

Status: bestanden

Prüfungen
  • Verbotene Muster: keine gefunden
  • Doppelte Absätze: keine kritischen Wiederholungen
  • Generische Titel: keine gefunden
  • Codeblöcke: vorhanden
  • Inline-SVGs: vorhanden
  • Navigation: details/summary
  • Content-Kategorien: standardmäßig geschlossen
  • Externe Assets: keine
Bekannte Grenzen

Die HTML-Datei ist groß. Auf älteren Mobilgeräten kann das erste Öffnen etwas dauern.

Dieser Bericht verhindert, dass später wieder unbemerkt mangelhafte HTML-Pakete entstehen.

---

14. Abnahme der finalen HTML-Datei

Die HTML-Datei gilt erst als abgenommen, wenn folgende Punkte erfüllt sind:

Vorlage / Textauszug
  • Öffnen auf Desktop-Browser funktioniert.
  • Öffnen auf iPhone/Safari funktioniert.
  • Navigation ist sichtbar und klappbar.
  • Kategorien im Content sind klappbar.
  • Standardzustand ist geschlossen.
  • Ankerlinks springen zum richtigen Bereich.
  • Codeblöcke sind lesbar und scrollbar.
  • SVGs werden angezeigt.
  • Keine externen Dateien fehlen.
  • Keine generischen Füllbegriffe sind sichtbar.
  • ZIP enthält HTML, Markdown-Quelle, README und Quality-Report.

Erst danach ist die Ausgabe eine echte Version.

---

15. Übung

Entwirf eine eigene Mini-Abnahme für die finale HTML-Datei.

Die Abnahme soll mindestens enthalten:

Vorlage / Textauszug
  1. Navigationstest
  2. Codeblock-Test
  3. SVG-Test
  4. Mobile-Test
  5. Wiederholungsprüfung
  6. ZIP-Prüfung

Beschreibe zu jedem Punkt, wie du den Test durchführen würdest.

---

16. Musterlösung

Vorlage / Textauszug

Navigationstest:

Ich öffne die HTML-Datei lokal im Browser. Danach öffne ich mehrere Navigationsgruppen und klicke auf interne Links. Der Zielbereich muss korrekt angesprungen werden.

Codeblock-Test:

Ich prüfe Java-, YAML-, SQL- und Bash-Codeblöcke. Die Schrift muss gut lesbar sein, der Hintergrund dunkel und horizontales Scrollen muss auf schmalem Bildschirm funktionieren.

SVG-Test:

Ich prüfe alle Diagramme im Buch. Jedes Diagramm muss sichtbar sein und darf nicht auf eine externe Datei verweisen.

Mobile-Test:

Ich öffne die HTML-Datei auf iPhone oder iPad. Navigation und Content-Kategorien müssen mit Tippen aufklappbar sein. Das Buch darf nicht von JavaScript abhängig sein.

Wiederholungsprüfung:

Ich führe ein Skript aus, das längere identische Absätze erkennt. Kritische Wiederholungen müssen vor der HTML-Erzeugung entfernt werden.

ZIP-Prüfung:

Ich öffne das ZIP und prüfe, ob HTML, Markdown-Quelle, README und Quality-Report enthalten sind. Danach teste ich das ZIP mit unzip -t oder einem vergleichbaren Tool.

---

17. Merksätze

Vorlage / Textauszug

HTML ist die Ausgabeform, nicht die Quelle der Wahrheit.

Die Quelle der Wahrheit bleibt Markdown.

Single-File-HTML ist für lokale Lernbücher robuster als viele getrennte Assets.

Navigation muss zuerst ohne JavaScript funktionieren.

Codeblöcke müssen auf Desktop und Mobilgerät lesbar sein.

Ein Quality-Report schützt vor Rückfällen in künstlich erzeugte Füllinhalte.

Finale Master-HTML, README, Quality-Report und Drive-Ablage

1. Ziel dieses Teils

Nach den bisherigen Teilen ist das Projekt nicht mehr nur eine Sammlung einzelner Lerntexte. Es ist ein vorbereitetes Fachbuch mit klaren Regeln, einer schrittweisen Markdown-Struktur und einer späteren HTML-Ausgabe. Dieser Teil beschreibt, wie aus dem bisherigen Material ein finales Delivery-Paket entsteht, ohne wieder in die Fehler der ersten Generator-Versionen zurückzufallen.

Die zentrale Idee bleibt: Das HTML darf nicht durch künstliche Wiederholungen wachsen. Es soll groß werden, weil die Inhalte fachlich stark sind. Es soll navigierbar sein, weil die Struktur sauber ist. Es soll auf iPhone, iPad und Desktop funktionieren, weil wichtige Funktionen nicht zwingend von JavaScript abhängen.

Der finale Auslieferungsstand besteht aus mehreren Bausteinen:

Vorlage / Textauszug
  1. konsolidierte Markdown-Datei
  2. einzelne Markdown-Teildateien
  3. Single-File-HTML
  4. README
  5. Quality-Report
  6. optionaler Build-Ordner mit Hilfsskripten
  7. ZIP-Paket
  8. Kopie in Google Drive

Dieser Teil ist also kein weiteres Fachkapitel zu Cloud oder Kubernetes. Er ist der Übergang vom geschriebenen Lernbuch zur kontrollierten Veröffentlichung.

---

2. Warum ein Quality-Report notwendig ist

In den ersten HTML-Fassungen sind mehrere typische Fehler entstanden: generische Titel, wiederholte Absätze, künstliche Tabellen, aufgeblasene Fülltexte und defekte Navigation. Genau deshalb braucht das Projekt eine formale Abnahme. Der Quality-Report ist kein Bürokratie-Dokument. Er ist der Nachweis, dass die Datei vor der Auslieferung geprüft wurde.

Ein guter Quality-Report beantwortet nicht nur die Frage „Wurde eine Datei erzeugt?“, sondern auch:

Vorlage / Textauszug

Wurde der Inhalt gegen Wiederholungen geprüft?

Sind die Navigationseinträge fachlich benannt?

Sind die Content-Kategorien standardmäßig geschlossen?

Funktionieren Ankerlinks ohne komplizierte JavaScript-Abhängigkeit?

Sind Codeblöcke lesbar?

Sind SVGs inline eingebettet?

Gibt es verbotene Muster wie „Praxisdurchlauf 3“?

Gibt es künstliche Tabellen mit nummerierten Füllzeilen?

Ist das ZIP vollständig?

Damit wird Qualität überprüfbar. Der Quality-Report ist besonders wichtig, wenn später HTML oder PDF aus dem Material erzeugt wird. Ohne diesen Schritt würde jede neue Ausgabe wieder riskieren, alte Fehler mitzunehmen.

---

3. Finale Delivery-Struktur

Das finale Paket sollte nicht aus vielen unklaren Dateien mit Namen wie final_fix_neu_v2_clean.html bestehen. Solche Namen zeigen meistens, dass die Ausgabe mehrfach repariert wurde und niemand mehr weiß, welche Datei gültig ist.

Die saubere Struktur lautet:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY/
├── README.md
├── QUALITY_REPORT.md
├── CHANGELOG.md
├── md/
│   ├── 00_Masterbook.md
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   └── ...
├── web/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html
├── build/
│   ├── build_html.py
│   ├── check_repetition.py
│   └── check_forbidden_patterns.py
└── archive/
    └── optional_alte_abnahmen.md

Für die laufenden Zwischenstände reicht weiterhin ein kompaktes ZIP mit den Markdown-Dateien. Für die finale Version wird diese Delivery-Struktur wichtig.

---

4. README für das finale Paket

Die README ist der Einstiegspunkt. Sie muss kurz erklären, was im Paket enthalten ist und welche Datei geöffnet werden soll. Sie ist nicht der Ort für lange Theorie. Sie ist die Gebrauchsanleitung.

Eine gute README sieht so aus:

Vorlage / Textauszug

Enterprise Platform Lab

Dieses Paket enthält das Lernbuch „Enterprise Platform Lab“.

Start

Für die HTML-Version öffnen:

`web/Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html`

Für die Markdown-Quelle öffnen:

`md/00_Masterbook.md`

Inhalt

Das Lernbuch behandelt:

  • Unternehmenskontext und Zielbild
  • Infrastruktur-Fundament
  • Cloud-Architektur
  • VM- und Bare-Metal-Betrieb
  • Kubernetes und OpenShift
  • Identity, Security und Governance
  • moderne Anwendungen
  • Daten, Messaging und Integration
  • Observability und Betrieb
  • Backup, Restore und Disaster Recovery
  • DevOps, GitOps und Release Management
  • Codex und Claude Code im Enterprise-Projekt
  • komplettes Beispielprojekt
  • Übungen, Runbooks und Musterlösungen
Hinweise

Die HTML-Datei ist eine Single-File-Ausgabe ohne externe Assets.

SVGs, CSS und Codeblock-Darstellung sind inline enthalten.

Die Navigation nutzt native HTML-Elemente und bleibt auch ohne JavaScript grundsätzlich nutzbar.

Diese README verhindert, dass der Leser im ZIP suchen muss.

---

5. CHANGELOG-Regel

Ein Changelog beschreibt nicht jede kleine interne Änderung, sondern relevante Versionen. Für dieses Projekt reicht ein einfaches Format.

Vorlage / Textauszug

CHANGELOG

1.0.0
  • erste vollständige Fachbuchversion
  • Markdown-Teile 1 bis 25 konsolidiert
  • Single-File-HTML erzeugt
  • Quality-Report ergänzt
  • ZIP-Paket erstellt
0.9.0
  • Markdown-Grundstruktur abgeschlossen
  • Wiederholungsregeln definiert
  • HTML-Build-Regeln vorbereitet

Wichtig ist: Versionen sollen nachvollziehbar sein. Wenn später eine neue HTML-Datei erzeugt wird, soll klar sein, ob sie Version 1.0.0, 1.1.0 oder eine experimentelle Zwischenfassung ist.

---

6. Quality-Report-Vorlage

Der Quality-Report wird später automatisch oder manuell gefüllt. Eine Vorlage kann so aussehen:

Vorlage / Textauszug

QUALITY REPORT

Paket

Name: Modern_Hybrid_Enterprise_Platform_Lab

Version: 1.0.0

Datum: 2026-07-06

Dateien
  • Markdown Masterbook: vorhanden
  • einzelne Markdown-Dateien: vorhanden
  • Single-File-HTML: vorhanden
  • README: vorhanden
  • CHANGELOG: vorhanden
Inhaltsprüfung
  • keine generischen „Praxisdurchlauf X“-Titel gefunden
  • keine generischen „integrierte Vertiefung X“-Titel gefunden
  • keine generischen „Anhang 16“-Navigationseinträge gefunden
  • keine künstlichen Entscheidung-2/Risiko-2-Tabellen gefunden
  • keine stark wiederholten Absätze über Grenzwert gefunden
HTML-Prüfung
  • Navigation nutzt details/summary
  • Navigation standardmäßig geschlossen
  • Content-Kategorien standardmäßig geschlossen
  • Ankerlinks vorhanden
  • Codeblöcke mit dunklem Hintergrund vorhanden
  • SVGs inline eingebettet
  • keine externen CDN-Links gefunden
Mobile Prüfung
  • horizontales Scrollen für Codeblöcke aktiv
  • Tabellen brechen nicht das Layout
  • wichtige Inhalte ohne JavaScript lesbar
Ergebnis

Status: akzeptiert / nicht akzeptiert

Bemerkung:

Diese Vorlage kann später in das finale ZIP aufgenommen werden.

---

7. Prüfskript gegen verbotene Muster

Das folgende Skript prüft zentrale Begriffe, die in den früheren fehlerhaften Fassungen aufgetreten sind. Es ersetzt keine fachliche Prüfung, aber es verhindert bekannte Fehler.

python
from pathlib import Path

ROOT = Path(class="str">"markdown")
FORBIDDEN = [
    class="str">"Praxisdurchlauf 1",
    class="str">"Praxisdurchlauf 2",
    class="str">"Praxisdurchlauf 3",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
]

failed = False

for path in ROOT.glob(class="str">"*.md"):
    text = path.read_text(encoding=class="str">"utf-8")
    for pattern in FORBIDDEN:
        if pattern in text:
            print(fclass="str">"FEHLER: {path} enthält verbotenes Muster: {pattern}")
            failed = True

if failed:
    raise SystemExit(1)

print(class="str">"OK: Keine verbotenen Muster gefunden.")

Wichtig ist: Die Liste kann erweitert werden, wenn neue schlechte Muster erkannt werden. Qualität entsteht hier durch Lernen aus Fehlern.

---

8. Prüfskript gegen Absatzwiederholungen

Ein zweites Skript sucht nach mehrfach identischen langen Absätzen. Es erkennt nicht jede semantische Wiederholung, aber es findet Copy/Paste-Blöcke.

python
from pathlib import Path
from collections import Counter
import re

ROOT = Path(class="str">"markdown")
paragraphs = []

for path in ROOT.glob(class="str">"*.md"):
    text = path.read_text(encoding=class="str">"utf-8")
    chunks = re.split(rclass="str">"\n\s*\n", text)
    for chunk in chunks:
        normalized = re.sub(rclass="str">"\s+", class="str">" ", chunk.strip())
        if len(normalized) > 180:
            paragraphs.append((normalized, path.name))

counter = Counter(p for p, _ in paragraphs)
failed = False

for paragraph, count in counter.items():
    if count >= 3:
        print(class="str">"FEHLER: Absatz mehrfach vorhanden:")
        print(class="str">"Anzahl:", count)
        print(paragraph[:300], class="str">"...")
        failed = True

if failed:
    raise SystemExit(1)

print(class="str">"OK: Keine auffälligen Absatzwiederholungen gefunden.")

Dieses Skript schützt besonders gegen den Fehler, bei dem nur einzelne Wörter ausgetauscht wurden, der Absatz aber strukturell gleich blieb.

---

9. HTML-Navigation: finale Regel

Die Navigation darf nicht mehr kompliziert gebaut werden. Sie muss robust bleiben.

Richtig:

html
<nav class=class="str">"sidebar">
  <details>
    <summary>Teil 1: Unternehmenskontext und Zielbild</summary>
    <a href=class="str">"#teil-1-ziel">Ziel des Kapitels</a>
    <a href=class="str">"#teil-1-architektur">Zielarchitektur</a>
  </details>
</nav>

Der Content dazu:

html
<main>
  <details class=class="str">"content-section" id=class="str">"teil-1">
    <summary>Teil 1: Unternehmenskontext und Zielbild</summary>
    <section id=class="str">"teil-1-ziel">
      <h2>Ziel des Kapitels</h2>
      <p>...</p>
    </section>
  </details>
</main>

Nicht erlaubt:

Vorlage / Textauszug

Navigation über verschachtelte Buttons ohne Fallback.

Navigation, die nur mit JavaScript sichtbar wird.

Kapitel, die per display:none versteckt bleiben und nicht mehr erreichbar sind.

Links ohne passende Ziel-ID.

Diese Regel ist besonders wichtig für iPhone und lokale HTML-Dateien.

---

10. Codeblock-Regel für finale HTML

Codeblöcke müssen lesbar und konsistent sein. Das Projekt folgt dem JetBrains-Dark-Prinzip: dunkler Hintergrund, gute Kontraste, farbige Syntax, horizontales Scrollen und keine hellgraue Schrift auf hellem Grund.

Minimalstruktur:

html
<figure class=class="str">"code-card">
  <figcaption>OrderApplicationService.java</figcaption>
  <pre><code class=class="str">"language-java">...</code></pre>
</figure>

CSS-Prinzip:

css
.code-card {
  background: #1e1f22;
  color: #bcbec4;
  border-radius: 16px;
  overflow: hidden;
}

.code-card pre {
  overflow-x: auto;
  padding: 1rem;
}

.tok-keyword { color: #cf8e6d; }
.tok-string  { color: #6aab73; }
.tok-number  { color: #2aacb8; }
.tok-comment { color: #7a7e85; }
.tok-type    { color: #bcbec4; }

Für Markdown bleibt normaler Codeblock erhalten. Die farbige Darstellung entsteht erst beim HTML-Build.

---

11. Inline-SVG-Regel

SVGs dürfen nicht als separate Dateien fehlen. Deshalb werden zentrale Diagramme inline in die HTML geschrieben. Für Markdown kann zusätzlich Mermaid verwendet werden. In der finalen HTML müssen wichtige Diagramme aber sichtbar sein, auch wenn kein externes Renderer-System geladen wird.

Geeignete Diagramme:

Vorlage / Textauszug

Gesamtarchitektur

Netzwerkzonen

OpenShift Deployment Flow

Outbox Event Flow

Backup/Restore Ablauf

GitOps Promotion Flow

Identity/OIDC Ablauf

Observability Pipeline

Ein SVG muss nicht künstlerisch perfekt sein. Es muss verständlich sein. Wichtiger als Dekoration ist die fachliche Aussage.

---

12. Google-Drive-Ablage

Das finale ZIP wird zusätzlich in Google Drive abgelegt. Der Zielordner ist der konfigurierte Projektordner chatgpt.

Die Ablage soll nicht nur „irgendwo“ erfolgen. Die Datei muss einen klaren Namen bekommen:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY_1_0_0.zip

Für Zwischenstände gilt:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_MD_STEPS_01_25.zip

Wenn später HTML erzeugt wird:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_HTML_MASTER_1_0_0.zip

So bleibt klar, welche Datei nur Markdown-Schritte enthält und welche Datei die finale HTML-Ausgabe enthält.

---

13. Abnahme vor Google Drive Upload

Vor jedem Upload nach Google Drive muss geprüft werden:

Vorlage / Textauszug
  1. ZIP existiert lokal.
  2. ZIP lässt sich öffnen.
  3. README ist enthalten.
  4. neue Markdown-Datei ist enthalten.
  5. alte Markdown-Dateien fehlen nicht.
  6. Dateiname ist eindeutig.
  7. Upload-Zielordner ist korrekt.
  8. Upload liefert Datei-ID oder Link zurück.

Wenn der Upload keine Datei-ID oder keinen Link zurückgibt, gilt er nicht als sicher bestätigt. In diesem Fall wird erneut geprüft oder die lokale ZIP-Datei bleibt als primärer Download erhalten.

---

14. Runbook: Drive-Upload schlägt fehl

Vorlage / Textauszug

Runbook: Google-Drive-Upload schlägt fehl

Symptom

Die Datei soll nach Google Drive kopiert werden, aber es wird keine Datei-ID oder kein Link zurückgegeben.

Prüfung
  1. Prüfen, ob die lokale ZIP-Datei existiert.
  2. Prüfen, ob der Zielordner korrekt ist.
  3. Upload erneut mit expliziter Parent-Folder-ID durchführen.
  4. Nach Upload auf `success: true`, `id`, `title` und `parent_id` achten.
  5. Wenn keine Bestätigung kommt, lokale Datei als Download-Link bereitstellen.
Akzeptanz

Der Upload gilt nur als erfolgreich, wenn eine Drive-Datei-ID vorliegt.

Dieses Runbook ist aus dem tatsächlichen Projektverlauf entstanden. Beim ersten Versuch gab es keine sichere Bestätigung. Erst ein erneuter Upload mit explizitem Zielordner war sauber nachvollziehbar.

---

15. Übung

Erstelle eine eigene Abnahmeliste für das finale HTML-Paket. Die Liste soll mindestens zehn Prüfpunkte enthalten und zwischen Inhalt, Technik, Navigation, Mobile und ZIP unterscheiden.

---

16. Musterlösung

Vorlage / Textauszug

Inhalt:

  1. Keine künstlichen Wiederholungsabsätze.
  2. Keine generischen Titel wie Praxisdurchlauf oder integrierte Vertiefung.
  3. Jedes Hauptkapitel enthält Fließtext, Beispiel, Fehleranalyse und Betriebssicht.

Technik:

  1. HTML ist eine Single-File-Datei ohne externe Assets.
  2. Codeblöcke sind dunkel, farbig und horizontal scrollbar.
  3. SVGs sind inline eingebettet.

Navigation:

  1. Sidebar verwendet details/summary.
  2. Content-Kategorien sind standardmäßig geschlossen.
  3. Ankerlinks führen zu existierenden IDs.

Mobile:

  1. Datei bleibt auf iPhone/Safari lesbar.
  2. Wichtige Inhalte sind nicht von JavaScript abhängig.

ZIP:

  1. README, Markdown, HTML und Quality-Report sind enthalten.
  2. ZIP wurde mit einem Testlauf geprüft.
  3. Dateiname ist eindeutig und ohne final_fix_v2-Chaos.

---

17. Merksätze

Vorlage / Textauszug

Ein Delivery-Paket ist erst fertig, wenn es lesbar, prüfbar und nachvollziehbar ist.

Eine große HTML-Datei ist nur dann wertvoll, wenn die Größe durch echten Inhalt entsteht.

Der Quality-Report schützt vor Wiederholung alter Fehler.

Google Drive Upload gilt erst als erledigt, wenn eine Datei-ID vorliegt.

Saubere Dateinamen sind Teil der Qualität.

Abschluss, Übergabe und nächste Projektphase

1. Zweck dieses Abschlussteils

Dieser Teil beschreibt, wie das bisherige Markdown-Mastermaterial geordnet abgeschlossen, an eine spätere HTML-/PDF-Erzeugung übergeben und in eine nächste Projektphase überführt wird. Der Schwerpunkt liegt nicht mehr darauf, neue Fachthemen künstlich hinzuzufügen. Der Schwerpunkt liegt auf Übergabefähigkeit.

Ein Lernbuch dieser Größe wird schnell unübersichtlich, wenn es nur aus vielen einzelnen Dateien besteht. Deshalb braucht der Abschluss klare Regeln: Was gilt als fertig? Was darf später ergänzt werden? Was muss vor einer HTML-Erzeugung geprüft werden? Welche Dateien gehören in das finale Paket? Welche Qualitätsgrenzen verhindern, dass die spätere Ausgabe wieder künstlich aufgeblasen wird?

Die wichtigste Abschlussregel lautet:

Vorlage / Textauszug

Das Mastermaterial ist nur dann reif für HTML, wenn der fachliche Inhalt stabil ist.

HTML ist Ausgabeformat, nicht Ersatz für fehlende Qualität.

2. Übergabestatus der bisherigen Teile

Die bisherigen Teile bilden eine vollständige Grundlage für das Enterprise Platform Lab. Sie decken nicht nur eine Anwendung ab, sondern die gesamte Umgebung: Unternehmenskontext, Infrastruktur, Cloud, VM, Bare Metal, Kubernetes/OpenShift, Security, moderne Anwendungen, Daten, Messaging, Observability, Backup, DevOps, AI-Unterstützung, Beispielprojekt, Übungen, Begriffe, Qualitätsprüfung und finale Ausgabevorbereitung.

Damit ist die erste große Inhaltsphase abgeschlossen. Die Inhalte sind als Markdown-Dateien getrennt gespeichert. Das ist sinnvoll, weil einzelne Teile später unabhängig verbessert werden können, ohne das ganze Buch neu anfassen zu müssen.

Die aktuelle Materialsammlung besteht aus:

Vorlage / Textauszug

01 Unternehmenskontext und Zielbild

02 Infrastruktur-Fundament

03 Cloud-Architektur

04 VM- und Bare-Metal-Betrieb

05 Kubernetes- und OpenShift-Plattform

06 Identity, Security und Governance

07 Moderne Anwendungen

08 Daten, Messaging und Integration

09 Observability und Betrieb

10 Backup, Restore und Disaster Recovery

11 DevOps, GitOps und Release Management

12 Codex und Claude Code im Enterprise-Projekt

13 Komplettes Beispielprojekt

14 Übungen, Runbooks und Musterlösungen

15 Integrierte Begriffserklärungen und Fachindex

16 Qualitätsprüfung, Konsolidierung und HTML-Vorbereitung

17 Finale Markdown-Konsolidierung und Masterbuch-Aufbau

18 Finale HTML-Erzeugung aus Markdown

19 Finale Delivery-Struktur, ZIP und Abnahmeprüfung

20 Versionierung, Pflegeplan und Weiterentwicklung

21 Übergang von Markdown zum finalen HTML-Masterbuch

22 Finale HTML-Build-Vorlage und Generator-Regeln

23 Finale Abnahme vor der HTML-Erzeugung

24 Finale HTML-Erzeugung als Single-File-Masterbuch

25 Finale Master-HTML, README, Quality-Report und Drive-Ablage

26 Abschluss, Übergabe und nächste Projektphase

3. Was jetzt nicht mehr passieren darf

Nach so vielen Teilen ist die Versuchung groß, einfach weiter Kapitel anzuhängen. Genau das soll vermieden werden. Ab jetzt darf kein Inhalt mehr ergänzt werden, nur um die Datei größer wirken zu lassen.

Nicht mehr erlaubt sind:

Vorlage / Textauszug
  • künstliche Praxisdurchläufe mit Nummern
  • generische Vertiefungsblöcke
  • Anhänge ohne fachlichen Zweck
  • Tabellen als Ersatz für Erklärung
  • Glossarlisten als Hauptinhalt
  • wiederholte Absätze mit ausgetauschten Begriffen
  • HTML-Aufblähung durch Copy/Paste
  • Diagramme ohne Bezug zum Kapitel
  • Code ohne Einordnung

Erlaubt sind nur fachliche Ergänzungen, die eine konkrete Lücke schließen. Eine Ergänzung muss erklären, warum sie nötig ist, in welchen Teil sie gehört und welchen vorhandenen Abschnitt sie verbessert.

4. Übergabepaket für die nächste Phase

Das Übergabepaket soll nicht chaotisch wachsen. Es braucht eine feste Struktur:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab/
├── md/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── ...
│   └── 26_Teil_26_Abschluss_Uebergabe_und_naechste_Projektphase.md
├── build/
│   ├── web/
│   ├── book/
│   └── quality-reports/
├── quality/
│   ├── forbidden-patterns.txt
│   ├── duplicate-check-report.md
│   └── acceptance-report.md
├── README.md
└── CHANGELOG.md

Für die aktuelle Schritt-ZIP reicht weiterhin die einfache Struktur mit den Markdown-Dateien und einer README. Für die finale Ausgabephase wird daraus ein vollständiges Delivery-Paket mit HTML, PDF, Quality-Report und optionaler Google-Drive-Ablage.

5. Abnahme vor der finalen HTML-Erzeugung

Bevor HTML erzeugt wird, muss das Markdown-Material geprüft werden. Die Prüfung soll nicht nur technische Fehler suchen, sondern auch inhaltliche Schwächen erkennen.

Die wichtigsten Prüffragen sind:

Vorlage / Textauszug

Gibt es wiederholte Absätze?

Gibt es künstliche Serientitel?

Besteht ein Kapitel hauptsächlich aus Tabellen?

Sind Begriffe im Kontext erklärt?

Sind Praxisabschnitte in Fachkapitel integriert?

Sind Codebeispiele erklärt?

Sind Runbooks konkret genug?

Sind Übungen mit Musterlösungen vorhanden?

Sind Navigationstitel fachlich?

Wenn eine dieser Fragen negativ beantwortet wird, soll nicht sofort HTML gebaut werden. Dann muss zuerst das Markdown-Material korrigiert werden.

6. Qualitätsprüfung mit einfachem Musterkatalog

Für die nächste Phase wird ein Musterkatalog verwendet. Er enthält Begriffe und Formulierungen, die in der finalen HTML nicht vorkommen dürfen.

Vorlage / Textauszug

Praxisdurchlauf 1

Praxisdurchlauf 2

integrierte Vertiefung

Anhang 16

Entscheidung 2

Risiko 2

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Bei Security, Hardening und Audit beginnt die Arbeit mit

Diese Muster sind nicht deshalb verboten, weil die Wörter grundsätzlich falsch wären. Sie sind verboten, weil sie im bisherigen Projekt als Hinweis auf künstlich generierten Fülltext aufgetreten sind.

7. Inhaltliche Konsolidierung statt blinde Zusammenführung

Die finale Markdown-Konsolidierung darf nicht einfach alle Dateien aneinanderkleben. Sie muss Übergänge prüfen.

Beispiel: Teil 5 erklärt OpenShift, Teil 6 erklärt Security, Teil 7 erklärt moderne Anwendungen. In der finalen Fassung müssen diese Teile aufeinander aufbauen. Es sollte nicht so wirken, als wären drei separate Artikel nebeneinandergelegt worden.

Gute Übergänge sind zum Beispiel:

Vorlage / Textauszug

Nachdem die Plattformgrundlagen beschrieben wurden, wird im nächsten Teil erklärt, wie Identität und Security darauf aufbauen.

Oder:

Vorlage / Textauszug

Die vorherige Security-Sicht wird nun auf konkrete Anwendungen angewendet: Customer Portal, Order Service und Billing Service.

Solche Übergänge machen aus einer Sammlung ein Buch.

8. HTML-Zielzustand

Die finale HTML-Ausgabe soll eine einzige robuste Datei sein. Sie soll offline funktionieren, auf iPhone/Safari stabil sein und keine externen Assets benötigen.

Die technische Zielstruktur lautet:

Vorlage / Textauszug

Single HTML

Inline CSS

Inline SVG

native details/summary Navigation

Content-Kategorien standardmäßig geschlossen

Codeblöcke mit dunklem JetBrains-Dark-Prinzip

Copy-Button nur als Komfortfunktion

Suche nur als Komfortfunktion

keine Pflichtabhängigkeit von JavaScript

Wichtige Funktionen dürfen nicht davon abhängen, dass JavaScript perfekt funktioniert. Das war eine zentrale Lehre aus den früheren iPhone-/Safari-Problemen.

9. PDF-Zielzustand

Eine spätere PDF-Ausgabe muss andere Regeln erfüllen als HTML. PDF ist nicht interaktiv. Deshalb dürfen klappbare Bereiche nicht einfach geschlossen exportiert werden. Für PDF müssen alle Inhalte sichtbar sein.

PDF-Regeln:

Vorlage / Textauszug

alle Kapitel sichtbar

Inhaltsverzeichnis mit Hierarchie

Codeblöcke gut lesbar

dunkler Codehintergrund mit hohem Kontrast

keine abgeschnittenen langen Zeilen ohne Alternative

Diagramme sichtbar und eingebettet

Seitenumbrüche nicht mitten in Überschriften

Die PDF-Ausgabe darf nicht aus einer kaputten HTML-Ansicht erzeugt werden, bei der Inhalte eingeklappt bleiben.

10. Google-Drive-Ablage

Für die Drive-Ablage gilt: Nur geprüfte ZIPs sollen hochgeladen werden. Ein Upload ist erst abgeschlossen, wenn Drive eine Datei-ID und einen Link zurückgibt.

Ein erfolgreicher Upload muss mindestens dokumentieren:

Vorlage / Textauszug

Dateiname

Google-Drive-Datei-ID

Zielordner-ID

Upload-Zeitpunkt

lokale ZIP-Version

Damit lässt sich später nachvollziehen, welche Version tatsächlich abgelegt wurde.

11. Nächste Projektphase

Die nächste sinnvolle Projektphase ist nicht weiteres Schreiben, sondern Produktion der finalen Ausgabe.

Empfohlene Reihenfolge:

Vorlage / Textauszug
  1. Alle Markdown-Dateien zu einem Master-Markdown konsolidieren
  2. Wiederholungsprüfung durchführen
  3. verbotene Muster prüfen
  4. Master-Markdown korrigieren
  5. Single-File-HTML erzeugen
  6. HTML-Navigation prüfen
  7. Codeblöcke prüfen
  8. mobile Darstellung prüfen
  9. ZIP mit HTML, MD, README und Quality-Report erzeugen
  10. ZIP optional in Google Drive hochladen

Erst danach sollte über PDF nachgedacht werden. PDF wird besser, wenn die HTML- und Markdown-Grundlage stabil ist.

12. Übung

Erstelle eine eigene Abnahmeliste für das Projekt. Die Liste soll mindestens zehn Punkte enthalten und unterscheiden zwischen:

Vorlage / Textauszug

Inhalt

Technik

Navigation

Codeblöcke

Mobile Darstellung

Ablage

13. Musterlösung

Eine mögliche Abnahmeliste:

Vorlage / Textauszug
  1. Alle Kapitel haben fachliche Überschriften.
  2. Keine verbotenen Muster kommen vor.
  3. Keine wiederholten Absätze mit nur ausgetauschten Begriffen kommen vor.
  4. Tabellen sind nur ergänzend, nicht Hauptinhalt.
  5. Codeblöcke sind farbig und lesbar.
  6. SVGs sind inline eingebettet.
  7. Navigation funktioniert mit nativen details/summary.
  8. Content-Kategorien sind standardmäßig geschlossen.
  9. HTML funktioniert ohne externe Assets.
  10. README erklärt Startdatei und Paketinhalt.
  11. Quality-Report dokumentiert Prüfungen.
  12. ZIP lässt sich fehlerfrei entpacken.
  13. Optionaler Drive-Upload liefert Datei-ID und Zielordner.

14. Abschlussmerksätze

Vorlage / Textauszug

Ein großes Lernbuch braucht nicht nur Umfang, sondern Struktur.

HTML ist erst dann sinnvoll, wenn der Inhalt sauber ist.

Qualitätsprüfung verhindert, dass frühere Generatorfehler zurückkommen.

Eine robuste Offline-HTML muss ohne externe Assets funktionieren.

Drive-Ablage ist erst bestätigt, wenn eine Datei-ID vorliegt.

Die nächste Phase ist die finale Ausgabeproduktion, nicht weiteres künstliches Anhängen.

Vorbereitung der finalen HTML-Erzeugung aus allen Markdown-Teilen

1. Ziel dieses Teils

Dieser Teil bereitet die finale HTML-Erzeugung praktisch vor. Bis hierher wurden die Inhalte bewusst als getrennte Markdown-Schritte aufgebaut. Das war wichtig, weil die vorherigen HTML-Fassungen zu stark durch automatische Wiederholung, künstliche Tabellen und generische Überschriften geprägt waren. Die neue Arbeitsweise trennt zuerst den fachlichen Inhalt von der späteren Darstellung.

Der nächste große Schritt ist nicht einfach „Markdown in HTML umwandeln“. Eine gute HTML-Ausgabe braucht eine saubere Konsolidierung. Dabei werden alle Markdown-Dateien in eine fachlich stabile Reihenfolge gebracht, Überschriften vereinheitlicht, Codeblöcke geprüft, interne Links vorbereitet und Qualitätsregeln angewendet. Erst danach darf die Single-File-HTML erzeugt werden.

Das Ziel lautet:

Vorlage / Textauszug

Aus vielen geprüften Markdown-Kapiteln entsteht ein robustes, offlinefähiges HTML-Masterbuch.

Dieses Masterbuch soll nicht wieder künstlich aufgeblasen werden. Umfang entsteht nur durch echten Inhalt, Beispiele, Erklärungen, Diagramme und Code.

---

2. Warum keine direkte Sofort-HTML-Erzeugung

Eine direkte Sofort-HTML-Erzeugung klingt praktisch, führt aber oft zu Fehlern. Wenn der Generator direkt HTML schreibt, entstehen schnell kaputte Navigationen, doppelte IDs, falsche Anker, uneinheitliche Codeblöcke oder überladene Abschnitte. Besonders bei iPhone/Safari und lokalen HTML-Dateien sind komplizierte JavaScript-Navigationen anfällig.

Deshalb wird die finale HTML-Datei nicht als neues Inhaltsprojekt betrachtet, sondern als Rendering des geprüften Markdown-Materials.

Die Regel lautet:

Vorlage / Textauszug

Markdown ist Quelle.

HTML ist Ausgabe.

ZIP ist Delivery.

Wenn später ein Fehler gefunden wird, wird nicht direkt wild in der HTML-Datei repariert. Stattdessen wird zuerst die Markdown-Quelle korrigiert und danach die HTML-Ausgabe neu erzeugt. Dadurch bleibt das Projekt wartbar.

---

3. Konsolidierungsreihenfolge

Die finale Zusammenführung verwendet eine feste Reihenfolge. Diese Reihenfolge folgt dem Lernpfad: zuerst Unternehmenskontext, dann Infrastruktur, dann Plattform, Anwendungen, Betrieb, Delivery und Übergabe.

Vorlage / Textauszug

01 Unternehmenskontext und Zielbild

02 Infrastruktur-Fundament

03 Cloud-Architektur

04 VM- und Bare-Metal-Betrieb

05 Kubernetes- und OpenShift-Plattform

06 Identity, Security und Governance

07 Moderne Anwendungen

08 Daten, Messaging und Integration

09 Observability und Betrieb

10 Backup, Restore und Disaster Recovery

11 DevOps, GitOps und Release Management

12 Codex und Claude Code im Enterprise-Projekt

13 Komplettes Beispielprojekt

14 Übungen, Runbooks und Musterlösungen

15 Integrierte Begriffserklärungen und Fachindex

16 Qualitätsprüfung, Konsolidierung und HTML-Vorbereitung

17 Finale Markdown-Konsolidierung und Masterbuch-Aufbau

18 Finale HTML-Erzeugung aus Markdown

19 Finale Delivery-Struktur, ZIP und Abnahmeprüfung

20 Versionierung, Pflegeplan und Weiterentwicklung

21 Übergang von Markdown zum finalen HTML-Masterbuch

22 Finale HTML-Build-Vorlage und Generator-Regeln

23 Finale Abnahme vor der HTML-Erzeugung

24 Finale HTML-Erzeugung als Single-File-Masterbuch

25 Finale Master-HTML, README, Quality-Report und Drive-Ablage

26 Abschluss, Übergabe und nächste Projektphase

27 Vorbereitung der finalen HTML-Erzeugung aus allen Markdown-Teilen

Diese Reihenfolge wird im finalen Inhaltsverzeichnis und in der Sidebar gespiegelt. Die Navigation darf keine technischen Dateinamen zeigen, sondern fachliche Titel.

---

4. Fachliche Kapitel statt technische Dateinamen

Im ZIP dürfen Dateinamen nummeriert sein, weil das Sortierung und Pflege erleichtert. In der HTML-Navigation sollen jedoch fachliche Namen stehen.

Nicht gut:

Vorlage / Textauszug

01_Teil_1_Unternehmenskontext_und_Zielbild.md

02_Teil_2_Infrastruktur_Fundament.md

Besser in der HTML-Navigation:

Vorlage / Textauszug

Unternehmenskontext und Zielbild

Infrastruktur-Fundament

Cloud-Architektur

VM- und Bare-Metal-Betrieb

Kubernetes- und OpenShift-Plattform

Die Dateinummern bleiben nur im ZIP sichtbar. Die Leserführung im HTML bleibt fachlich.

---

5. HTML-Struktur der finalen Ausgabe

Die finale HTML-Datei soll eine einfache, robuste Struktur verwenden.

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>
    /* zentrales Offline-CSS */
  </style>
</head>
<body>
  <header class=class="str">"topbar">
    <h1>Enterprise Platform Lab</h1>
  </header>

  <main class=class="str">"layout">
    <aside class=class="str">"sidebar">
      <nav aria-label=class="str">"Inhaltsverzeichnis">
        <!-- details/summary Navigation -->
      </nav>
    </aside>

    <article class=class="str">"content">
      <!-- klappbare Fachkapitel -->
    </article>
  </main>

  <script>
    // nur Komfortfunktionen, keine Pflichtlogik
  </script>
</body>
</html>

Wichtig ist: Die Inhalte müssen auch ohne JavaScript erreichbar bleiben. JavaScript darf Copy-Buttons, Suche oder sanftes Öffnen von Zielbereichen verbessern, aber nicht die Grundfunktion tragen.

---

6. Navigation mit details/summary

Die Navigation wird mit nativen HTML-Elementen aufgebaut.

html
<details class=class="str">"nav-group">
  <summary>Infrastruktur</summary>
  <a href=class="str">"#teil-02-infrastruktur-fundament">Infrastruktur-Fundament</a>
  <a href=class="str">"#teil-03-cloud-architektur">Cloud-Architektur</a>
  <a href=class="str">"#teil-04-vm-und-bare-metal-betrieb">VM- und Bare-Metal-Betrieb</a>
</details>

Vorteile:

Vorlage / Textauszug
  • funktioniert ohne JavaScript
  • funktioniert lokal auf iPhone/Safari besser
  • ist zugänglicher als selbstgebaute Buttons
  • lässt sich gut stylen
  • bleibt auch bei deaktiviertem Script nutzbar

Alle Navigationsgruppen sind standardmäßig geschlossen. Der Leser öffnet nur den Bereich, den er braucht.

---

7. Content-Kategorien standardmäßig geschlossen

Auch der Hauptinhalt wird mit details/summary aufgebaut.

html
<details class=class="str">"chapter" id=class="str">"teil-05-kubernetes-und-openshift-plattform">
  <summary>
    <h2>Kubernetes- und OpenShift-Plattform</h2>
  </summary>
  <section>
    <h3>Warum Kubernetes im Enterprise-Kontext wichtig ist</h3>
    <p>...</p>
  </section>
</details>

Diese Struktur vermeidet riesige Scrollwände. Das ist besonders wichtig bei einer großen HTML-Datei über 2 MB. Der Inhalt bleibt vollständig vorhanden, aber optisch kontrollierbar.

Wichtig: Der summary darf nicht mit komplexen Buttons verschachtelt werden. Native Interaktion bleibt stabiler.

---

8. Regeln für Überschriften und IDs

Jede Überschrift bekommt eine stabile ID. Die ID wird aus dem Titel erzeugt.

Beispiel:

Vorlage / Textauszug

Titel: Kubernetes- und OpenShift-Plattform

ID: teil-05-kubernetes-und-openshift-plattform

Regeln:

Skizze / Textauszug
- alles kleinschreiben
- Umlaute umwandeln: ä → ae, ö → oe, ü → ue, ß → ss
- Leerzeichen durch Bindestriche ersetzen
- Sonderzeichen entfernen
- doppelte Bindestriche reduzieren
- Nummer des Teils vorne behalten

Dadurch bleiben Links stabil und lesbar.

---

9. Codeblock-Regeln

Codeblöcke sind ein zentraler Bestandteil des Lernbuchs. Sie müssen lesbar, farbig und mobil nutzbar sein.

Mindestregeln:

Vorlage / Textauszug
  • dunkler Hintergrund
  • ausreichend Kontrast
  • horizontales Scrollen
  • Sprache sichtbar machen
  • Copy-Button nur als Komfortfunktion
  • keine externen Highlighting-CDNs
  • einheitlicher JetBrains-Dark-orientierter Stil

Beispielstruktur:

html
<figure class=class="str">"code-card">
  <figcaption>Java · OrderApplicationService</figcaption>
  <pre><code class=class="str">"language-java">...</code></pre>
</figure>

Der Copy-Button kann per JavaScript ergänzt werden, aber der Code selbst bleibt auch ohne Button lesbar.

---

10. SVG-Regeln

Diagramme werden inline eingebettet. Dadurch gibt es keine fehlenden Bilddateien im ZIP und keine kaputten relativen Pfade.

Beispiel:

html
<figure
class
=
class
="str">"diagram">

class="str">"
0

0

900

360
" role=
class
="str">"img" aria-labelledby=
class
="str">"arch-title">

class="str">"arch-title">Hybrid-Architektur





  <figcaption>Vereinfachte Hybrid-Zielarchitektur</figcaption>
</figure>

Regeln:

Vorlage / Textauszug
  • SVG inline
  • keine externen Bilder
  • klare Beschriftungen
  • gute Lesbarkeit im hellen und dunklen Modus
  • nicht zu dekorativ
  • Diagramm muss fachlich helfen

---

11. Suchfunktion als Komfort

Die Suchfunktion darf die Nutzbarkeit verbessern, aber nicht notwendig sein. Wenn sie ausfällt, muss die Navigation trotzdem funktionieren.

Zulässige Komfortfunktionen:

Vorlage / Textauszug
  • Suchbegriff markieren
  • passende Kapitel öffnen
  • Trefferanzahl anzeigen
  • zum ersten Treffer springen

Nicht zulässig:

Vorlage / Textauszug
  • Inhalte nur über Suche sichtbar machen
  • Kapitel per JavaScript vollständig aus dem DOM entfernen
  • Navigation nur dynamisch erzeugen
  • Hash-Routing als Pflichtfunktion

Diese Regel kommt direkt aus den bisherigen iPhone-/Safari-Problemen.

---

12. Qualitätsprüfung vor dem HTML-Build

Vor dem finalen HTML-Build wird das Markdown-Material geprüft.

Prüfkriterien:

Vorlage / Textauszug
  • keine generischen Serientitel
  • keine Praxisdurchlauf-X-Blöcke
  • keine Anhang-X-Navigation
  • keine integrierte-Vertiefung-X-Titel
  • keine künstlichen Entscheidung-2/Risiko-2-Tabellen
  • keine stark wiederholten Absätze
  • keine leeren Kapitel
  • keine Kapitel, die nur aus Glossar bestehen
  • keine Kapitel, die nur aus Tabellen bestehen

Dazu wird ein kleiner Prüfbericht erzeugt.

---

13. Beispiel: Prüfskript gegen verbotene Muster

python
from pathlib import Path

base = Path(class="str">"mhepl_md_steps")
forbidden = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
]

failed = []
for md in sorted(base.glob(class="str">"*.md")):
    text = md.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            failed.append((md.name, pattern))

if failed:
    print(class="str">"Verbotene Muster gefunden:")
    for file, pattern in failed:
        print(fclass="str">"- {file}: {pattern}")
    raise SystemExit(1)

print(class="str">"OK: Keine verbotenen Muster gefunden.")

Dieses Skript ersetzt keine fachliche Prüfung, aber es verhindert bekannte Fehler.

---

14. Beispiel: Prüfung auf Absatzwiederholungen

python
from pathlib import Path
from collections import defaultdict
import re

base = Path(class="str">"mhepl_md_steps")
paragraphs = defaultdict(list)

for md in sorted(base.glob(class="str">"*.md")):
    text = md.read_text(encoding=class="str">"utf-8")
    raw_paragraphs = re.split(rclass="str">"\n\s*\n", text)
    for p in raw_paragraphs:
        normalized = re.sub(rclass="str">"\s+", class="str">" ", p.strip())
        if len(normalized) > 280:
            paragraphs[normalized].append(md.name)

duplicates = {
    paragraph: files
    for paragraph, files in paragraphs.items()
    if len(files) > 1
}

if duplicates:
    print(class="str">"Mögliche Wiederholungen gefunden:")
    for paragraph, files in list(duplicates.items())[:20]:
        print(class="str">"---")
        print(files)
        print(paragraph[:300])
    raise SystemExit(1)

print(class="str">"OK: Keine langen identischen Absätze gefunden.")

Diese Prüfung ist bewusst streng. Sie soll verhindern, dass die finale HTML-Datei wieder durch Copy/Paste-Absätze aufgeblasen wird.

---

15. Build-Ausgabe

Die finale Build-Ausgabe soll später mindestens enthalten:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_FINAL_DELIVERY/
├── index.html
├── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md
├── README.md
├── CHANGELOG.md
├── QUALITY_REPORT.md
└── sources/
    ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
    ├── ...
    └── 27_Teil_27_Vorbereitung_der_finalen_HTML_Erzeugung.md

Das ZIP enthält also nicht nur die HTML-Datei, sondern auch die Quelle und den Qualitätsbericht.

---

16. Abnahmekriterien für den nächsten Schritt

Bevor die finale HTML wirklich erzeugt wird, müssen diese Punkte erfüllt sein:

Vorlage / Textauszug
  1. Alle Markdown-Dateien sind im ZIP enthalten.
  2. README beschreibt Struktur und Startpunkt.
  3. Keine verbotenen Muster werden gefunden.
  4. Keine langen identischen Absätze werden gefunden.
  5. Codeblöcke sind sprachlich markiert.
  6. Die spätere Navigation verwendet fachliche Namen.
  7. Content-Kategorien werden standardmäßig geschlossen.
  8. HTML bleibt offlinefähig.
  9. SVGs werden inline erzeugt.
  10. JavaScript bleibt Komfort, nicht Pflicht.

Wenn diese Punkte erfüllt sind, kann die finale HTML-Erzeugung sauber beginnen.

---

17. Übung

Beschreibe, warum es besser ist, zuerst Markdown-Dateien zu prüfen und erst danach HTML zu erzeugen.

Beantworte:

Vorlage / Textauszug
  1. Welche Fehler werden dadurch vermieden?
  2. Warum ist HTML als direkte Arbeitsquelle schwieriger?
  3. Warum ist eine Single-File-HTML trotzdem als Ausgabe sinnvoll?
  4. Welche Rolle spielt der Quality-Report?

---

18. Musterlösung

Eine mögliche Antwort:

Vorlage / Textauszug

Markdown ist als Quelle besser wartbar, weil Inhalte klar getrennt, einfacher vergleichbar und leichter prüfbar sind. Wiederholungen, verbotene Muster und leere Kapitel lassen sich in Markdown einfacher erkennen als in fertigem HTML.

HTML als direkte Arbeitsquelle ist schwieriger, weil Inhalt, Layout, Navigation, CSS und JavaScript vermischt werden. Kleine Reparaturen können dabei schnell neue Fehler erzeugen, zum Beispiel kaputte Navigation, doppelte IDs oder unlesbare Codeblöcke.

Eine Single-File-HTML ist trotzdem als Ausgabe sinnvoll, weil sie leicht weitergegeben werden kann, offline funktioniert und keine externen Assets benötigt. Sie sollte aber aus einer sauberen Quelle gebaut werden.

Der Quality-Report dokumentiert, welche Prüfungen durchgeführt wurden. Dadurch wird sichtbar, dass die Datei nicht nur erzeugt, sondern auch kontrolliert wurde.

---

19. Merksätze

Vorlage / Textauszug

Markdown ist die Quelle, HTML ist das Produkt.

Eine große HTML-Datei darf groß sein, aber nicht künstlich aufgeblasen.

Navigation muss robust sein, nicht spektakulär.

Qualität entsteht vor dem Build, nicht durch nachträgliches Reparieren.

Ein gutes Delivery-Paket enthält Quelle, Ausgabe, README und Qualitätsbericht.

Finale HTML-Erzeugung praktisch durchführen

1. Ziel dieses Teils

In den vorherigen Teilen wurde festgelegt, wie die finale HTML-Ausgabe aussehen soll. Dieser Teil beschreibt nun den praktischen Ablauf: Aus den vorhandenen Markdown-Dateien wird ein einziges robustes HTML-Masterbuch erzeugt. Dabei bleibt die Markdown-Sammlung weiterhin die Quelle der Wahrheit. Die HTML-Datei ist die lesbare Auslieferungsform für Browser, iPhone, iPad und Desktop.

Der wichtigste Grundsatz lautet:

Vorlage / Textauszug

Markdown bleibt fachliche Quelle.

HTML ist eine abgeleitete Ausgabe.

Wenn später Inhalt geändert wird, wird zuerst Markdown geändert und danach HTML neu erzeugt.

Dadurch vermeiden wir, dass Inhalte in mehreren Versionen auseinanderlaufen. Besonders bei großen Lernbüchern entsteht sonst schnell Chaos: eine korrigierte Markdown-Datei, eine ältere HTML-Datei, ein halbfertiges PDF und ein ZIP mit falschem Stand. Die finale Pipeline verhindert genau das.

---

2. Eingangsdateien

Die finale HTML-Erzeugung verwendet alle Markdown-Teile in numerischer Reihenfolge.

Vorlage / Textauszug

01_Teil_1_Unternehmenskontext_und_Zielbild.md

02_Teil_2_Infrastruktur_Fundament.md

03_Teil_3_Cloud_Architektur.md

...

27_Teil_27_Vorbereitung_der_finalen_HTML_Erzeugung_aus_allem_Markdown.md

28_Teil_28_Finale_HTML_Erzeugung_praktisch_durchfuehren.md

Die Reihenfolge ist wichtig, weil die Navigation später daraus aufgebaut wird. Dateinamen dienen dabei nur als technische Sortierung. In der HTML-Navigation sollen fachliche Titel stehen, nicht rohe Dateinamen.

Falsch wäre:

Vorlage / Textauszug

27_Teil_27_Vorbereitung_der_finalen_HTML_Erzeugung_aus_allem_Markdown

Besser ist:

Vorlage / Textauszug

Vorbereitung der finalen HTML-Erzeugung

---

3. Build-Pipeline im Überblick

Der Build-Prozess besteht aus mehreren klaren Schritten.

Skizze / Textauszug
Markdown-Dateien sammeln
↓
Metadaten extrahieren
↓
Kapitelstruktur erzeugen
↓
verbotene Muster prüfen
↓
Wiederholungen prüfen
↓
Markdown nach HTML umwandeln
↓
Codeblöcke formatieren
↓
Navigation erzeugen
↓
Inline-SVGs einfügen
↓
Single-File-HTML schreiben
↓
Quality-Report schreiben
↓
ZIP-Paket bauen

Jeder Schritt hat einen Zweck. Die Pipeline soll nicht nur HTML erzeugen, sondern auch verhindern, dass frühere Qualitätsprobleme wieder auftreten: künstliche Wiederholungen, generische Überschriften, kaputte Navigation oder Inhalte, die hauptsächlich aus Glossar und Tabellen bestehen.

---

4. Zielstruktur der finalen HTML

Die finale HTML-Datei soll diese Struktur haben:

html
<!doctype html>
<html lang=class="str">"de">
<head>
  <meta charset=class="str">"utf-8">
  <meta name=class="str">"viewport" content=class="str">"width=device-width, initial-scale=1">
  <title>Enterprise Platform Lab</title>
  <style>/* komplettes Theme inline */</style>
</head>
<body>
  <header>...</header>
  <div class=class="str">"layout">
    <aside class=class="str">"sidebar">...</aside>
    <main class=class="str">"content">...</main>
  </div>
  <script>/* nur Komfortfunktionen */</script>
</body>
</html>

Alle CSS-Regeln, kleinen Komfort-Skripte und SVGs liegen direkt in der Datei. Dadurch gibt es keine fehlenden Assets, keine CDN-Abhängigkeiten und keine kaputten relativen Pfade.

---

5. Robuste Navigation

Die Navigation wird mit nativen HTML-Elementen gebaut.

html
<details class=class="str">"nav-group">
  <summary>Teil 5: Kubernetes und OpenShift</summary>
  <a href=class="str">"#teil-5-kubernetes-und-openshift">Überblick</a>
  <a href=class="str">"#namespaces">Namespaces</a>
  <a href=class="str">"#networkpolicy">NetworkPolicy</a>
</details>

Der Vorteil: Diese Navigation funktioniert auch ohne JavaScript. Besonders auf iPhone/Safari ist das wichtig, weil lokale HTML-Dateien je nach Öffnungsweg JavaScript-Funktionen unzuverlässig ausführen können.

JavaScript darf nur Komfort bieten, zum Beispiel:

Vorlage / Textauszug
  • Zielbereich beim Anklicken automatisch öffnen
  • Suchtreffer markieren
  • Copy-Button für Codeblöcke

Es darf aber nicht notwendig sein, damit der Inhalt überhaupt lesbar ist.

---

6. Content-Kategorien standardmäßig geschlossen

Auch im Hauptinhalt werden die großen Teile als details dargestellt.

html
<details class=class="str">"chapter" id=class="str">"teil-8-daten-messaging-und-integration">
  <summary>
    <h2>Teil 8: Daten, Messaging und Integration</h2>
  </summary>
  <section>...</section>
</details>

Standardmäßig bleiben die Kategorien geschlossen. Der Leser öffnet gezielt den Teil, den er braucht. Das ist bei einer großen HTML-Datei wichtig, weil sonst eine sehr lange Seite entsteht.

Trotzdem muss der Inhalt per Suche und Ankerlink erreichbar bleiben. Wenn ein Link auf einen geschlossenen Bereich zeigt, kann JavaScript den Bereich zusätzlich öffnen. Ohne JavaScript kann der Benutzer den Bereich manuell öffnen.

---

Jede Überschrift bekommt eine stabile ID.

Beispiel:

Vorlage / Textauszug

Teil 8: Daten, Messaging und Integration

wird zu:

Vorlage / Textauszug

teil-8-daten-messaging-und-integration

Regeln:

Vorlage / Textauszug
  • alles klein schreiben
  • Umlaute umwandeln
  • Sonderzeichen entfernen
  • Leerzeichen durch Bindestriche ersetzen
  • doppelte IDs mit Suffix eindeutig machen

Doppelte IDs sind gefährlich. Wenn zwei Abschnitte dieselbe ID haben, springt der Browser möglicherweise zur falschen Stelle. Deshalb muss der Build doppelte IDs erkennen und korrigieren.

---

8. Codeblock-Darstellung

Codeblöcke sind ein Kernbestandteil des Lernbuchs. Sie müssen gut lesbar sein.

Der Stil folgt dem JetBrains-Dark-Prinzip:

Vorlage / Textauszug
  • dunkler Hintergrund
  • helle Grundschrift
  • farbige Keywords
  • farbige Strings
  • farbige Zahlen
  • farbige Kommentare
  • horizontales Scrollen
  • Copy-Button rechts oben, wo möglich

Für Markdown entsteht aus:

Vorlage / Textauszug

public class OrderServiceApplication {

}

Vorlage / Textauszug

diese HTML-Struktur:

html
<figure class=class="str">"code-card">
  <figcaption>java</figcaption>
  <button class=class="str">"copy">Kopieren</button>
  <pre><code class=class="str">"language-java">...</code></pre>
</figure>

Die farbliche Darstellung kann CSS-basiert erfolgen. Für eine robuste Offline-Datei wird keine externe Highlighting-Bibliothek verwendet.

---

9. Beispiel: einfacher HTML-Generator in Python

Der Generator darf nicht versuchen, fachlichen Inhalt zu erfinden. Er darf nur vorhandenes Markdown technisch umwandeln.

python
from pathlib import Path
import html
import re

SOURCE_DIR = Path(class="str">"mhepl_md_steps")
OUTPUT = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html")


def slugify(text: str) -> str:
    replacements = {
        class="str">"ä": class="str">"ae", class="str">"ö": class="str">"oe", class="str">"ü": class="str">"ue", class="str">"ß": class="str">"ss",
        class="str">"Ä": class="str">"ae", class="str">"Ö": class="str">"oe", class="str">"Ü": class="str">"ue",
    }
    for source, target in replacements.items():
        text = text.replace(source, target)
    text = text.lower()
    text = re.sub(rclass="str">"[^a-z0-9]+", class="str">"-", text)
    return text.strip(class="str">"-")


def read_parts() -> list[Path]:
    return sorted(SOURCE_DIR.glob(class="str">"[0-9][0-9]_Teil_*.md"))


def extract_title(markdown: str) -> str:
    for line in markdown.splitlines():
        if line.startswith(class="str">"# "):
            return line[2:].strip()
    return class="str">"Ohne Titel"


def escape_text(value: str) -> str:
    return html.escape(value, quote=False)

Dieses Beispiel ist bewusst klein. Es zeigt: Der Generator sammelt, sortiert, benennt und escaped. Er ersetzt keine fachliche Redaktion.

---

10. Prüfung gegen verbotene Muster

Vor dem HTML-Build muss geprüft werden, ob verbotene Muster vorkommen.

python
FORBIDDEN_PATTERNS = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
]


def check_forbidden_patterns(text: str) -> list[str]:
    hits = []
    for pattern in FORBIDDEN_PATTERNS:
        if pattern.lower() in text.lower():
            hits.append(pattern)
    return hits

Wenn Treffer gefunden werden, darf die HTML-Datei nicht als final gelten. Der Build kann zwar technisch erzeugt werden, aber der Quality-Report muss den Status auf „nicht abgenommen“ setzen.

---

11. Prüfung gegen Wiederholungen

Die frühere schlechte Version hatte viele nahezu gleiche Absätze. Deshalb braucht der neue Prozess eine Wiederholungsprüfung.

Eine einfache Prüfung kann Absätze normalisieren und zählen.

python
from collections import Counter


def normalize_paragraph(paragraph: str) -> str:
    paragraph = paragraph.lower()
    paragraph = re.sub(rclass="str">"\s+", class="str">" ", paragraph)
    paragraph = re.sub(rclass="str">"\b\d+\b", class="str">"<num>", paragraph)
    return paragraph.strip()


def find_repeated_paragraphs(text: str, min_length: int = 180) -> list[tuple[str, int]]:
    paragraphs = [p.strip() for p in text.split(class="str">"\n\n")]
    normalized = [normalize_paragraph(p) for p in paragraphs if len(p) >= min_length]
    counts = Counter(normalized)
    return [(paragraph, count) for paragraph, count in counts.items() if count > 1]

Diese Prüfung ersetzt keine menschliche Qualitätskontrolle. Sie erkennt aber typische Generatorfehler schnell.

---

12. Prüfung gegen tabellenlastigen Inhalt

Tabellen sind erlaubt, aber sie dürfen nicht den Hauptinhalt ersetzen. Eine einfache Kennzahl ist das Verhältnis von Tabellenzeilen zu normalen Absätzen.

python
def table_line_ratio(text: str) -> float:
    lines = [line for line in text.splitlines() if line.strip()]
    if not lines:
        return 0.0
    table_lines = [line for line in lines if line.strip().startswith(class="str">"|")]
    return len(table_lines) / len(lines)

Wenn der Anteil sehr hoch ist, muss geprüft werden, ob der Teil wirklich als Lernbuch lesbar ist oder nur aus Listen und Tabellen besteht.

---

13. Quality-Report

Jede finale Ausgabe bekommt einen Quality-Report.

Vorlage / Textauszug

Quality Report

Ausgabe

Datei: Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html

Quelle: Markdown Teile 1 bis 28

Prüfungen
  • verbotene Muster: bestanden
  • doppelte Absätze: bestanden
  • Navigation: bestanden
  • Codeblöcke: bestanden
  • externe Assets: keine
  • Tabellenlastigkeit: unkritisch
  • iPhone/Safari-Grundregeln: bestanden
Hinweise

Die HTML-Datei ist eine abgeleitete Ausgabe. Fachliche Änderungen erfolgen zuerst in Markdown.

Der Report ist wichtig, weil später nachvollziehbar bleibt, welche Qualitätsregeln angewendet wurden.

---

14. Finale ZIP-Struktur

Die finale HTML-Ausgabe sollte nicht nur die HTML-Datei enthalten.

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_FINAL_DELIVERY.zip
├── web/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html
├── md/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── ...
│   └── 28_Teil_28_Finale_HTML_Erzeugung_praktisch_durchfuehren.md
├── quality-reports/
│   └── QUALITY_REPORT.md
├── README.md
└── CHANGELOG.md

So bleibt die Ausgabe nachvollziehbar. Die HTML-Datei ist bequem zum Lesen. Die Markdown-Dateien bleiben bearbeitbar. Der Quality-Report dokumentiert die Prüfung.

---

15. Typische Fehler beim finalen Build

Ein häufiger Fehler ist, die HTML-Datei manuell zu bearbeiten. Dadurch stimmt sie nicht mehr mit Markdown überein. Besser ist: Markdown ändern, Build neu ausführen.

Ein zweiter Fehler ist, CSS oder JavaScript aus externen Quellen einzubinden. Das macht die Datei abhängig von Internetverbindung und CDN-Verfügbarkeit.

Ein dritter Fehler ist, die Navigation durch komplexes JavaScript zu steuern. Wenn JavaScript auf iPhone oder bei lokalen Dateien nicht sauber läuft, wirkt die ganze Datei defekt. Native details/summary ist stabiler.

Ein vierter Fehler ist, Inhalte künstlich aufzublasen, um eine Dateigröße zu erreichen. Die Dateigröße darf nur durch echten Inhalt entstehen.

Ein fünfter Fehler ist, Markdown-Codeblöcke unescaped in HTML zu schreiben. Dann können Zeichen wie <, > oder & die HTML-Struktur beschädigen.

---

16. Übung

Entwirf eine eigene Build-Checkliste für die finale HTML-Ausgabe.

Die Checkliste soll enthalten:

Vorlage / Textauszug
  1. Prüfung der Markdown-Dateien
  2. Prüfung der verbotenen Muster
  3. Prüfung der Navigation
  4. Prüfung der Codeblöcke
  5. Prüfung der mobilen Nutzbarkeit
  6. Prüfung der ZIP-Struktur
  7. Prüfung des Quality-Reports

---

17. Musterlösung

Vorlage / Textauszug

Vor dem Build werden alle Markdown-Dateien numerisch sortiert und auf Vollständigkeit geprüft. Danach wird der gesamte Text gegen verbotene Muster wie Praxisdurchlauf, integrierte Vertiefung und Anhang 16 geprüft.

Anschließend wird geprüft, ob Absätze mehrfach vorkommen. Auffällige Wiederholungen müssen redaktionell bewertet werden. Danach wird die HTML-Datei erzeugt.

Die Navigation wird im Browser getestet. Alle Hauptteile müssen standardmäßig geschlossen sein und sich über details/summary öffnen lassen. Ankerlinks müssen zur richtigen Stelle springen.

Codeblöcke werden auf Lesbarkeit geprüft. Sie brauchen dunklen Hintergrund, ausreichenden Kontrast und horizontales Scrollen.

Auf dem iPhone wird geprüft, ob die Datei ohne externe Ressourcen lesbar ist. JavaScript darf nur Komfort bieten.

Zum Schluss wird das ZIP geöffnet und geprüft, ob HTML, Markdown, README, CHANGELOG und Quality-Report enthalten sind.

---

18. Merksätze

Vorlage / Textauszug

Der HTML-Build darf kein Autor sein.

Er darf nur vorhandenes Fachmaterial sauber ausliefern.

Markdown ist die Quelle der Wahrheit.

HTML ist die lesbare Ausgabe.

Robuste Navigation ist wichtiger als optisch komplexe Navigation.

Eine große Datei ist nur dann gut, wenn der Inhalt wirklich fachlich stark ist.

Quality-Reports verhindern, dass alte Fehler unbemerkt zurückkommen.

Erstellung des finalen Single-File-HTML-Masterbuchs

1. Ziel dieses Teils

Nachdem die Markdown-Teile einzeln aufgebaut, geprüft und in mehreren Schritten gegen künstliche Wiederholungen abgesichert wurden, geht es jetzt um den eigentlichen Übergang zum finalen HTML-Masterbuch. Dieser Teil beschreibt nicht nur, dass HTML erzeugt werden soll, sondern wie die Erzeugung kontrolliert, nachvollziehbar und reproduzierbar ablaufen muss.

Das Ziel ist eine einzelne HTML-Datei, die auf Desktop, Tablet und iPhone lesbar bleibt. Die Datei soll ohne externe Bibliotheken funktionieren. Sie soll keine CDN-Abhängigkeiten haben, keine fehlenden Bilddateien referenzieren und keine Navigation verwenden, die nur durch JavaScript bedienbar ist. JavaScript darf Komfort liefern, aber der Inhalt muss auch ohne JavaScript erreichbar bleiben.

Das finale HTML-Masterbuch ist damit kein zufälliger Export. Es ist ein bewusst gebautes Artefakt mit klarer Struktur:

Skizze / Textauszug
Markdown-Quellen
↓
Konsolidierung
↓
Qualitätsprüfung
↓
HTML-Build
↓
Navigationstest
↓
Codeblock-Test
↓
ZIP-Paket
↓
optional Google-Drive-Ablage

Dieser Ablauf schützt vor den Fehlern, die in früheren Fassungen aufgetreten sind: kaputte Navigation, künstlich aufgeblasene Inhalte, Tabellen als Hauptinhalt, generische Titel und wiederholte Absätze mit nur ausgetauschten Schlagworten.

---

2. Unterschied zwischen Markdown-Quelle und HTML-Ausgabe

Die Markdown-Dateien bleiben die fachliche Quelle. Sie sind leicht lesbar, versionierbar und können einzeln gepflegt werden. Das HTML ist die nutzerfreundliche Leseausgabe. Es bekommt Navigation, Design, Codeblock-Darstellung, Suchfeld, mobile Optimierung und inline SVGs.

Die wichtigste Regel lautet:

Vorlage / Textauszug

Inhalt wird in Markdown gepflegt.

Darstellung wird im HTML gelöst.

Das bedeutet: Die Markdown-Dateien dürfen keine HTML-Tricks enthalten, die später die Darstellung zerstören. Umgekehrt darf die HTML-Erzeugung keine neuen fachlichen Inhalte erfinden. Sie darf Inhalte strukturieren, aber nicht künstlich erweitern.

Wenn später ein Kapitel verbessert wird, wird zuerst die passende .md-Datei geändert. Danach wird das HTML neu gebaut. Dadurch bleibt die Quelle sauber.

---

3. Zielstruktur der finalen HTML-Datei

Die finale HTML-Datei besteht aus vier großen Bereichen.

Vorlage / Textauszug
  1. Kopfbereich
  2. Seitenleiste mit Navigation
  3. Hauptbereich mit Kapiteln
  4. eingebettete Hilfslogik und Styles

Der Kopfbereich enthält Titel, Projektbeschreibung, Versionshinweis und Suchfeld. Die Seitenleiste enthält die Navigation. Der Hauptbereich enthält die eigentlichen Kapitel. Styles und kleine Komfortfunktionen werden direkt in die Datei eingebettet.

Die Datei darf keine externen CSS- oder JavaScript-Dateien benötigen.

Nicht erlaubt:

html
<link rel=class="str">"stylesheet" href=class="str">"style.css">
<script src=class="str">"app.js"></script>
<img src=class="str">"diagramm.svg">

Erlaubt:

html
<style>
  /* eingebettetes CSS */
</style>

<script>
  /* kleine Komfortfunktion */
</script>


class="str">"
0

0

100

40
" role=
class
="str">"img">


Dadurch kann die HTML-Datei lokal geöffnet, verschoben, gezippt und weitergegeben werden, ohne dass Assets fehlen.

---

4. Navigation mit nativen HTML-Elementen

Die Navigation muss robust sein. Deshalb wird sie mit details und summary gebaut. Diese Elemente funktionieren in modernen Browsern ohne eigenes JavaScript. Sie sind besonders sinnvoll für lokale HTML-Dateien auf iPhone und Safari, weil dort komplexe JavaScript-Navigationen oft fehleranfällig sind.

Grundstruktur:

html
<nav class=class="str">"sidebar">
  <details>
    <summary>Teil 1: Unternehmenskontext und Zielbild</summary>
    <a href=class="str">"#teil-1">Überblick</a>
    <a href=class="str">"#teil-1-zielbild">Zielbild</a>
    <a href=class="str">"#teil-1-repository">Repository-Struktur</a>
  </details>

  <details>
    <summary>Teil 2: Infrastruktur-Fundament</summary>
    <a href=class="str">"#teil-2">Überblick</a>
    <a href=class="str">"#teil-2-netzwerkzonen">Netzwerkzonen</a>
    <a href=class="str">"#teil-2-firewall">Firewall-Regeln</a>
  </details>
</nav>

Wichtig: Die Navigation ist standardmäßig geschlossen. Das hält die Oberfläche kompakt. Der Nutzer öffnet nur den Bereich, den er gerade braucht.

Keine Navigationseinträge dürfen generisch heißen:

Vorlage / Textauszug

Anhang 16

Praxisdurchlauf 4

integrierte Vertiefung 7

Abschnitt 12

Stattdessen muss jeder Eintrag fachlich benannt sein:

Vorlage / Textauszug

OpenShift Namespace-Strategie

Outbox Backlog analysieren

Keycloak Rollenmodell

PostgreSQL Restore-Test

Argo CD Rollback

---

5. Content-Kategorien standardmäßig geschlossen

Nicht nur die Navigation, sondern auch der Hauptinhalt wird mit klappbaren Bereichen organisiert. Dadurch bleibt eine große HTML-Datei auf mobilen Geräten bedienbar.

Beispiel:

html
<main>
  <details class=class="str">"chapter" id=class="str">"teil-5">
    <summary>
      <h2>Teil 5: Kubernetes- und OpenShift-Plattform</h2>
    </summary>

    <section id=class="str">"teil-5-namespace-strategie">
      <h3>Namespace-Strategie</h3>
      <p>...</p>
    </section>
  </details>
</main>

Dabei ist wichtig: Im summary steht eine echte Überschrift. Der Bereich ist ohne open-Attribut standardmäßig geschlossen.

Nicht empfohlen:

html
<details open>

Empfohlen:

html
<details class=class="str">"chapter">

So bleibt die Seite beim Öffnen übersichtlich.

---

Jedes Kapitel und jeder wichtige Unterabschnitt bekommt eine stabile ID. Diese IDs dürfen nicht automatisch aus dem kompletten Titel mit Sonderzeichen erzeugt werden, weil dadurch fehlerhafte Links entstehen können.

Besser ist eine kontrollierte Slug-Strategie:

Skizze / Textauszug
Teil 5: Kubernetes- und OpenShift-Plattform
→ teil-5-kubernetes-und-openshift-plattform

ServiceMonitor und PrometheusRule
→ teil-9-servicemonitor-und-prometheusrule

Runbook: Outbox Backlog
→ teil-13-runbook-outbox-backlog

Ein Link ist nur gültig, wenn genau eine passende Ziel-ID existiert. Deshalb muss die HTML-Erzeugung nach dem Build prüfen:

Vorlage / Textauszug

Jeder href=class="str">"#..." zeigt auf eine vorhandene ID.

Keine ID kommt doppelt vor.

Keine Navigation zeigt auf einen gelöschten Abschnitt.

Ein einfaches Prüfskript kann diese Fehler finden, bevor die Datei verteilt wird.

---

7. Codeblöcke nach JetBrains-Dark-Prinzip

Die Codeblöcke müssen gut lesbar sein. Der Nutzer hatte mehrfach bemängelt, dass Codebereiche zu hell, zu grau oder nicht farbig genug waren. Deshalb wird ein einheitliches dunkles Codeblock-Design verwendet.

Die HTML-Ausgabe soll Codeblöcke so darstellen:

html
<figure class=class="str">"code-card" data-lang=class="str">"java">
  <figcaption>
    <span>Java</span>
    <button type=class="str">"button" class=class="str">"copy-code">Kopieren</button>
  </figcaption>
  <pre><code class=class="str">"language-java">...</code></pre>
</figure>

Die Darstellung erfolgt über eingebettetes CSS. Ein vereinfachtes Farbschema:

css
.code-card {
  background: #1e1f22;
  border: 1px solid #3c3f44;
  border-radius: 14px;
  overflow: hidden;
}

.code-card figcaption {
  background: #2b2d30;
  color: #dfe1e5;
  display: flex;
  justify-content: space-between;
  padding: .65rem .9rem;
}

.code-card pre {
  margin: 0;
  padding: 1rem;
  overflow-x: auto;
  color: #dfe1e5;
}

.tok-keyword { color: #cf8efa; }
.tok-string  { color: #6aab73; }
.tok-number  { color: #2aacb8; }
.tok-comment { color: #7a7e85; }
.tok-type    { color: #56a8f5; }
.tok-anno    { color: #b3ae60; }

Der Copy-Button ist nur Komfort. Wenn JavaScript nicht funktioniert, bleibt der Code trotzdem lesbar.

---

8. Inline-SVG statt externe Bilder

Alle Diagramme werden inline eingebettet. Das verhindert fehlende Bilddateien im ZIP.

Beispiel für ein einfaches Zielarchitektur-SVG:

html
class=
class
="str">"diagram" viewBox=
class
="str">"
0

0

900

360
" role=
class
="str">"img" aria-label=
class
="str">"Hybrid Enterprise Zielarchitektur">

class="str">"
30
" y=
class
="str">"
40
" width=
class
="str">"
180
" height=
class
="str">"
70
" rx=
class
="str">"
14
">


class="str">"
120
" y=
class
="str">"
82
" text-anchor=
class
="str">"middle">Customer Portal


class="str">"
330
" y=
class
="str">"
40
" width=
class
="str">"
200
" height=
class
="str">"
70
" rx=
class
="str">"
14
">


class="str">"
430
" y=
class
="str">"
82
" text-anchor=
class
="str">"middle">OpenShift APIs


class="str">"
660
" y=
class
="str">"
40
" width=
class
="str">"
190
" height=
class
="str">"
70
" rx=
class
="str">"
14
">


class="str">"
755
" y=
class
="str">"
82
" text-anchor=
class
="str">"middle">PostgreSQL


class="str">"
210
" y1=
class
="str">"
75
" x2=
class
="str">"
330
" y2=
class
="str">"
75
">


class="str">"
530
" y1=
class
="str">"
75
" x2=
class
="str">"
660
" y2=
class
="str">"
75
">

Die SVGs sollen nicht dekorativ sein. Sie müssen fachlich helfen. Gute Diagramme zeigen:

Vorlage / Textauszug
  • Systemgrenzen
  • Datenflüsse
  • Sicherheitszonen
  • Deployment-Flüsse
  • Backup-/Restore-Flüsse
  • Observability-Flüsse

Ein Diagramm pro großem Hauptteil ist sinnvoller als viele kleine Deko-Grafiken.

---

9. Suchfunktion als Komfort, nicht als Pflicht

Eine Suchfunktion ist nützlich, aber sie darf nicht die einzige Möglichkeit sein, Inhalte zu finden. Navigation und Überschriften müssen auch ohne Suche funktionieren.

Eine robuste Suchfunktion kann so arbeiten:

javascript
function searchContent(term) {
  const normalized = term.trim().toLowerCase();
  document.querySelectorAll(class="str">'.search-hit').forEach(el => el.classList.remove(class="str">'search-hit'));

  if (!normalized) return;

  document.querySelectorAll(class="str">'main section, main details.chapter').forEach(el => {
    const text = el.textContent.toLowerCase();
    if (text.includes(normalized)) {
      el.classList.add(class="str">'search-hit');
      const parent = el.closest(class="str">'details');
      if (parent) parent.open = true;
    }
  });
}

Wenn JavaScript nicht läuft, bleibt die Seite trotzdem nutzbar. Der Nutzer kann die Browser-Suche verwenden.

---

10. Qualitätsprüfung vor dem HTML-Build

Vor der HTML-Erzeugung muss geprüft werden, ob die Markdown-Dateien verbotene Muster enthalten.

Verbotene Muster:

Vorlage / Textauszug

Praxisdurchlauf

integrierte Vertiefung

Anhang 16

Entscheidung 2

Risiko 3

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit und reproduzierbare Umsetzung

Prüfskript:

python
from pathlib import Path

root = Path(class="str">"mhepl_md_steps")
forbidden = [
    class="str">"Praxisdurchlauf",
    class="str">"integrierte Vertiefung",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit und reproduzierbare Umsetzung",
]

failed = False
for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    for pattern in forbidden:
        if pattern in text:
            print(fclass="str">"FEHLER: {pattern} in {file}")
            failed = True

if failed:
    raise SystemExit(1)

print(class="str">"OK: Keine verbotenen Muster gefunden.")

Diese Prüfung verhindert, dass alte Fehler wieder in die finale Ausgabe gelangen.

---

11. Prüfung gegen Absatzwiederholungen

Zusätzlich wird geprüft, ob identische Absätze mehrfach vorkommen. Nicht jede Wiederholung ist falsch, aber lange identische Absätze deuten auf künstliche Generierung hin.

Ein einfacher Ansatz:

python
from pathlib import Path
from collections import Counter
import re

root = Path(class="str">"mhepl_md_steps")
paragraphs = []

for file in sorted(root.glob(class="str">"*.md")):
    text = file.read_text(encoding=class="str">"utf-8")
    blocks = re.split(rclass="str">"\n\s*\n", text)
    for block in blocks:
        clean = class="str">" ".join(block.split())
        if len(clean) > 180:
            paragraphs.append(clean)

counter = Counter(paragraphs)
for paragraph, count in counter.items():
    if count > 1:
        print(class="str">"DOPPELTER ABSATZ", count, paragraph[:160])

Diese Prüfung soll nicht jeden wiederkehrenden Merksatz verbieten. Sie soll große Copy/Paste-Blöcke sichtbar machen.

---

12. Build-Reihenfolge

Die finale HTML-Erzeugung folgt dieser Reihenfolge:

Vorlage / Textauszug
  1. alle Markdown-Dateien nach Nummer sortieren
  2. README ausnehmen oder separat behandeln
  3. Überschriften analysieren
  4. Kapitelstruktur erzeugen
  5. Navigation aus echten Überschriften erzeugen
  6. Markdown in HTML umwandeln
  7. Codeblöcke in Code-Cards umwandeln
  8. Diagramme inline einfügen
  9. Ankerlinks prüfen
  10. verbotene Muster prüfen
  11. Wiederholungen prüfen
  12. HTML-Dateigröße prüfen
  13. ZIP mit HTML, Markdown, README und Quality-Report erzeugen

Wichtig: Wenn die HTML-Datei zu klein ist, darf sie nicht künstlich aufgeblasen werden. Stattdessen muss fachlicher Inhalt ergänzt werden. Umfang entsteht durch Substanz, nicht durch Wiederholung.

---

13. Beispiel für eine Build-Ausgabe

Die finale Ausgabe könnte so heißen:

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_FINAL_DELIVERY/
├── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html
├── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md
├── markdown_steps/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   └── ...
├── QUALITY_REPORT.md
├── README.md
└── CHANGELOG.md

Das ZIP dazu:

Vorlage / Textauszug

Modern_Hybrid_Enterprise_Platform_Lab_FINAL_DELIVERY.zip

Keine Dateinamen wie:

Vorlage / Textauszug

final_neu_fix_v2_korrigiert_letzte_version.html

Saubere Dateinamen sind Teil der Qualität.

---

14. Quality-Report für die finale HTML-Datei

Der Quality-Report dokumentiert, was geprüft wurde.

Vorlage / Textauszug

Quality Report

Datei

Modern_Hybrid_Enterprise_Platform_Lab_MASTER.html

Strukturprüfung
  • Navigation mit details/summary: OK
  • Content-Kategorien standardmäßig geschlossen: OK
  • keine externen Assets: OK
  • inline SVGs vorhanden: OK
  • Codeblöcke vorhanden: OK
Musterprüfung
  • keine Praxisdurchlauf-Titel: OK
  • keine integrierte Vertiefung-Titel: OK
  • keine Anhang-Nummerntitel: OK
  • keine künstlichen Entscheidung/Risiko-Tabellen: OK
Wiederholungsprüfung
  • keine langen identischen Absätze über Schwellwert: OK
Mobile Prüfung
  • Codeblöcke horizontal scrollbar: OK
  • Navigation ohne JavaScript nutzbar: OK
  • Hauptbereiche klappbar: OK

Der Bericht ist wichtig, weil er nicht nur behauptet, dass die Datei gut ist, sondern die Prüfung dokumentiert.

---

15. Typische Fehler bei der finalen HTML-Erzeugung

Ein häufiger Fehler ist, die Navigation aus Dateinamen statt aus fachlichen Überschriften zu erzeugen. Dann entstehen technische oder unlesbare Einträge.

Ein zweiter Fehler ist, alle Kapitel standardmäßig geöffnet auszugeben. Bei großen Dateien führt das auf mobilen Geräten zu einer kaum bedienbaren Wand aus Text.

Ein dritter Fehler ist, Codeblöcke ohne horizontales Scrollen darzustellen. Dann sprengen lange Java- oder YAML-Zeilen das Layout.

Ein vierter Fehler ist, Mermaid-Code als Diagramm zu erwarten, obwohl in einer lokalen HTML ohne externe Library kein Mermaid gerendert wird. Wenn Mermaid ohne Library eingebettet wird, sieht der Nutzer nur Code. Für die finale HTML sind deshalb inline SVGs stabiler.

Ein fünfter Fehler ist, beim Build neue Füllinhalte zu erzeugen. Der Build darf formatieren, aber nicht fachlich erfinden.

---

16. Übung

Entwerfe eine finale HTML-Abnahmeliste für dieses Projekt. Die Liste soll mindestens zehn Punkte enthalten und prüfen, ob die Datei wirklich benutzbar ist.

Berücksichtige:

Vorlage / Textauszug
  • Navigation
  • Content-Klappbarkeit
  • Codeblöcke
  • SVGs
  • mobile Lesbarkeit
  • verbotene Muster
  • Wiederholungen
  • ZIP-Struktur
  • README
  • Quality-Report

---

17. Musterlösung

Eine mögliche Abnahmeliste:

Vorlage / Textauszug
  1. Die HTML-Datei öffnet lokal ohne Server.
  2. Die Navigation ist mit nativen details/summary-Elementen klappbar.
  3. Alle Navigationslinks zeigen auf vorhandene IDs.
  4. Alle Hauptkapitel sind standardmäßig geschlossen.
  5. Codeblöcke haben dunklen Hintergrund, gute Lesbarkeit und horizontales Scrollen.
  6. Copy-Buttons sind nur Komfort und blockieren nicht die Lesbarkeit.
  7. SVG-Diagramme sind inline eingebettet und benötigen keine externen Dateien.
  8. Die HTML-Datei enthält keine generischen Titel wie Praxisdurchlauf oder integrierte Vertiefung.
  9. Die HTML-Datei enthält keine künstlichen Entscheidung/Risiko-Fülltabellen.
  10. Die Wiederholungsprüfung meldet keine langen identischen Absätze.
  11. Die README erklärt Startdatei, Inhalt und Nutzung.
  12. Der Quality-Report dokumentiert Prüfungen und Ergebnis.
  13. Das ZIP enthält HTML, Master-Markdown, Einzelschritte, README, CHANGELOG und Quality-Report.
  14. Die Datei ist auf Desktop und iPhone lesbar.
  15. Die Browser-Suche funktioniert auch ohne eigenes JavaScript.

Diese Liste ist bewusst praktisch. Sie prüft nicht nur technische Korrektheit, sondern auch Nutzbarkeit.

---

18. Merksätze

Vorlage / Textauszug

Das finale HTML ist eine Leseausgabe, nicht die fachliche Quelle.

Markdown bleibt die pflegbare Wahrheit.

Navigation muss ohne JavaScript nutzbar sein.

Große HTML-Dateien brauchen geschlossene Kapitelbereiche.

Codeblöcke müssen lesbar, farbig und horizontal scrollbar sein.

SVGs werden inline eingebettet, damit keine Assets fehlen.

Umfang darf nur durch echten Inhalt entstehen.

Ein Quality-Report schützt vor wiederholten alten Fehlern.

Großer Schritt – Finaler Buildlauf, Master-Markdown und HTML-Abnahme

1. Ziel dieses großen Schritts

Dieser Schritt ist bewusst größer als die vorherigen Einzelteile. Bisher wurden die Inhalte kapitelweise aufgebaut: Unternehmenskontext, Infrastruktur, Cloud, VM und Bare Metal, OpenShift, Security, Anwendungen, Daten, Betrieb, Backup, DevOps, AI-Unterstützung, Beispielprojekt, Übungen, Begriffe und mehrere Abnahmeregeln. Jetzt wird daraus ein zusammenhängender Buildlauf gedacht.

Der Zweck dieses Schritts ist nicht, noch ein weiteres loses Kapitel anzuhängen. Der Zweck ist, die bisherige Arbeit in eine belastbare Übergabeform zu bringen. Das bedeutet: Die Markdown-Dateien bleiben die Quelle. Daraus entsteht ein Master-Markdown. Aus dem Master-Markdown kann später eine Single-File-HTML entstehen. Jede Ausgabe wird gegen Qualitätsregeln geprüft, bevor sie als Version akzeptiert wird.

Wichtig ist dabei eine klare Grenze:

Vorlage / Textauszug

Quelle: Markdown-Dateien je Teil

Konsolidierung: Master-Markdown

Ausgabe: Single-File-HTML

Nachweis: Quality-Report

Archiv: ZIP-Paket

Die Quelle darf nicht durch eine zufällige HTML-Reparatur ersetzt werden. Wenn später im HTML etwas falsch ist, wird nicht wild im HTML geflickt. Stattdessen wird die passende Markdown-Quelle verbessert und danach erneut gebaut.

---

2. Warum dieser Schritt notwendig ist

Die vorherigen schlechten HTML-Versionen hatten ein wiederkehrendes Problem: Sie wurden größer gemacht, ohne fachlich besser zu werden. Dadurch entstanden Tabellen mit ähnlichen Zeilen, generische Überschriften, wiederholte Absätze und künstliche Praxisblöcke. Die Datei war groß, aber der Inhalt war nicht wirklich ein Fachbuch.

Ein sauberes Buildmodell verhindert genau das. Es trennt drei Aufgaben voneinander:

Vorlage / Textauszug
  1. Fachinhalt schreiben
  2. Ausgabeformat erzeugen
  3. Qualität prüfen

Wenn diese drei Aufgaben vermischt werden, entstehen typische Fehler:

Vorlage / Textauszug
  • HTML wird repariert, aber Markdown bleibt falsch.
  • Navigation wird korrigiert, aber Content wird beschädigt.
  • Inhalt wird aufgeblasen, aber Wiederholungen bleiben drin.
  • Tabellen werden erzeugt, obwohl Fließtext benötigt wird.
  • Glossar wird separat angehängt, obwohl Begriffe im Kontext erklärt werden sollen.

Der neue Ablauf ist strenger. Jede spätere HTML-Datei muss aus dem Master-Markdown nachvollziehbar erzeugt werden. Jede Änderung muss in der Quelle landen. Jede Ausgabe bekommt einen Quality-Report.

---

3. Zielstruktur des finalen Delivery-Pakets

Das finale Paket soll nicht aus vielen unklaren Dateinamen bestehen. Es soll eine klare Struktur haben.

Skizze / Textauszug
Modern_Hybrid_Enterprise_Platform_Lab_DELIVERY/
├── README.md
├── CHANGELOG.md
├── QUALITY_REPORT.md
├── source/
│   ├── 01_Teil_1_Unternehmenskontext_und_Zielbild.md
│   ├── 02_Teil_2_Infrastruktur_Fundament.md
│   ├── ...
│   └── 30_Teil_30_Grosser_Schritt_Finaler_Buildlauf_Master_MD_und_HTML_Abnahme.md
├── master/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md
├── web/
│   └── Modern_Hybrid_Enterprise_Platform_Lab_MASTER_SINGLE.html
└── tools/
    ├── build_html.py
    ├── check_repetition.py
    ├── check_forbidden_patterns.py
    └── package_release.py

Für die laufenden Zwischenschritte reicht weiterhin das ZIP mit allen Markdown-Dateien. Für die finale Version wird aber zusätzlich eine Delivery-Struktur erzeugt. Diese Trennung hilft, weil man jederzeit weiß, was Quelle, was Ausgabe und was Werkzeug ist.

---

4. Master-Markdown: Konsolidierung ohne Informationsverlust

Der Master-Markdown ist eine einzige große Markdown-Datei, die alle Teile in der richtigen Reihenfolge enthält. Er ist wichtig für spätere HTML- und PDF-Erzeugung.

Beim Zusammenführen gelten diese Regeln:

Vorlage / Textauszug
  • Reihenfolge der Teile bleibt erhalten.
  • Überschriften werden nicht automatisch umbenannt.
  • Codeblöcke bleiben unverändert.
  • Mermaid-Blöcke bleiben erhalten.
  • Übungen und Musterlösungen bleiben im passenden Teil.
  • Begriffserklärungen bleiben dort, wo sie fachlich passen.
  • Keine generischen Sammelanhänge werden erzeugt.

Nicht erlaubt ist ein Konsolidierungsschritt, der die Datei künstlich verlängert. Der Master-Markdown darf nicht automatisch zusätzliche Absätze erzeugen, die nicht in den Quellen stehen.

---

5. Regeln für die spätere HTML-Ausgabe

Die HTML-Ausgabe muss robust sein. Sie soll auf Desktop, iPad und iPhone nutzbar sein. Besonders wichtig ist, dass die Datei auch dann lesbar bleibt, wenn JavaScript auf einem lokalen iPhone/Safari-Kontext unzuverlässig ist.

Deshalb gilt:

Vorlage / Textauszug

Navigation: native details/summary

Content-Kategorien: native details/summary

Standardzustand: geschlossen

Ankerlinks: echte id-Attribute

Suche: optionaler Komfort, nicht notwendig

Copy-Button: optionaler Komfort, nicht notwendig

Dark Mode: CSS-basiert, nicht abhängig von localStorage

SVGs: inline

Codeblöcke: inline CSS, keine CDN-Abhängigkeit

Die HTML darf nicht auf externe Dateien zeigen. Bilder, Diagramme, CSS und JavaScript müssen entweder inline sein oder weggelassen werden. Für dieses Projekt ist Single-File wichtiger als perfekte Web-App-Funktionalität.

---

6. Navigation: fachlich statt technisch

Die Navigation darf keine Dateinamen oder künstlichen Titel anzeigen. Sie soll fachlich lesen wie ein Inhaltsverzeichnis.

Gut:

Vorlage / Textauszug

Unternehmenskontext und Zielbild

Infrastruktur-Fundament

Cloud-Architektur

VM- und Bare-Metal-Betrieb

Kubernetes- und OpenShift-Plattform

Identity, Security und Governance

Moderne Anwendungen

Daten, Messaging und Integration

Observability und Betrieb

Backup, Restore und Disaster Recovery

DevOps, GitOps und Release Management

Codex und Claude Code im Enterprise-Projekt

Komplettes Beispielprojekt

Übungen, Runbooks und Musterlösungen

Integrierte Begriffserklärungen und Fachindex

Schlecht:

Vorlage / Textauszug

Teil 16

Anhang 16

Praxisdurchlauf 4

integrierte Vertiefung 7

Referenzkarte 12

Erweiterung 3

Die Navigation muss für Menschen gedacht sein, nicht für den Generator.

---

7. Content-Kategorien: geschlossen, aber sinnvoll

Die Content-Kategorien werden standardmäßig geschlossen. Das hat zwei Vorteile. Erstens bleibt eine große HTML-Datei übersichtlich. Zweitens funktioniert die Struktur auf mobilen Geräten besser.

Jede Hauptkategorie bekommt einen klaren Einstieg. Nach dem Öffnen soll der Leser sofort erkennen:

Vorlage / Textauszug
  • Worum geht es?
  • Warum ist es wichtig?
  • Welche Systeme sind betroffen?
  • Welche Beispiele enthält der Abschnitt?
  • Welche Artefakte werden gezeigt?

Ein guter Kategorieeinstieg ist ein kurzer fachlicher Absatz, nicht eine Tabelle und nicht ein Glossarblock.

---

8. Codeblöcke: fachlich eingebettet

Codeblöcke dürfen nicht lose im Dokument stehen. Jeder Codeblock braucht Kontext.

Ein guter Codeblock hat davor:

Vorlage / Textauszug
  • Welche Aufgabe löst der Code?
  • In welchem Teil der Architektur liegt er?
  • Welche Annahmen gelten?
  • Was ist produktionsnah und was ist Lab-Vereinfachung?

Und danach:

Vorlage / Textauszug
  • Welche Stellen sind kritisch?
  • Welche Fehler können auftreten?
  • Wie wird der Code getestet?
  • Welche Betriebsrelevanz hat er?

Beispiel für gute Einbettung:

java
package com.seb4u.demo.spring.order.application;

public interface CreateOrderUseCase {
    CreateOrderResult createOrder(CreateOrderCommand command);
}

Dieser kleine Ausschnitt ist nur sinnvoll, wenn erklärt wird, dass der Use Case die fachliche Eingangsstelle bildet und nicht direkt HTTP, Datenbank oder Messaging kennt.

---

9. Qualitätsprüfung: verbotene Muster

Vor jeder finalen Ausgabe wird auf verbotene Muster geprüft.

Verboten sind:

Vorlage / Textauszug

integrierte Vertiefung

Praxisdurchlauf

Anhang 16

Entscheidung 2

Entscheidung 3

Risiko 2

Risiko 3

Warum das im Lab wichtig ist: klare Nachvollziehbarkeit

Bei Security, Hardening und Audit beginnt die Arbeit mit einer konkreten Fragestellung

Diese Muster stehen stellvertretend für künstlich erzeugte Wiederholungen. Wenn eines davon auftaucht, darf die Ausgabe nicht akzeptiert werden.

---

10. Qualitätsprüfung: Absatzwiederholungen

Nicht jede Wiederholung ist schlecht. Ein Fachbuch darf Begriffe wieder aufnehmen. Aber ganze Absätze dürfen nicht mehrfach mit nur einem ausgetauschten Wort vorkommen.

Die Prüfung soll deshalb Absätze normalisieren:

Vorlage / Textauszug
  • Kleinbuchstaben
  • mehrere Leerzeichen entfernen
  • Zahlen teilweise neutralisieren
  • sehr kurze Absätze ignorieren
  • Codeblöcke separat behandeln

Wenn ein langer Absatz mehrfach vorkommt, ist das ein Warnsignal. Dann muss man prüfen, ob der Inhalt wirklich notwendig ist oder ob der Generator nur Text recycelt hat.

---

11. Beispiel: Python-Prüfung gegen verbotene Muster

python
from pathlib import Path

FORBIDDEN = [
    class="str">"integrierte Vertiefung",
    class="str">"Praxisdurchlauf",
    class="str">"Anhang 16",
    class="str">"Entscheidung 2",
    class="str">"Entscheidung 3",
    class="str">"Risiko 2",
    class="str">"Risiko 3",
    class="str">"Warum das im Lab wichtig ist: klare Nachvollziehbarkeit",
    class="str">"Bei Security, Hardening und Audit beginnt die Arbeit mit einer konkreten Fragestellung",
]

def check_forbidden_patterns(path: Path) -> list[str]:
    text = path.read_text(encoding=class="str">"utf-8", errors=class="str">"ignore")
    findings = []
    for pattern in FORBIDDEN:
        if pattern.lower() in text.lower():
            findings.append(pattern)
    return findings

if __name__ == class="str">"__main__":
    target = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md")
    findings = check_forbidden_patterns(target)
    if findings:
        print(class="str">"Verbotene Muster gefunden:")
        for item in findings:
            print(fclass="str">"- {item}")
        raise SystemExit(1)
    print(class="str">"Keine verbotenen Muster gefunden.")

Dieses Skript ist einfach, aber nützlich. Es verhindert, dass bekannte schlechte Muster unbemerkt wieder in die finale Ausgabe gelangen.

---

12. Beispiel: Python-Prüfung gegen Absatzwiederholungen

python
from pathlib import Path
from collections import Counter
import re

CODE_FENCE = re.compile(rclass="str">"```.*?```", re.DOTALL)

def normalize(paragraph: str) -> str:
    paragraph = paragraph.lower()
    paragraph = re.sub(rclass="str">"\d+", class="str">"#", paragraph)
    paragraph = re.sub(rclass="str">"\s+", class="str">" ", paragraph)
    return paragraph.strip()

def extract_paragraphs(text: str) -> list[str]:
    text = CODE_FENCE.sub(class="str">"", text)
    raw = re.split(rclass="str">"\n\s*\n", text)
    result = []
    for paragraph in raw:
        clean = normalize(paragraph)
        if len(clean) >= 240:
            result.append(clean)
    return result

if __name__ == class="str">"__main__":
    path = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md")
    text = path.read_text(encoding=class="str">"utf-8", errors=class="str">"ignore")
    paragraphs = extract_paragraphs(text)
    counts = Counter(paragraphs)
    duplicates = [(p, c) for p, c in counts.items() if c > 1]

    if duplicates:
        print(class="str">"Mögliche Absatzwiederholungen gefunden:")
        for paragraph, count in duplicates[:20]:
            print(class="str">"---")
            print(fclass="str">"Anzahl: {count}")
            print(paragraph[:500])
        raise SystemExit(1)

    print(class="str">"Keine langen doppelten Absätze gefunden.")

Diese Prüfung ist nicht perfekt, aber sie ist genau für dieses Projekt sinnvoll. Sie schützt vor dem Fehler, der zuvor aufgetreten ist: lange, fast identische Absätze mit kleinen Wortwechseln.

---

13. Beispiel: HTML-Build-Grundstruktur

Die HTML-Erzeugung soll nicht versuchen, eine komplexe Web-App zu bauen. Sie soll eine stabile Lesedatei erzeugen.

python
from pathlib import Path
import html
import re

SOURCE = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER.md")
OUTPUT = Path(class="str">"Modern_Hybrid_Enterprise_Platform_Lab_MASTER_SINGLE.html")

CSS = class="str">"""
:root {
  color-scheme: light dark;
  --bg: #111827;
  --panel: #1f2937;
  --text: #e5e7eb;
  --muted: #9ca3af;
  --accent: #60a5fa;
  --code-bg: #0b1020;
  --border: #374151;
}
body {
  margin: 0;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, class="str">"Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
}
.layout {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  min-height: 100vh;
}
nav {
  border-right: 1px solid var(--border);
  padding: 1rem;
  overflow: auto;
  position: sticky;
  top: 0;
  height: 100vh;
}
main {
  padding: 2rem;
  max-width: 1100px;
}
details {
  border: 1px solid var(--border);
  border-radius: 14px;
  margin: 1rem 0;
  background: rgba(255,255,255,0.03);
}
summary {
  cursor: pointer;
  padding: 1rem;
  font-weight: 700;
}
.details-body {
  padding: 0 1rem 1rem 1rem;
}
pre {
  background: var(--code-bg);
  color: #e5e7eb;
  padding: 1rem;
  border-radius: 14px;
  overflow-x: auto;
  border: 1px solid var(--border);
}
code {
  font-family: class="str">"SFMono-Regular", Consolas, class="str">"Liberation Mono", monospace;
}
@media (max-width: 900px) {
  .layout {
    display: block;
  }
  nav {
    position: relative;
    height: auto;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
  main {
    padding: 1rem;
  }
}
class="str">"""

def slugify(title: str) -> str:
    value = title.lower()
    value = value.replace(class="str">"ä", class="str">"ae").replace(class="str">"ö", class="str">"oe").replace(class="str">"ü", class="str">"ue").replace(class="str">"ß", class="str">"ss")
    value = re.sub(rclass="str">"[^a-z0-9]+", class="str">"-", value).strip(class="str">"-")
    return value or class="str">"abschnitt"

# Ein echter Builder würde Markdown sauber parsen.
# Für das Lab wird diese Skizze als Build-Regel dokumentiert.

Der wichtige Punkt ist nicht dieses konkrete Skript. Der wichtige Punkt ist die Architektur des Buildprozesses: Quelle lesen, prüfen, strukturieren, HTML schreiben, erneut prüfen.

---

14. Umgang mit Mermaid und SVG

Mermaid ist für Markdown gut lesbar. In einer lokalen Single-File-HTML ohne externe Bibliotheken ist Mermaid aber schwierig, weil ein Browser Mermaid nicht automatisch rendert. Deshalb gibt es zwei Möglichkeiten:

Vorlage / Textauszug

Option A: Mermaid-Codeblock anzeigen

Option B: Diagramm als inline SVG nachbauen

Für das finale HTML ist Option B besser, wenn das Diagramm zentral ist. Für ergänzende Diagramme reicht Option A.

Ein inline SVG muss direkt im HTML stehen:

html
class="str">"
0

0

900

260
" role=
class
="str">"img" aria-label=
class
="str">"Hybrid Platform Overview">

class="str">"
20
" y=
class
="str">"
40
" width=
class
="str">"
160
" height=
class
="str">"
70
" rx=
class
="str">"
14
">


class="str">"
100
" y=
class
="str">"
82
" text-anchor=
class
="str">"middle">Customer Portal

So vermeidet man kaputte Bildpfade auf iPhone, in Messenger-Apps oder bei lokal geöffneten Dateien.

---

15. Was der Quality-Report enthalten muss

Der Quality-Report ist kein Marketingtext. Er ist ein Nachweis.

Er enthält:

Vorlage / Textauszug
  1. Build-Datum
  2. Quell-Dateien
  3. erzeugte Dateien
  4. Dateigrößen
  5. Prüfung gegen verbotene Muster
  6. Prüfung gegen Absatzwiederholungen
  7. Prüfung der Navigation
  8. Prüfung der Codeblöcke
  9. Prüfung der inline SVGs
  10. bekannte Grenzen

Beispiel:

Vorlage / Textauszug

Quality Report

Ergebnis

Status: bestanden

Quellmaterial
  • 30 Markdown-Dateien
  • Master-Markdown erzeugt
  • Single-File-HTML erzeugt
Prüfungen
  • verbotene Muster: keine Treffer
  • lange doppelte Absätze: keine kritischen Treffer
  • Navigation: details/summary vorhanden
  • Content-Kategorien: standardmäßig geschlossen
  • externe Assets: keine
  • Codeblöcke: vorhanden
  • inline SVGs: vorhanden
Bekannte Grenzen
  • Copy-Button ist Komfortfunktion
  • Suche ist Komfortfunktion
  • Mermaid wird nur angezeigt oder als SVG nachgebaut

---

16. Abnahmeentscheidung

Eine Ausgabe darf nur als finale Version gelten, wenn folgende Aussagen wahr sind:

Vorlage / Textauszug

Die Inhalte stammen aus Markdown-Quellen.

Die Navigation ist fachlich und stabil.

Die Content-Kategorien sind standardmäßig geschlossen.

Es gibt keine bekannten Füllmuster.

Es gibt keine langen doppelten Absätze aus Generator-Recycling.

Codeblöcke sind lesbar und farblich konsistent.

SVGs sind inline oder bewusst als Codeblock belassen.

Das ZIP enthält README und Quality-Report.

Die Datei ist lokal nutzbar.

Wenn eine dieser Aussagen nicht stimmt, wird nicht improvisiert. Dann wird die Quelle korrigiert und der Build wiederholt.

---

17. Großer Praxisblock: vollständiger Ablauf

Der gesamte Ablauf sieht so aus:

Vorlage / Textauszug
  1. Markdown-Dateien sammeln
  2. Dateinamen prüfen
  3. Reihenfolge bestimmen
  4. Master-Markdown erzeugen
  5. verbotene Muster prüfen
  6. doppelte lange Absätze prüfen
  7. HTML aus Master-Markdown erzeugen
  8. Navigation prüfen
  9. Content-Kategorien prüfen
  10. Codeblöcke prüfen
  11. SVGs prüfen
  12. Quality-Report schreiben
  13. Delivery-ZIP bauen
  14. ZIP testen
  15. optional in Google Drive kopieren

Dieser Ablauf ist bewusst unspektakulär. Genau das ist seine Stärke. Er ist wiederholbar.

---

18. Beispiel: Release-Checkliste

Vorlage / Textauszug

[ ] Alle Markdown-Dateien vorhanden

[ ] README aktuell

[ ] Master-Markdown erzeugt

[ ] Keine verbotenen Muster gefunden

[ ] Keine kritischen Absatzwiederholungen gefunden

[ ] HTML erzeugt

[ ] Navigation funktioniert ohne JavaScript

[ ] Content-Kategorien sind geschlossen

[ ] Codeblöcke sind lesbar

[ ] Tabellen sind nicht Hauptinhalt

[ ] Glossar ist nicht Hauptinhalt

[ ] SVGs sind inline

[ ] ZIP enthält alle relevanten Dateien

[ ] ZIP lässt sich entpacken

[ ] Optional: Drive-Upload erfolgreich

---

19. Typische Fehler beim großen Buildschritt

Ein typischer Fehler ist, die HTML-Datei manuell nachzubessern und danach die Markdown-Quelle zu vergessen. Beim nächsten Build ist der Fehler wieder da.

Ein zweiter Fehler ist, beim Erzeugen der Navigation technische Dateinamen direkt zu übernehmen. Dadurch entstehen Einträge, die für Leser unschön sind.

Ein dritter Fehler ist, Diagramme als externe Dateien abzulegen. Lokal funktionieren sie vielleicht, aber auf iPhone oder nach dem Teilen per Messenger fehlen sie.

Ein vierter Fehler ist, die Datei künstlich über eine Zielgröße zu bringen. Die Größe ist kein Qualitätskriterium, wenn sie durch Wiederholungen entsteht.

Ein fünfter Fehler ist, den Quality-Report nur positiv zu formulieren. Ein guter Quality-Report nennt auch Grenzen und offene Punkte.

---

20. Ergebnis dieses Schritts

Nach diesem Schritt hat das Projekt eine klare technische Übergabelogik. Die Inhalte sind nicht mehr nur einzelne Kapitel. Sie sind Teil eines kontrollierten Buildsystems.

Das wichtigste Ergebnis lautet:

Vorlage / Textauszug

Ab jetzt ist nicht mehr die HTML-Datei die Wahrheit.

Die Markdown-Quelle ist die Wahrheit.

HTML, PDF und ZIP sind reproduzierbare Ausgaben daraus.

Damit kann die nächste große Phase sauber beginnen: die echte finale HTML-Erzeugung aus dem konsolidierten Master-Markdown mit Quality-Report und Delivery-ZIP.

---

21. Übung

Beschreibe in eigenen Worten, warum es gefährlich ist, eine große HTML-Datei direkt zu reparieren, ohne die Markdown-Quelle zu ändern.

Beantworte dabei:

Vorlage / Textauszug
  1. Was passiert beim nächsten Build?
  2. Warum entstehen dadurch widersprüchliche Versionen?
  3. Wie hilft ein Quality-Report?
  4. Warum ist ein Master-Markdown sinnvoll?

---

22. Musterlösung

Eine direkte Reparatur an der HTML-Datei ist gefährlich, weil HTML nur die Ausgabe ist. Wenn die Markdown-Quelle unverändert bleibt, wird der gleiche Fehler beim nächsten Build erneut erzeugt. Dadurch entstehen zwei Wahrheiten: eine korrigierte HTML-Datei und eine falsche Quelle. Später weiß niemand mehr, welche Version korrekt ist.

Ein Master-Markdown hilft, weil alle Inhalte an einer nachvollziehbaren Stelle zusammengeführt werden. Der Quality-Report dokumentiert, welche Prüfungen durchgeführt wurden und ob verbotene Muster oder Wiederholungen gefunden wurden. Dadurch wird die Ausgabe nicht nur erzeugt, sondern auch überprüfbar gemacht.

Die richtige Arbeitsweise lautet daher: Quelle korrigieren, Master-Markdown neu erzeugen, HTML neu bauen, Quality-Report schreiben und ZIP neu packen.

---

23. Merksätze

Vorlage / Textauszug

Die Quelle ist Markdown, nicht HTML.

Eine HTML-Reparatur ohne Quellenänderung ist nur Kosmetik.

Ein großes Dokument braucht Buildregeln, nicht Zufall.

Qualität entsteht durch Inhalt, Struktur und Prüfung.

Ein ZIP ohne README und Quality-Report ist keine saubere Übergabe.

Eine große Datei ist nur dann wertvoll, wenn sie fachlich dicht und nachvollziehbar ist.

Vollständiger HTML-Rebuild ohne Inhaltsverlust

Ziel dieses Schritts

Dieser Schritt korrigiert den Fehler aus dem vorherigen HTML-Build: Die Markdown-Quelle war gewachsen, aber die erzeugte HTML-Datei wurde kleiner. Das ist ein klares Signal, dass der Build nicht alle Formatierungs- und Inhaltsbestandteile sauber übernommen hat. Der neue Build behandelt das Master-Markdown als verbindliche Quelle und erzeugt daraus eine vollständige Single-File-HTML mit stabiler Navigation, Inline-SVGs, dunklen Codeblöcken und sichtbarer Inhaltsstruktur.

Wichtig ist: Dieser Schritt ergänzt nicht künstlich Text, um eine Dateigröße zu erreichen. Er stellt sicher, dass der vorhandene Inhalt vollständig im HTML sichtbar wird. Die Größe darf durch echte HTML-Struktur, Codeblock-Formatierung, Inline-SVGs und Syntax-Markup wachsen, aber nicht durch sinnlose Wiederholungen.

Build-Entscheidung

Die HTML-Datei wird aus allen nummerierten Markdown-Teilen erzeugt. Die Quelle bleibt Markdown. Die HTML-Ausgabe ist eine Lesefassung mit Navigation, Abschnitten, Codeblock-Kacheln und mobilen Layoutregeln.

Der neue Build übernimmt:

  • alle Fachkapitel von Teil 1 bis Teil 35
  • große Praxis-Szenarien aus Teil 34
  • kompakte SVGs aus Teil 32 und Teil 33
  • großen Fachglossar und Querverweise
  • Codebeispiele aus allen Kapiteln
  • Quality-Regeln und Abnahmeabschnitte
  • README- und Delivery-Hinweise

Nicht übernommen werden künstliche Füllmuster. Wenn verbotene Begriffe in Negativlisten vorkommen, werden sie im Quality-Report markiert und nicht als fachliche Kapitel interpretiert.

HTML-Regel

Die Navigation nutzt native details/summary-Elemente. Dadurch funktioniert die Datei auch lokal auf iPhone und Safari zuverlässiger als eine rein JavaScript-basierte Navigation. JavaScript wird nur als Komfort verwendet, zum Beispiel für Copy-Buttons und automatisches Öffnen eines Zielabschnitts beim Anklicken eines Ankerlinks.

Content-Kategorien bleiben standardmäßig geschlossen. Dadurch lädt die Seite übersichtlich und der Leser entscheidet selbst, welchen Teil er öffnet. Die Inhalte werden nicht per JavaScript nachgeladen oder versteckt. Sie sind vollständig im HTML vorhanden.

Codeblock-Regel

Codeblöcke werden als dunkle Kacheln dargestellt. Sie bekommen horizontales Scrollen, Copy-Button und sprachabhängige Klassen. Java, YAML, SQL, Bash, HCL, Markdown und Text werden unterscheidbar behandelt. Das Styling orientiert sich am JetBrains-Dark-Prinzip: dunkler Hintergrund, helle Grundschrift, farbige Keywords, Strings, Zahlen, Kommentare und Schlüssel.

SVG-Regel

Alle SVGs bleiben inline. Es gibt keinen externen Asset-Ordner. Dadurch entstehen keine kaputten Bildpfade, wenn die HTML-Datei lokal, auf iPhone, aus Google Drive oder aus einem ZIP geöffnet wird.

Prüfregel

Nach dem Build wird geprüft:

  • Anzahl der Markdown-Teile
  • Größe des Master-Markdowns
  • Größe der Single-File-HTML
  • Anzahl der Codeblöcke
  • Anzahl der Inline-SVGs
  • verbotene Muster
  • ZIP-Test

Ergebnis dieses Schritts

Dieser Schritt erzeugt eine neue HTML-Version auf Basis des vollständigen Master-Markdowns 1 bis 35. Die HTML darf nicht kleiner sein als die vorherige bessere Version aus Teil 33. Wenn sie kleiner wäre, gilt der Build als nicht abgenommen.

↑ Oben⌂ Cockpit