#Deployment, Runtime, Konfiguration und Secrets
EAR/WAR/EJB-JAR, Application Server, JNDI, Umgebungskonfiguration, Secrets, Runbooks und Smoke Tests.
#Deployment, Application Server, Container, Config, Secrets und Runtime-Betrieb
Dieser Abschnitt ergänzt den Master-Lernpfad um den operativen Teil der Legacy-Modernisierung. Viele Java-Enterprise-Projekte scheitern nicht am Refactoring selbst, sondern an Deployment, Server-Konfiguration, Classloading, JNDI, Secrets, Umgebungsunterschieden und fehlender Betriebsfähigkeit. Deshalb wird dieser Bereich als eigenes Modernisierungs-Slice behandelt.
#Warum Deployment ein eigenes Modernisierungs-Slice ist
Bei alten Java-Enterprise-Systemen ist Deployment selten nur mvn package. Häufig besteht das System aus EAR-, WAR-, EJB-JAR- und Shared-Library-Artefakten, die auf einem Application Server laufen. Dazu kommen Datasources, JMS-Destinations, Security Realms, Keystores, Truststores, System Properties, JNDI-Namen und Server-spezifische Deployment-Deskriptoren.
Typische Legacy-Realität:
legacy-app.ear
├── legacy-web.war
├── legacy-ejb.jar
├── legacy-soap.war
├── lib/
│ ├── shared-model.jar
│ ├── old-vendor-client.jar
│ └── generated-soap-client.jar
└── META-INF/application.xml
Modernisierung bedeutet daher nicht nur Code ändern, sondern auch:
- Deployment reproduzierbar machen
- Runtime-Konfiguration dokumentieren
- Umgebungen vergleichbar machen
- Secrets aus Code und Artefakten entfernen
- Server-Abhängigkeiten sichtbar machen
- Rollback-fähige Deployments vorbereiten
- Monitoring und Health Checks ergänzen
Wichtigster Grundsatz:
Wenn du nicht reproduzierbar deployen kannst,
kannst du nicht sicher modernisieren.
#Deployment-Inventar erstellen
Lege im Projekt diese Datei an:
docs/deployment-inventory.md
Vorlage:
# Deployment Inventory
## Application Server
| Umgebung | Server | Version | Java | Betriebssystem | Verantwortlich |
|---|---|---|---|---|---|
| DEV | WebLogic/WildFly/WebSphere/... | ... | ... | ... | ... |
| TEST | ... | ... | ... | ... | ... |
| PROD | ... | ... | ... | ... | ... |
## Deployables
| Artefakt | Typ | Quelle | Zielserver | Kontext / Name |
|---|---|---|---|---|
| legacy-app.ear | EAR | Maven/Ant/manual | app-cluster-1 | legacy-app |
| legacy-web.war | WAR | web-module | im EAR | /legacy |
| legacy-ejb.jar | EJB-JAR | ejb-module | im EAR | legacy-ejb |
## Deployment-Prozess
| Schritt | Tool | Manuell/Automatisch | Risiko |
|---|---|---|---|
| Build | Maven/Ant | manuell | hoch |
| Artefakt kopieren | SCP/Console/CI | manuell | hoch |
| Server restart | Admin Console | manuell | mittel |
| Smoke Test | Browser | manuell | hoch |
## Rollback
- Gibt es altes Artefakt?
- Wie schnell kann zurückgerollt werden?
- Müssen DB-Migrationen zurückgerollt werden?
- Müssen Queues geleert oder Events neu verarbeitet werden?
Dieses Inventar ist die Grundlage für jeden Cutover.
#EAR/WAR/EJB-JAR-Struktur verstehen
Bei Legacy-Systemen ist Packaging oft Teil der Architektur. Ein EAR bestimmt Classloader-Grenzen, Sichtbarkeit von Klassen und Reihenfolge der Module.
Dokumentiere:
# EAR Structure
| Modul | Typ | Enthält | Darf zugreifen auf | Risiko |
|---|---|---|---|---|
| legacy-web.war | WAR | JSP, Servlet, Filter | EJB Interfaces, Shared DTOs | mittel |
| legacy-soap.war | WAR | SOAP Endpoints | EJB Services, JAXB | hoch |
| legacy-ejb.jar | EJB-JAR | Business Beans, MDBs | Persistence, JMS | hoch |
| legacy-core.jar | JAR | DTOs, Utils, Domain | keine Server APIs idealerweise | niedrig |
| lib/vendor-client.jar | JAR | externer Client | SOAP/Security libs | hoch |
Typische Probleme:
- gleiche Library mehrfach in WAR und EAR/lib
- verschiedene Versionen von JAXB, JAX-WS, Hibernate oder Logging
- App Server bringt eigene APIs mit
- ClassCastException durch unterschiedliche Classloader
- generierte SOAP-Klassen in mehreren Modulen
- DTOs sowohl in Web als auch EJB dupliziert
Modernisierungsziel:
Shared Code reduzieren.
Fachlichen Kern in ein klares Modul ziehen.
Server-APIs an den Rändern halten.
Gute Zielstruktur im Übergang:
legacy-modernization/
legacy-ear/
legacy-web/
legacy-ejb/
order-core/ # Plain Java Domain + Use Cases + Ports
order-adapters/ # JPA/JMS/SOAP Adapter für Legacy Runtime
#Application-Server-Konfiguration sichtbar machen
Viele kritische Einstellungen liegen nicht im Git-Repo, sondern im Application Server.
Zu erfassen sind:
- Datasources
- XA Datasources
- JMS Connection Factories
- Queues und Topics
- Security Realms
- LDAP/AD-Konfiguration
- Keystores und Truststores
- System Properties
- JVM Arguments
- Thread Pools
- EJB Pools
- Transaction Timeout
- JMS Redelivery Settings
- Classloader Policies
- Deployment Order
Vorlage:
# Runtime Server Configuration
## JVM
| Umgebung | Java | Heap | GC | System Properties |
|---|---|---|---|---|
| PROD | 8/11/17 | -Xmx... | ... | ... |
## Transaction Manager
| Einstellung | Wert | Risiko |
|---|---|---|
| Default Transaction Timeout | ... | hoch |
| XA Recovery enabled | ja/nein | hoch |
| Object Store Location | ... | mittel |
## Thread Pools
| Pool | Größe | Genutzt von | Risiko |
|---|---|---|---|
| default | ... | HTTP/EJB | mittel |
| jms-consumer | ... | MDB | hoch |
## EJB Pools
| Bean | Pool Size | Timeout | Risiko |
|---|---|---|---|
| OrderServiceBean | ... | ... | mittel |
| InvoiceMDB | ... | ... | hoch |
Wichtig:
Server-Konfiguration ist Teil des Systems.
Wenn sie nicht versioniert ist, ist sie technischer Blindflug.
#JNDI-Konfiguration externalisieren
Legacy-Code enthält oft direkte JNDI-Lookups:
Queue queue = (Queue) new InitialContext()
.lookup("java:/jms/queue/OrderCreatedQueue");
Oder:
DataSource ds = (DataSource) new InitialContext()
.lookup("java:/jdbc/OrderDS");
Problem:
- Namen sind hart codiert
- Umgebungen unterscheiden sich
- Tests werden schwer
- Migration nach Spring/Quarkus wird schwer
Zwischenschritt:
public interface RuntimeResourceNames {
String orderDatasource();
String orderPaidQueue();
String paymentConnectionFactory();
}
Legacy-Implementierung:
public class PropertiesRuntimeResourceNames implements RuntimeResourceNames {
private final Properties properties;
public PropertiesRuntimeResourceNames(Properties properties) {
this.properties = properties;
}
@Override
public String orderDatasource() {
return properties.getProperty("jndi.datasource.order");
}
@Override
public String orderPaidQueue() {
return properties.getProperty("jndi.queue.orderPaid");
}
@Override
public String paymentConnectionFactory() {
return properties.getProperty("jndi.jms.connectionFactory");
}
}
Konfiguration:
jndi.datasource.order=java:/jdbc/OrderDS
jndi.queue.orderPaid=java:/jms/queue/OrderPaidQueue
jndi.jms.connectionFactory=java:/ConnectionFactory
Ziel:
JNDI bleibt am Rand.
Der fachliche Kern kennt keine JNDI-Namen.
#Umgebungen vergleichbar machen
Ein häufiger Legacy-Satz lautet:
In TEST funktioniert es, in PROD nicht.
Deshalb brauchst du eine Environment-Matrix:
# Environment Matrix
| Kategorie | DEV | TEST | STAGE | PROD | Abweichung kritisch? |
|---|---|---|---|---|---|
| Java Version | ... | ... | ... | ... | ja/nein |
| App Server Version | ... | ... | ... | ... | ja/nein |
| DB Version | ... | ... | ... | ... | ja/nein |
| JMS Provider | ... | ... | ... | ... | ja/nein |
| SOAP Endpoint URLs | ... | ... | ... | ... | ja/nein |
| Transaction Timeout | ... | ... | ... | ... | ja/nein |
| Queue Redelivery | ... | ... | ... | ... | ja/nein |
| Security Realm | ... | ... | ... | ... | ja/nein |
Besonders kritisch sind Unterschiede bei:
- Transaction Timeout
- XA / non-XA Datasources
- JMS Redelivery Count
- Thread Pool Größe
- SOAP Timeouts
- Encoding und Locale
- Timezone
- DB Isolation Level
Empfehlung:
Stage sollte PROD so ähnlich wie möglich sein.
DEV darf einfacher sein, aber Abweichungen müssen bewusst sein.
#Konfiguration in Code, Artefakt und Runtime trennen
Schlechtes Legacy-Muster:
private static final String PAYMENT_URL = "https://prod-payment.example.com/soap";
Oder:
<property name="password" value="secret123" />
Zielstruktur:
Code:
- enthält keine Umgebung
- enthält keine Secrets
- enthält keine PROD-URLs
Artefakt:
- ist für alle Umgebungen gleich
Runtime Config:
- unterscheidet DEV/TEST/STAGE/PROD
- wird beim Deployment injiziert
Konfigurationskategorien:
| Kategorie | Beispiel | Darf ins Git? | Bemerkung |
|---|---|---|---|
| Fachliche Defaults | max retry = 3 | ja | wenn nicht geheim |
| Umgebungsspezifische URL | payment endpoint | ja, wenn nicht geheim | besser als Config |
| Passwort | DB password | nein | Secret Store |
| Zertifikat Private Key | keystore key | nein | Secret Store / Vault |
| Feature Flag | new payment flow enabled | ja/Config | kontrollierter Rollout |
#Secrets inventarisieren und entfernen
Suche nach:
password
passwd
secret
token
apikey
api_key
client_secret
keystore
truststore
javax.net.ssl
BasicAuth
Authorization
username
credential
Dokumentiere:
# Secrets Inventory
| Secret | Typ | Aktueller Ort | Zielort | Rotation möglich? | Risiko |
|---|---|---|---|---|---|
| DB password | Passwort | Server config | Secret Store | ja | hoch |
| SOAP cert password | Keystore-Passwort | properties file | Vault/K8s Secret | prüfen | hoch |
| Payment API key | Token | Code? | Secret Store | ja | kritisch |
Regeln:
- Keine Secrets im Code
- Keine Secrets im Git
- Keine Secrets im Log
- Keine Secrets in Fehlermeldungen
- Rotation planen
- Zugriffe auditieren
Für Legacy-Umgebungen kann ein erster Schritt sein:
- Secrets aus Properties-Dateien entfernen
- über Server-Konfiguration injizieren
- Dateirechte härten
- Zugriff dokumentieren
Später:
- Vault
- Kubernetes Secrets
- Cloud Secret Manager
- externe Secret Injection
#Keystore und Truststore kontrollieren
SOAP- und Enterprise-Systeme nutzen oft Zertifikate.
Zu erfassen:
# TLS / Keystore Inventory
| Zweck | Datei / Alias | Umgebung | Ablaufdatum | Passwort-Ort | Risiko |
|---|---|---|---|---|---|
| Payment SOAP Client Cert | payment-client.jks / payment | PROD | ... | Server secret | hoch |
| Truststore Partner CA | truststore.jks | PROD | ... | Server secret | mittel |
Prüfe:
- Welche Zertifikate laufen bald ab?
- Sind Keystores pro Umgebung getrennt?
- Werden Passwörter geloggt?
- Gibt es Dokumentation für Rotation?
- Gibt es Monitoring für Ablaufdatum?
- Werden Truststores hart im Artefakt gepackt?
Sicheres Ziel:
Zertifikate und Passwörter gehören nicht in das Anwendungsartefakt.
Die Anwendung bekommt Pfade/Secrets über Runtime-Konfiguration.
#Feature Flags für risikoarme Migration
Bei Modernisierung brauchst du kontrollierte Umschaltpunkte.
Beispiele:
feature.order.useNewCreateOrderUseCase=true
feature.payment.useOutbox=false
feature.invoice.consumeOrderPaid=true
feature.security.useNewRoleMapping=false
Feature-Flag-Regeln:
- Flags müssen dokumentiert sein
- Default muss bewusst sein
- Flags brauchen Owner
- Flags brauchen Ablaufdatum
- Flags dürfen keine dauerhafte Architektur werden
- Kritische Flags müssen auditierbar sein
Feature-Flag-Dokumentation:
# Feature Flags
| Flag | Zweck | Default | PROD Wert | Owner | Entfernen bis |
|---|---|---|---|---|---|
| feature.payment.useOutbox | Payment über Outbox statt direkt | false | false | Team Order | nach Cutover |
| feature.invoice.consumeOrderPaid | Invoice auf OrderPaid statt OrderCreated | false | false | Team Billing | nach Parallel Run |
Beispiel im Code:
public class CreateOrderFacade {
private final boolean useNewUseCase;
private final LegacyOrderService legacyOrderService;
private final CreateOrderApplicationService newOrderService;
public String createOrder(CreateOrderRequest request) {
if (useNewUseCase) {
return newOrderService.createOrder(request);
}
return legacyOrderService.createOrder(request);
}
}
#Blue-Green und Rolling Deployment für Legacy realistisch bewerten
Nicht jedes Legacy-System ist sofort rolling-deploy-fähig.
Prüfe:
# Deployment Strategy Assessment
| Frage | Ja/Nein | Risiko |
|---|---|---|
| Ist die App stateless? | ... | hoch |
| Gibt es HTTP Session State? | ... | hoch |
| Sind DB-Migrationen rückwärtskompatibel? | ... | hoch |
| Können zwei Versionen parallel laufen? | ... | hoch |
| Sind JMS Consumer versionstolerant? | ... | hoch |
| Sind SOAP Contracts kompatibel? | ... | hoch |
Wenn HTTP Session State stark genutzt wird, ist Rolling Deployment schwieriger.
Mögliche Strategien:
Big Stop Deployment:
- einfach, aber Downtime
- oft Legacy-Realität
Rolling Deployment:
- nur möglich, wenn Versionen kompatibel sind
- Session und DB beachten
Blue-Green:
- zwei komplette Umgebungen
- schneller Cutover
- braucht Datenbankstrategie
Canary:
- kleiner Traffic-Anteil auf neue Version
- braucht Routing und Observability
Für Legacy-Modernisierung ist oft der erste realistische Schritt:
Reproduzierbarer Stop/Start-Deploy mit automatischem Smoke Test.
Danach:
Blue-Green oder Rolling vorbereiten.
#Rollback-Plan pro Modernisierungs-Slice
Jeder Slice braucht einen Rollback-Plan.
Vorlage:
# Rollback Plan: Slice 001 Create Order
## Änderung
- CreateOrderUseCase extrahiert
- Outbox PaymentRequested eingeführt
- Feature Flag: feature.payment.useOutbox
## Rollback-Auslöser
- Fehlerquote > 2%
- PENDING Outbox Events älter als 10 Minuten
- Payment UNKNOWN steigt stark
- Invoice-Erzeugung bleibt aus
## Technischer Rollback
1. Feature Flag zurück auf false
2. Neue Worker deaktivieren
3. Alte OrderCreatedQueue Consumer aktiv lassen
4. Smoke Test ausführen
5. Monitoring prüfen
## Daten-Rollback
- Bereits erzeugte Outbox Events bleiben erhalten
- Nicht veröffentlichte Events auf HOLD setzen
- Keine Daten löschen ohne manuelle Prüfung
## Nacharbeit
- Fehleranalyse
- betroffene Orders identifizieren
- manuelle Korrektur falls nötig
Wichtig:
Rollback ist nicht nur Artefakt zurückspielen.
Daten, Queues und externe Side Effects müssen mitgedacht werden.
#Smoke-Test nach Deployment automatisieren
Ein Deployment ohne Smoke Test ist blind.
Minimum-Smoke-Test:
- App startet
- Health Endpoint erreichbar
- DB Verbindung funktioniert
- JMS Verbindung funktioniert
- SOAP Client Config geladen
- Login funktioniert
- ein read-only Use Case funktioniert
Beispiel-Script:
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-https://test.example.com/legacy}"
curl -fsS "$BASE_URL/health" > /dev/null
curl -fsS "$BASE_URL/health/db" > /dev/null
curl -fsS "$BASE_URL/health/jms" > /dev/null
curl -fsS "$BASE_URL/health/config" > /dev/null
echo "Smoke test passed"
Für Legacy ohne Health Endpoints:
- Login-Seite abrufen
- statische Ressource abrufen
- einfache Suchseite aufrufen
- technisches Ping-Servlet ergänzen
Später:
- synthetischer Use Case
- Test-Order anlegen in Testumgebung
- JMS Test Message
- SOAP Mock Call
#Health Checks für Runtime-Ressourcen
Gute Health Checks unterscheiden:
Liveness:
- Ist der Prozess lebendig?
Readiness:
- Kann die App Traffic verarbeiten?
Dependency Health:
- DB, JMS, SOAP, Keystore, Outbox okay?
Beispiel:
# Health Checks
| Check | Kritisch für Readiness? | Beschreibung |
|---|---|---|
| app alive | ja | Prozess läuft |
| db connectivity | ja | SELECT 1 |
| jms connectivity | abhängig | ConnectionFactory erreichbar |
| outbox backlog | nein/ja | PENDING Events nicht zu alt |
| payment soap endpoint | nein | nicht immer extern blockieren |
| keystore loaded | ja | Zertifikat verfügbar |
Achtung:
Externe Partner-Systeme sollten Readiness nicht immer hart blockieren.
Sonst nimmt ein Payment-Ausfall deine ganze App aus dem Load Balancer.
Besser:
- kritische interne Ressourcen: readiness-relevant
- externe Systeme: degraded status + Alarm
#Containerisierung von Legacy-Systemen realistisch planen
Nicht jede EAR-Anwendung sollte sofort in Docker/Kubernetes wandern. Aber Containerisierung kann hilfreich sein, um Builds, Testumgebungen und Deployments reproduzierbar zu machen.
Bewerte:
# Containerization Assessment
| Frage | Antwort | Risiko |
|---|---|---|
| Läuft der App Server headless? | ... | mittel |
| Kann Konfiguration automatisiert eingespielt werden? | ... | hoch |
| Sind Logs stdout-kompatibel? | ... | mittel |
| Sind Sessions extern oder stateless? | ... | hoch |
| Sind Keystores/Secrets injizierbar? | ... | hoch |
| Ist Startup-Zeit akzeptabel? | ... | mittel |
Mögliche Zwischenziele:
Stufe 1:
- Container nur für lokale Entwicklungsumgebung
Stufe 2:
- Container für Integration Tests
Stufe 3:
- Container für Test/Staging Deployment
Stufe 4:
- Produktion, wenn Betrieb bereit ist
Nicht zuerst:
Riesiges Legacy-EAR ohne Tests direkt nach Kubernetes verschieben.
#Docker Compose für lokale Legacy-Abhängigkeiten
Auch wenn die App selbst noch im alten Server läuft, kannst du lokale Abhängigkeiten containerisieren:
version: "3.9"
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: legacy
POSTGRES_USER: legacy
POSTGRES_PASSWORD: legacy
ports:
- "5432:5432"
activemq:
image: apache/activemq-classic:latest
ports:
- "61616:61616"
- "8161:8161"
wiremock:
image: wiremock/wiremock:latest
ports:
- "8089:8080"
Ziel:
Entwickler können lokal testen,
ohne auf geteilte TEST-Umgebung angewiesen zu sein.
Dokumentiere:
# Local Runtime
## Start
```bash
docker compose up -d
#Services
| Service | URL | Zweck |
|---|---|---|
| DB | localhost:5432 | Legacy Test DB |
| JMS | localhost:61616 | Queue Tests |
| WireMock | localhost:8089 | SOAP/HTTP Mock |
---
## Konfigurationsdrift verhindern
Konfigurationsdrift entsteht, wenn DEV, TEST und PROD manuell unterschiedlich gepflegt werden.
Gegenmaßnahmen:
```text
- Server-Konfiguration exportieren
- Runtime-Inventar versionieren
- Infrastrukturänderungen über Pull Requests
- Drift-Checks in CI/CD
- regelmäßiger Vergleich von Environment-Matrix
Beispiel Drift-Check:
# Config Drift Check
| Einstellung | TEST | PROD | Gleich? | Aktion |
|---|---|---|---|---|
| tx timeout | 300s | 60s | nein | klären |
| JMS redelivery | 5 | 10 | nein | dokumentieren |
| Payment timeout | 30s | 10s | nein | prüfen |
Wichtig:
Nicht jede Abweichung ist falsch.
Aber jede Abweichung muss bewusst sein.
#Runtime-Runbooks schreiben
Ein Runbook beschreibt, was der Betrieb bei Problemen tun soll.
Für Complex Slice 001 brauchst du mindestens:
- Outbox backlog wächst
- Payment UNKNOWN steigt
- JMS Consumer verarbeitet nicht
- Invoice doppelt erzeugt
- SOAP Payment Provider down
- DB Migration fehlgeschlagen
Vorlage:
# Runbook: Outbox Backlog
## Symptom
- Metrik `outbox.pending.count` steigt
- ältestes PENDING Event > 10 Minuten
## Impact
- Payments oder Invoices verzögern sich
## Prüfung
```sql
SELECT event_type, status, COUNT(*)
FROM outbox_event
GROUP BY event_type, status;
SELECT *
FROM outbox_event
WHERE status = 'FAILED'
ORDER BY created_at
FETCH FIRST 20 ROWS ONLY;
#Sofortmaßnahmen
- Logs des Outbox Publishers prüfen
- JMS Broker Health prüfen
- SOAP Endpoint Health prüfen
- DEAD Events nicht automatisch löschen
- Bei Bedarf Publisher pausieren
#Recovery
- Ursache beheben
- FAILED Events auf PENDING zurücksetzen, wenn sicher
- Consumer-Idempotenz prüfen
- Monitoring beobachten
Runbooks sind ein großer Professionalisierungsschritt.
---
## Deployment-Plan für Complex Slice 001
Für den Order/Payment/Invoice-Slice könnte ein sicherer Deployment-Plan so aussehen:
```markdown
# Deployment Plan: Complex Slice 001
## Vorbedingungen
- Tests grün
- DB Migrationen in STAGE getestet
- Outbox Tabelle vorhanden
- Processed Message Tabelle vorhanden
- Feature Flags vorhanden
- Monitoring Dashboard vorhanden
- Rollback-Plan geprüft
## Schritt 1: Passive Infrastruktur deployen
- Outbox Tabelle hinzufügen
- Processed Message Tabelle hinzufügen
- neue Codepfade deployen, aber Flags aus
- keine Verhaltensänderung in PROD
## Schritt 2: Shadow Mode
- neuer Use Case schreibt optional Vergleichslogs
- keine externen Side Effects
- Metriken prüfen
## Schritt 3: Outbox für kleinen Traffic aktivieren
- Flag für internen Testnutzer oder Mandant aktivieren
- PaymentRequested Events beobachten
- OrderPaid Events beobachten
- Invoice-Erzeugung prüfen
## Schritt 4: Ausrollen
- Traffic-Anteil erhöhen
- Backlog beobachten
- Fehlerquote beobachten
- Payment UNKNOWN beobachten
## Schritt 5: Alte Pfade deaktivieren
- direkter JMS Send aus CreateOrder entfernen
- OrderCreated Consumer deaktivieren
- alte Flags entfernen
## Rollback
- Flag zurücksetzen
- Worker pausieren
- Outbox Events auf HOLD setzen
- Legacy Flow wieder aktivieren
#Runtime-Plan für dein echtes Projekt
Als nächstes solltest du für dein echtes Projekt diese Dateien erstellen:
docs/deployment-inventory.md
docs/runtime-server-configuration.md
docs/environment-matrix.md
docs/secrets-inventory.md
docs/feature-flags.md
docs/rollback-plan-template.md
docs/runbooks/outbox-backlog.md
docs/runbooks/jms-consumer-failure.md
docs/runbooks/payment-provider-down.md
Arbeitsreihenfolge:
1. Deployables identifizieren
2. App-Server-Konfiguration erfassen
3. JNDI-Ressourcen dokumentieren
4. Secrets inventarisieren
5. Environment-Matrix bauen
6. Deployment-Prozess dokumentieren
7. Smoke-Test automatisieren
8. Rollback-Plan für ersten Slice schreiben
9. Runbook für kritischsten Fehlerfall schreiben
10. Erst dann produktionsnahe Modernisierung deployen
Merksatz:
Eine Modernisierung ist erst dann produktionsfähig,
wenn Deployment, Config, Secrets, Rollback und Betrieb verstanden sind.