Das Deployment-Spektrum
Es gibt nicht die eine „Cloud“. Es gibt ein Spektrum, das beschreibt, wie viel der Maschine man selbst kontrolliert, und jedes Produkt ist irgendwo auf dieser Skala einzuordnen.
Ein Virtual Private Server (VPS) ist eine Maschine, die man mietet und selbst administriert. Man installiert die Runtime, konfiguriert einen Reverse Proxy, verwaltet TLS-Zertifikate und hält das OS aktuell. Er ist günstig, flexibel und man kann ihn komplett selbst zerschießen. Er ist die richtige Wahl, wenn man eine spezifische Runtime benötigt oder jede einzelne Ebene verstehen möchte.
Platform as a Service (PaaS) wie Railway, Render oder Fly.io nehmen den Code oder das Image entgegen und führen es aus. Man definiert einen Port und einen Health Check; die Plattform kümmert sich um TLS, Routing, Neustarts und Scaling. Hier sollten die meisten kleinen Teams anfangen.
Managed Containers wie AWS ECS, Google Cloud Run oder Azure Container Apps führen ein Container-Image mit mehr Einstellmöglichkeiten als eine PaaS aus: Networking, IAM-Rollen, Autoscaling-Regeln. Vom operativen Aufwand her liegen sie zwischen einer PaaS und einem Cluster.
Serverless Functions wie AWS Lambda oder Cloudflare Workers führen pro Request eine Funktion aus, skalieren auf Null und rechnen pro Aufruf ab. Sie sind hervorragend für sprunghafte, kurzlebige Aufgaben geeignet, aber unpraktisch für langlebige Verbindungen und CPU-intensive Prozesse.
Kubernetes ist ein Allzweck-Orchestrator. Es bietet die größte Kontrolle, aber auch die größte Angriffsfläche. Es ist gerechtfertigt, wenn man viele Services mit einem Platform-Team betreibt; für eine einzelne API ist es Overkill.
Der richtige Weg zur Auswahl ist, am managed Ende zu beginnen und sich erst dann in Richtung mehr Kontrolle zu bewegen, wenn eine konkrete Einschränkung auftritt. Der häufigste Fehler ist die Einführung von Kubernetes für einen einzigen Service, nur weil es sich wie die „professionelle“ Entscheidung anfühlt.
| Option | Du verwaltest | Gut für | Achtung bei |
|---|---|---|---|
| VPS | OS, Runtime, Proxy, TLS | Volle Kontrolle, eigene Runtimes | Patching, Failover, On-Call |
| PaaS | Nur die Applikation | Kleine Teams, schnelle Iteration | Weniger Kontrolle, Kosten pro Einheit |
| Managed Containers | Image, IAM, Scaling-Regeln | Services, die Konfiguration benötigen | Mehr Konfigurationsaufwand als PaaS |
| Serverless | Eine Funktion nach der anderen | Sprunghafte, kurzlebige Aufgaben | Cold Starts und Zeitlimits |
| Kubernetes | Alles oberhalb des Kernels | Viele Services, Platform-Teams | Hoher operativer Aufwand |
Was „managed“ tatsächlich bedeutet
Das Wort managed verbirgt eine Liste von Aufgaben, für die ihr nicht mehr zuständig seid.
- Patching. Der Kernel, die Runtime und das Base Image werden aktualisiert, ohne dass ihr ein Wartungsfenster planen müsst.
- TLS. Zertifikate werden automatisch ausgestellt und erneuert, und HTTP wird auf HTTPS umgeleitet.
- Scaling. Instanzen werden hinzugefügt, wenn die CPU-Auslastung oder die Anzahl der Anfragen einen Schwellenwert überschreiten, und wieder entfernt, wenn sie sinken.
- Health und Restarts. Ein Prozess, der abstürzt oder seinen Health Check nicht besteht, wird automatisch ohne menschliches Eingreifen ersetzt.
- Logs und Metrics. Die Ausgaben werden zentral gesammelt und sind abfragbar, anstatt in einer Datei auf einem Server zu liegen, auf den ihr euch per SSH einloggen müsst.
- Backups. Managed Datenbanken erstellen Snapshots und unterstützen Point-in-Time Recovery.
Was ihr dafür aufgebt, sind die volle Kontrolle und ein Stück Kosteneffizienz. Ihr könnt den Kernel nicht optimieren, keine beliebigen Systempakete installieren oder den letzten Cent aus einer Maschine herausholen. Für die meisten Teams ist das ein fairer Tausch: Die Alternative wäre eine On-Call-Rotation für eine Infrastruktur, in der ihr nicht spezialisiert seid.
Die Ausnahme sind Daten. Managed PostgreSQL, Redis und Object Storage lohnen sich fast immer. Der Aufbau von zuverlässigen Backups, Failover und Replikation in Eigenregie ist ein eigenes Projekt, und die managed Version ist in der Regel günstiger als die Ingenieursstunden, die sie einspart.
Twelve-Factor-Ansatz
Die Twelve-Factor App ist eine bewährte Checkliste, die Cloud-native Deployments auch heute noch präzise beschreibt. Drei dieser Faktoren sind im Alltag besonders wichtig.
Konfiguration in der Umgebung. Alles, was zwischen verschiedenen Deployments variiert – Datenbank-URLs, API-Keys, Feature-Flags –, wird über Umgebungsvariablen gesteuert und nicht über Dateien, die im Repository eingecheckt sind. Das ermöglicht es, dass ein einziges Image in jeder Umgebung läuft.
DATABASE_URL=postgres://user:pass@host:5432/app
REDIS_URL=redis://host:6379
PORT=8080
Zustandslose Prozesse. Eine Instanz hält keinen dauerhaften Zustand. Sie kann beliebig gestartet, gestoppt, dupliziert und zerstört werden. Sessions, Uploads und Caches liegen in externen Diensten.
Logs als Event-Streams. Die Anwendung schreibt strukturierte Zeilen an stdout und unternimmt sonst nichts damit. Die Plattform sammelt, speichert und durchsucht diese. Es gibt keine Log-Dateien, die rotiert werden müssen, und keine Festplatten, die vollaufen.
Ein vierter Faktor ist besonders hervorzuheben: Disposability (Verwerfbarkeit). Schneller Start und ein Graceful Shutdown. Ein Prozess, der eine Minute zum Booten benötigt, macht das Scaling und Deployments langsam; einer, der SIGTERM ignoriert, lässt laufende Requests einfach fallen.
Ein kleines Container-Image erstellen
Ein gutes Production-Image ist klein, reproduzierbar und wird als Non-Root-User ausgeführt. Ein Multi-Stage-Build vereint alle drei Anforderungen.
FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:22-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/package.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]
Die Build-Stage installiert alles, kompiliert den Code und entfernt anschließend die Development-Dependencies. Die Runtime-Stage kopiert nur node_modules, dist und das Manifest. TypeScript, Test-Frameworks und Quelldateien gelangen so niemals in die Production.
USER node verzichtet auf Root-Rechte, sodass ein kompromittierter Prozess seine Rechte nicht einfach ausweiten kann. NODE_ENV=production deaktiviert das Development-Verhalten und aktiviert Framework-Optimierungen. Fixieren Sie das Base-Image auf eine spezifische Variante und bevorzugen Sie bookworm-slim oder ein Distroless-Image gegenüber einer vollständigen Debian-Installation, um die Angriffsfläche zu verringern.
Fügen Sie eine .dockerignore hinzu, damit der Build-Kontext klein bleibt:
node_modules
.git
dist
.env
Ein kleineres Image wird schneller heruntergeladen, was Cold Starts und Deployment-Zeiten direkt verkürzt.
Umgebungskonfiguration und Secrets
Konfigurationen sollten injiziert und niemals fest im Code verbaut werden. Dasselbe Image muss sowohl in der Staging- als auch in der Production-Umgebung laufen, wobei lediglich die Umgebung variiert.
Die Mechanismen unterscheiden sich je nach Plattform, aber das Prinzip bleibt gleich: Eine Konfigurationsdatei definiert nicht-sensible Werte, während ein Secret Store die sensiblen Daten verwaltet.
[env]
NODE_ENV = "production"
LOG_LEVEL = "info"
[[services]]
internal_port = 8080
Secrets werden separat gesetzt und niemals in das Repository eingecheckt:
fly secrets set DATABASE_URL=postgres://...
fly secrets set STRIPE_SECRET_KEY=sk_live_...
In Kubernetes kommen Secrets entweder als Umgebungsvariablen oder als gemountete Dateien an:
envFrom:
- secretRef:
name: api-secrets
Zwei Gewohnheiten sorgen hier für Sicherheit. Erstens: Validieren Sie die Konfiguration beim Start und lassen Sie die Anwendung mit einer deutlichen Fehlermeldung abstürzen, falls etwas fehlt, anstatt erst beim ersten Request, der den Wert benötigt, einen Fehler zu werfen. Zweitens: Bevorzugen Sie identitätsbasierten Zugriff: Eine Workload Identity oder eine Instance Role ermöglicht es der App, kurzlebige Credentials abzurufen, ohne dass überhaupt ein statisches Secret benötigt wird.
import { z } from "zod";
const Env = z.object({
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
PORT: z.coerce.number().default(3000),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});
const parsed = Env.safeParse(process.env);
if (!parsed.success) {
console.error("invalid configuration", parsed.error.flatten().fieldErrors);
process.exit(1);
}
export const config = parsed.data;
Der Prozess weigert sich zu starten, wenn die Umgebung unvollständig ist. Dadurch wird eine ganze Klasse von Runtime-Überraschungen in einen sofortigen, offensichtlichen Deployment-Fehler verwandelt. Loggen Sie niemals die Umgebung selbst: Eine einzige Debug-Zeile, die process.env ausgibt, kann jedes Secret preisgeben, über das der Service verfügt.
Zero-downtime releases und Health Checks
Ein Deployment, bei dem Anfragen verloren gehen, fällt den Nutzern auf. Zero-downtime releases hängen von zwei Dingen ab: neue Instanzen zu starten, bevor alte gestoppt werden, und zu wissen, wann eine neue Instanz tatsächlich bereit ist.
Readiness bedeutet, dass die Instanz Traffic verarbeiten kann. Sie hat die Verbindung zur Datenbank hergestellt, ihre Caches aufgewärmt und den Startvorgang abgeschlossen. Erst dann sollte der Load Balancer Anfragen an sie routen.
Liveness bedeutet, dass die Instanz weiterhin gesund ist. Wenn sie wiederholt fehlschlägt, wird sie von der Plattform beendet und ersetzt.
app.get("/healthz", async (req, res) => {
try {
await db.query("select 1");
res.status(200).json({ status: "ok" });
} catch {
res.status(503).json({ status: "unhealthy" });
}
});
Halten Sie den Health Check ressourcensparend und ehrlich. Die Datenbank zu prüfen ist angemessen; fünf Downstream-Services aufzurufen hingegen nicht, da dies einen kurzen Ausfall an anderer Stelle in eine Restart-Schleife verwandeln kann. Wenn eine Abhängigkeit optional ist, melden Sie den Status als „healthy“ und implementieren Sie ein Graceful Degradation.
Graceful Shutdown ist die andere Hälfte. Bei SIGTERM sollten Sie aufhören, neue Verbindungen zu akzeptieren, laufende Anfragen abschließen, den Datenbank-Pool schließen und den Prozess beenden. Geben Sie der Plattform eine Grace Period, die geringfügig länger ist als die langsamste Anfrage.
process.on("SIGTERM", () => {
server.close(async () => {
await db.end();
await redis.quit();
process.exit(0);
});
setTimeout(() => process.exit(1), 15_000).unref();
});
Das Timeout dient als Sicherheitsnetz: Wenn etwas hängen bleibt, wird der Prozess dennoch beendet, bevor die Grace Period der Plattform abläuft und die Plattform zu SIGKILL greift.
Horizontale Skalierung und Statelessness
Horizontale Skalierung bedeutet, dass mehr Kopien derselben Instanz ausgeführt werden. Dies funktioniert nur, wenn die Instanzen austauschbar sind, was voraussetzt, dass keine von ihnen einen eindeutigen State hält.
Der State gehört in Services, die genau dafür entwickelt wurden:
- Sessions in Redis, nicht im Arbeitsspeicher oder allein in einem signierten Cookie.
- Uploads in einem Object Storage wie S3 oder R2, nicht auf der lokalen Festplatte.
- Caches in Redis oder einem CDN, nicht in einer prozesslokalen Map.
- Background jobs in einer Queue mit dedizierten Workern, nicht über einen Timer innerhalb des Web-Prozesses.
Sobald der State externalisiert ist, wird Skalierung zu einer bloßen Zahl. Die Plattform fügt unter Last Instanzen hinzu und entfernt sie wieder, wenn die Last sinkt, wobei jede Instanz jede beliebige Anfrage bedienen kann.
Autoscaling benötigt ein Signal. CPU ist der gängige Standard, aber für I/O-bound Node.js Services bilden die Request-Concurrency oder die Queue-Tiefe die Last oft besser ab. Skalieren Sie basierend auf der Metrik, die die Sättigung tatsächlich vorhersagt, und legen Sie immer eine minimale Instanzanzahl fest, damit der Service einen Traffic-Spike überlebt, während neue Instanzen booten.
Denken Sie an das Verbindungsproblem: Jede Instanz öffnet ihren eigenen Datenbank-Pool. Zwanzig Instanzen mit einem Pool von zwanzig benötigen vierhundert Verbindungen, was PostgreSQL so nicht zulassen wird. Begrenzen Sie den Pool pro Instanz und setzen Sie einen Pooler vor die Datenbank.
Managed Postgres und Redis
Die Datenbank ist der Teil des Stacks, der sich am wenigsten dazu eignet, selbst betrieben zu werden – und gleichzeitig am verlockendsten ist, es trotzdem zu tun. Ein managed Postgres bietet Ihnen automatisierte Backups, Point-in-Time-Recovery, ein Failover-Standby und oft Read-Replicas, ohne dass Sie sich um den betrieblichen Aufwand kümmern müssen.
Zwei Regeln sorgen für einen stabilen Betrieb. Erstens: Dimensionieren Sie Verbindungen bewusst. Setzen Sie max im Pool auf eine kleine Zahl pro Instanz und nutzen Sie einen Pooler wie PgBouncer für Deployments mit vielen Instanzen. Zweitens: Führen Sie Migrationen als Release-Schritt aus, nicht beim Booten der Anwendung. Wenn jede Instanz beim Start migriert, führt ein Rolling Deploy dieselbe Migration fünfmal gleichzeitig aus.
# run once, before the new version starts
npm run db:migrate
Managed Redis ist der natürliche Ort für Sessions, Rate-Limit-Counter und Job-Queues. Aktivieren Sie die Persistenz, wenn die Daten wichtig sind, und behandeln Sie die Instanz als gemeinsame Abhängigkeit, deren Latenz jede Anfrage beeinflusst. Halten Sie Redis in derselben Region wie die Anwendung; ein Cross-Region-Hop bei jedem Cache-Read ist eine „Steuer“, die Sie spüren werden.
DNS, TLS und eigene Domains
DNS ordnet einem Namen die Plattform zu und TLS sorgt dafür, dass die Verbindung vertrauenswürdig ist. Beides ist mittlerweile weitgehend automatisiert, aber die Details sind nach wie vor wichtig.
Setzen Sie einen A- oder ALIAS-Record auf die Adresse der Plattform oder einen CNAME-Record auf deren Hostnamen. Verwenden Sie während der Migration eine kurze TTL, damit Fehler schnell behoben werden können, und erhöhen Sie diese anschließend. Halten Sie die Apex-Domain und den www-Host konsistent und leiten Sie einen auf den anderen um, damit Links und Cookies nicht über zwei Origins fragmentiert werden.
TLS wird von den meisten Plattformen automatisch ausgestellt und erneuert. Erzwingen Sie HTTPS, aktivieren Sie HSTS, sobald Sie sicher sind, und stellen Sie sicher, dass die App den Forwarding-Headern des Proxys vertraut, damit https-URLs generiert werden und die echte Client-IP erkannt wird.
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
Wenn Sie Ihren eigenen Reverse Proxy betreiben, beschreibt der Nginx-Guide diese Header, die TLS-Terminierung und die Upstream-Konfiguration im Detail. Fehler in diesen Einstellungen führen zu Redirect-Loops und dazu, dass Rate-Limiter jede Anfrage als stammend von der IP des Proxys wahrnehmen.
Observability und Alerting
Man kann nichts betreiben, was man nicht sieht. Drei Signalarten decken die meisten Anforderungen ab.
Logs sind strukturierte Ereignisse. Geben Sie JSON mit einem Level, einer Nachricht und einer Request-ID aus, damit diese gefiltert und korreliert werden können. Schreiben Sie in stdout und lassen Sie die Plattform die Sammlung übernehmen.
console.log(JSON.stringify({ level: "info", msg: "request", id, path, ms }));
Metrics sind Zahlen über einen Zeitraum: Request-Rate, Fehlerrate, Latenz-Perzentile, CPU, Speicher und Queue-Tiefe. Tracken Sie die „Four Golden Signals“ — Latenz, Traffic, Fehler und Sättigung — für jeden Service.
Traces verfolgen einen Request über mehrere Services hinweg und machen einen langsamen Fan-out sichtbar. Sie werden wichtiger, je mehr Services hinzukommen; für eine einzelne API reichen gute Logs und Metrics in der Regel aus.
Richten Sie Alerts auf Symptome ein, die Nutzer spüren, nicht auf Ursachen. Eine steigende Fehlerrate oder eine p95-Latenz über einem Schwellenwert ist es wert, jemanden zu wecken. Eine hohe CPU-Auslastung, die keine Auswirkungen auf die Requests hat, ist eine Notiz im Dashboard, kein Page. Jeder Alert sollte actionable sein, ansonsten gewöhnen sich die Leute daran, Alerts zu ignorieren.
Kostenbewusstsein
Cloud-Rechnungen wachsen oft in den Lücken zwischen einzelnen Entscheidungen. Die üblichen Verdächtigen sind dabei vorhersehbar.
Egress ist oft die größte Überraschung. Daten, die den Provider verlassen, werden berechnet – teilweise massiv –, während eingehende Daten meist kostenlos sind. Servieren Sie große Assets über ein CDN, komprimieren Sie Antworten und halten Sie kommunikationsintensive Dienste in derselben Region.
Idle resources werden leicht vergessen: eine Staging-Datenbank, die weiterläuft, nicht zugeordnete Volumes, alte Snapshots oder überdimensionierte Instanzen, die „nur für den Fall“ behalten werden. Skalieren Sie Nicht-Produktionsumgebungen außerhalb der Arbeitszeiten herunter.
Over-provisioning verschwendet Geld auf die andere Art. Passen Sie die Größe Ihrer Instanzen durch die Messung der tatsächlichen Nutzung an und lassen Sie Autoscaling die Spitzen abfangen, anstatt rund um die Uhr für den Worst Case zu bezahlen.
Serverless Trade-offs. Funktionen, die auf Null skalieren, sind im Leerlauf günstig, können aber bei gleichbleibender Last im Vergleich zu einem kleinen, dauerhaft laufenden Container teuer werden. Modellieren Sie beides, wenn der Traffic vorhersehbar ist.
Richten Sie einen Budget-Alarm für das Konto ein. Eine Rechnung, die sich stillschweigend verdoppelt, ist weitaus schlimmer als eine Benachrichtigung darüber, dass ein Schwellenwert überschritten wurde.
Infrastructure as Code
Sich durch eine Konsole zu klicken ist für das erste Deployment in Ordnung, danach wird es zum Risiko. Infrastructure as Code hält den gewünschten Zustand in Dateien fest, sodass Umgebungen reproduzierbar, überprüfbar und wiederherstellbar sind.
Terraform und OpenTofu beschreiben Ressourcen deklarativ. Pulumi nutzt echte Programmiersprachen. Kubernetes-Manifeste und PaaS-Konfigurationsdateien sind einfachere Formen desselben Konzepts. Das Tool ist weniger wichtig als die Praxis: Die Definition liegt in der Versionsverwaltung und wird über eine Pipeline angewendet.
resource "aws_db_instance" "main" {
engine = "postgres"
instance_class = "db.t4g.small"
allocated_storage = 50
backup_retention_period = 7
}
Zwei Regeln machen IaC sicher. Halten Sie den State remote und gesperrt, damit zwei Personen keine widersprüchlichen Änderungen anwenden können. Überprüfen Sie Plans, bevor Sie diese ausführen, denn ein Plan, der eine Datenbank löschen will, sollte einen Menschen stoppen und nicht stillschweigend fortfahren.
Für einen einzelnen PaaS-Service ist bereits eine fly.toml oder render.yaml, die im Repository eingecheckt ist, Infrastructure as Code. Beginnen Sie dort und führen Sie ein vollständiges Tool ein, wenn die Komplexität wächst.
Rollbacks und Forward Fixes
Jedes Deployment benötigt einen Weg zurück. Da das Artefakt unveränderlich (immutable) ist und per Commit getaggt wird, besteht ein Rollback darin, den vorherigen Digest erneut zu deployen.
fly releases
fly deploy --image ghcr.io/me/app@sha256:previous
Unter Kubernetes kehrt ein rollout undo zur vorherigen Revision zurück. Bei einem PaaS führen die meisten Plattformen eine Liste der Releases und ermöglichen es, eines davon mit einem Klick erneut zu deployen.
Rollbacks sind schnell, aber nicht immer ausreichend. Wenn eine Datenbank-Migration bereits ausgeführt wurde und das Schema geändert hat, muss der alte Code immer noch in der Lage sein, darauf zuzugreifen. Aus diesem Grund sollten Migrationen für mindestens ein Release abwärtskompatibel sein: Fügen Sie Spalten hinzu, bevor Sie diese verwenden; hören Sie auf, Spalten zu verwenden, bevor Sie diese löschen; und kombinieren Sie niemals eine destruktive Änderung mit dem Code, der davon abhängt, im selben Deployment.
Wenn ein Rollback unmöglich ist – etwa bei einer Datenmigration, die nicht rückgängig gemacht werden kann –, ist die Lösung ein Forward Fix: Liefern Sie die Korrektur schnell über dieselbe Pipeline-Disziplin aus, anstatt Änderungen manuell in der Produktionsumgebung vorzunehmen.
Networking, Regionen und Latenz
Wo Ihre Instanzen und Daten liegen, bestimmt, wie schnell sich die Anwendung anfühlt. Eine Anfrage, die einen Ozean überqueren muss, um die Datenbank zu erreichen, verursacht bereits eine Latenz von mindestens einhundert Millisekunden, noch bevor überhaupt Arbeit verrichtet wird – und kein Tuning der Anwendung kann dies beheben.
Halten Sie die Anwendung, die Datenbank und den Cache in der gleichen Region. Dies ist die effektivste Entscheidung zur Latenzreduzierung, und sie ist kostenlos. Platzieren Sie statische Assets hinter einem CDN, damit diese von einem Edge-Standort nahe beim Nutzer ausgeliefert werden, und lassen Sie nur dynamische Anfragen zum Origin reisen.
Exponieren Sie die Datenbank nicht dem öffentlichen Internet. Nutzen Sie das private Netzwerk der Plattform, sodass nur Dienste im selben Netzwerk darauf zugreifen können, und erlauben Sie den Zugriff über Security Groups oder Firewall-Regeln statt über einen offenen Port. Dies eliminiert eine ganze Klasse von Angriffen und verbessert in der Regel gleichzeitig die Latenz.
Multi-Region ist ein Schritt, den die meisten Produkte nicht benötigen. Er vervielfacht die Kosten, verkompliziert die Datenbank-Architektur und führt zu Replication Lag, wodurch ein Nutzer, der in einer Region schreibt und in einer anderen liest, veraltete Daten sieht. Beginnen Sie mit einer Region und einem CDN, messen Sie die Latenz, die echte Nutzer erleben, und expandieren Sie erst, wenn eine spezifische Zielgruppe dies erfordert.
Ein Deployment testen, bevor die Nutzer es sehen
Die Production-Umgebung sollte nicht der erste Ort sein, an dem eine Änderung ausgeführt wird. Einige zusätzliche Testebenen fangen Fehler ab, die Unit-Tests nicht finden können.
Preview-Umgebungen fahren für jeden Pull Request ein vollständiges Deployment hoch, sodass ein Reviewer die tatsächliche Änderung direkt durchklicken kann. Plattformen, die dies unterstützen, verwandeln das Review vom bloßen Lesen eines Diffs in das aktive Ausprobieren des Features.
Staging spiegelt die Konfiguration der Production wider – gleiche Datenbank-Engine, gleiche Struktur der Umgebungsvariablen, gleicher Proxy –, jedoch ohne die Produktionsdaten. Die Aufgabe von Staging ist es, jene Art von Bugs zu finden, die erst auftreten, wenn die realen Komponenten miteinander verknüpft sind.
Smoke-Tests werden unmittelbar nach jedem Deploy ausgeführt und prüfen den kritischen Pfad: den Health-Endpoint, einen Login sowie einen Lese- und einen Schreibvorgang. Sie sind keine vollständige Test-Suite, sondern eine schnelle Bestätigung, dass das Release grundsätzlich funktioniert.
#!/usr/bin/env bash
set -euo pipefail
BASE="${1:-https://app.example.com}"
curl -fsS "$BASE/healthz" >/dev/null
curl -fsS "$BASE/api/version" | grep -q '"sha"'
echo "smoke test passed"
Feature-Flags entkoppeln das Deployment vom Release. Der Code wird „dark“ ausgeliefert, dann aktiviert ein Flag das Feature für einen einzelnen Nutzer, dann für einen bestimmten Prozentsatz und schließlich für alle. Wenn etwas nicht stimmt, kann das Flag innerhalb von Sekunden deaktiviert werden, ohne dass ein erneutes Deploy nötig ist.
Load-Testing vor einem Launch zeigt dir, wo der erste Flaschenhals liegt: bei den Verbindungen, der CPU oder einer langsamen Query. Teste mit realistischer Concurrency und behalte die Datenbank im Auge, nicht nur die API.
Best Practices
- Starten Sie mit Managed Services und wechseln Sie erst zu mehr Kontrolle, wenn tatsächliche Einschränkungen auftreten.
- Erstellen Sie ein kleines Multi-Stage-Image und führen Sie es als Non-Root-User aus.
- Injizieren Sie die gesamte Konfiguration über die Umgebungsvariablen; betten Sie diese niemals direkt in das Image ein.
- Halten Sie jede Instanz zustandslos (stateless); speichern Sie Sessions, Uploads und Caches in Managed Stores.
- Stellen Sie einen ressourcensparenden Readiness- und Liveness-Endpoint bereit.
- Behandeln Sie
SIGTERMund schließen Sie Verbindungen, bevor der Prozess beendet wird. - Führen Sie Migrationen als Release-Schritt aus und halten Sie diese abwärtskompatibel.
- Begrenzen Sie die Datenbankverbindungen pro Instanz und nutzen Sie einen Pool vor Postgres.
- Taggen Sie Releases per Digest und halten Sie die vorherige Version für einen Rollback bereit.
- Schreiben Sie strukturierte Logs nach stdout und richten Sie Alarme für benutzer sichtbare Symptome ein.
- Definieren Sie die Infrastruktur in der Versionsverwaltung und prüfen Sie die Plans, bevor Sie diese anwenden.
- Richten Sie ein Budget-Alert ein und behalten Sie den Egress im Auge.
Häufige Fehler
- Kubernetes für einen einzigen Service einzuführen und die gesamte Roadmap für die Cluster-Verwaltung zu opfern.
- Sessions im Arbeitsspeicher zu speichern und sich dann zu wundern, warum Benutzer bei jedem Deploy ausgeloggt werden.
- Uploads auf die lokale Festplatte zu schreiben und diese zu verlieren, wenn die Instanz ersetzt wird.
- Die Umgebungskonfiguration direkt in das Image einzubauen und für jede Umgebung neu zu bauen.
- Migrationen beim Anwendungsstart auszuführen, sodass ein Rolling Deploy diese mehrfach ausführt.
- Instanzen zu skalieren, ohne das Limit der Datenbankverbindungen anzupassen.
- Proxy-Headern blind zu vertrauen oder sie gar nicht erst weiterzuleiten.
- Die Standardgröße des Datenbank-Connection-Pools bei einer Flotte von Instanzen beizubehalten.
- CPU-Auslastung zu überwachen, anstatt auf Fehler und Latenzen zu achten, die die Benutzer tatsächlich spüren.
- Egress und Idle-Ressourcen zu vergessen, bis die erste überraschende Rechnung kommt.
- Keinen Rollback-Pfad zu haben oder einen, der fehlschlägt, weil eine Migration nicht kompatibel war.
- Die Produktion manuell über eine Konsole zu verwalten, ohne eine Aufzeichnung darüber zu haben, was geändert wurde.
Wie geht es weiter?
Dieser Guide ist das Ziel für das Artefakt, das durch CI/CD gebaut und bereitgestellt wird. Das Image selbst stammt aus dem Docker-Guide, der Multi-Stage-Builds und Layer-Caching ausführlich behandelt. Wenn Sie TLS selbst terminieren oder den Traffic routen, zeigt der Nginx-Guide die Proxy-Konfiguration, und der Linux-Guide erklärt den Host und die Shell, die Sie automatisieren. Zusammen decken sie den gesamten Weg von einem committed Change bis hin zu einem laufenden, beobachtbaren Release ab.