- Authors

- Name
- Phillip Pham
- @ddppham
Node.js Express-App auf Kubernetes deployen
TL;DR
Node.js auf Kubernetes erfordert ein schlankes Multi-Stage Dockerfile, einen Non-Root-User, korrektes SIGTERM-Handling für Graceful Shutdown und passende Resource Limits. HPA skaliert die Pods automatisch basierend auf CPU-Auslastung, während Liveness- und Readiness-Probes die Verfügbarkeit absichern.
Express ist das meistgenutzte Web-Framework für Node.js. Auf Kubernetes läuft es stabil und skalierbar -- vorausgesetzt, Dockerfile und Manifeste sind richtig konfiguriert. Dieser Guide deckt alle Schritte von der Containerisierung bis zum Autoscaling ab.
Multi-Stage Dockerfile für Node.js
Ein optimiertes Dockerfile nutzt zwei Stages und reduziert die Image-Größe erheblich:
# Build-Stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production=false
COPY . .
RUN yarn build
# Runtime-Stage
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]
Alpine-basierte Images sind rund 5x kleiner als Debian-Varianten. Der Non-Root-User appuser verhindert, dass ein kompromittierter Container Root-Rechte im Cluster erhält. --frozen-lockfile stellt sicher, dass exakt die gleichen Dependency-Versionen wie in der Entwicklung installiert werden.
Graceful Shutdown mit SIGTERM
Kubernetes sendet SIGTERM an den Container vor dem Herunterfahren. Ohne explizites Handling bricht Node.js laufende Requests ab:
// server.js
import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/healthz', (req, res) => res.json({ status: 'ok' }));
app.get('/ready', (req, res) => res.json({ status: 'ready' }));
app.get('/', (req, res) => {
res.json({ message: 'Hello from Kubernetes' });
});
const server = app.listen(PORT, () => {
console.log(`Server läuft auf Port ${PORT}`);
});
// Graceful Shutdown
const shutdown = (signal) => {
console.log(`${signal} empfangen, fahre herunter...`);
server.close(() => {
console.log('Alle Verbindungen geschlossen');
process.exit(0);
});
// Erzwinge Shutdown nach 30 Sekunden
setTimeout(() => {
console.error('Erzwungener Shutdown nach Timeout');
process.exit(1);
}, 30000);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
server.close() beendet keine aktiven Verbindungen sofort, sondern wartet bis laufende Requests abgeschlossen sind. Der Timeout-Fallback verhindert, dass der Container ewig hängt. Setzen Sie terminationGracePeriodSeconds im Pod-Spec auf denselben Wert.
Kubernetes Deployment mit Resource Limits
Node.js ist single-threaded. Ein einzelner Worker nutzt maximal einen CPU-Kern. Die Resource Limits sollten das berücksichtigen:
apiVersion: apps/v1
kind: Deployment
metadata:
name: express-app
spec:
replicas: 3
selector:
matchLabels:
app: express-app
template:
metadata:
labels:
app: express-app
spec:
terminationGracePeriodSeconds: 35
containers:
- name: express
image: registry.example.com/express-app:1.0.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
resources:
requests:
cpu: 200m
memory: 128Mi
limits:
cpu: "1"
memory: 256Mi
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 5
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 3
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: express-service
spec:
selector:
app: express-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
Der terminationGracePeriodSeconds-Wert liegt absichtlich 5 Sekunden über dem Shutdown-Timeout im Code. So hat der Container genug Zeit für den sauberen Shutdown, bevor Kubernetes den Prozess mit SIGKILL beendet.
| Parameter | Empfehlung | Begründung |
|---|---|---|
| CPU Request | 200m | Node.js ist single-threaded, braucht wenig Baseline-CPU |
| CPU Limit | 1000m | Maximal ein Kern pro Pod sinnvoll |
| Memory Request | 128Mi | Express-Apps starten mit ca. 50-80 MB |
| Memory Limit | 256Mi | Puffer für Request-Spitzen und Garbage Collection |
Horizontal Pod Autoscaler (HPA)
Statt manuell Replicas hochzusetzen, skaliert der HPA automatisch basierend auf CPU:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: express-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: express-app
minReplicas: 3
maxReplicas: 15
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Pods
value: 3
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 120
Das behavior-Feld steuert die Skalierungsgeschwindigkeit. Scale-Up ist aggressiv konfiguriert (3 Pods pro Minute), Scale-Down konservativ (1 Pod alle 2 Minuten). Das verhindert Flapping bei schwankender Last.
Voraussetzung ist ein laufender Metrics Server im Cluster. Prüfen Sie das mit:
kubectl top pods -n default
ConfigMap für Anwendungskonfiguration
Umgebungsspezifische Werte gehören in eine ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: express-config
data:
LOG_LEVEL: "info"
CORS_ORIGIN: "https://www.kubernetes-administration.de"
RATE_LIMIT_MAX: "100"
RATE_LIMIT_WINDOW_MS: "60000"
Referenzieren Sie die ConfigMap im Deployment mit envFrom:
envFrom:
- configMapRef:
name: express-config
Für Secrets wie API-Keys oder Datenbank-Passwörter nutzen Sie ein Kubernetes Secret mit stringData statt einer ConfigMap.
Production-Checkliste
Vor dem Go-Live sollten diese Punkte erfüllt sein:
| Punkt | Status-Prüfung |
|---|---|
| Non-Root User im Dockerfile | USER appuser gesetzt |
| Graceful Shutdown | SIGTERM-Handler implementiert |
| Health Checks | Liveness + Readiness Probes aktiv |
| Resource Limits | CPU und Memory definiert |
| HPA konfiguriert | Min/Max Replicas + Metriken |
| NODE_ENV=production | Weniger Logging, kein Stack-Trace |
FAQ
Warum nicht npm start als CMD im Dockerfile?
npm start startet einen zusätzlichen npm-Prozess, der SIGTERM nicht korrekt an Node.js weiterleitet. node dist/server.js startet den Prozess direkt als PID 1 und empfängt Signale korrekt.
Wie viele Replicas brauche ich für eine Express-App?
Starten Sie mit 3 Replicas für Hochverfügbarkeit und nutzen Sie HPA für automatische Skalierung. Drei Pods ermöglichen Rolling Updates ohne Downtime und verteilen sich auf verschiedene Nodes.
Soll ich das Node.js Cluster-Modul auf Kubernetes verwenden?
Nein. Auf Kubernetes skalieren Sie horizontal über mehrere Pods statt vertikal über Worker-Threads in einem Pod. Jeder Pod läuft als einzelner Node.js-Prozess. Das vereinfacht Debugging und Ressourcen-Management.
Wie debugge ich Memory Leaks in Node.js auf Kubernetes?
Setzen Sie das Memory Limit bewusst knapp und überwachen Sie OOMKilled-Events mit kubectl get events. Nutzen Sie --max-old-space-size als Node.js-Flag, um den Heap-Speicher zu begrenzen, bevor Kubernetes den Pod killt.
Warum ist das Scale-Down langsamer als das Scale-Up?
Zu schnelles Herunterskalieren kann bei wiederkehrenden Traffic-Spitzen zu Engpässen führen. Das stabilizationWindowSeconds von 300 Sekunden beim Scale-Down wartet 5 Minuten ab, ob die Last wirklich dauerhaft gesunken ist.
Legacy zu Kubernetes migrieren?
Wir begleiten Ihre Migration von VMs zu Containern – ohne Produktionsausfall. Erfahrung aus 50+ Migrationsprojekten.
Kubernetes-Beratung gesucht?
Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
Java Spring Boot auf Kubernetes containerisieren
Spring Boot Anwendungen für Kubernetes containerisieren: Multi-Stage Dockerfile, JVM-Tuning, Health Checks mit Actuator und fertige Deployment-YAMLs.
.NET-Anwendungen auf Kubernetes containerisieren
.NET- und ASP.NET-Core-Anwendungen für Kubernetes containerisieren: Multi-Stage Dockerfile, Health Checks, Kestrel-Konfiguration und komplette Manifeste.
Image Pull Policies und Private Registry einrichten
Kubernetes Image Pull Policies verstehen und private Registries mit imagePullSecrets konfigurieren – von Always bis Never und Harbor-Integration.
Legacy-Modernisierung: Von VMs zu Kubernetes
Legacy-Anwendungen von VMs zu Kubernetes migrieren: Die 6 Rs der Modernisierung, Containerisierung einer 3-Tier-App und Datenbank-Strategien.
Python Django auf Kubernetes deployen
Django-Anwendungen auf Kubernetes deployen mit Multi-Stage Dockerfile, Gunicorn, Celery-Workern und automatischen Health Checks für Production-Umgebungen.