Veröffentlicht am

Python Django auf Kubernetes deployen

Teilen:
Authors

Python Django auf Kubernetes deployen

TL;DR

Django auf Kubernetes erfordert ein Multi-Stage Dockerfile mit Gunicorn als WSGI-Server, WhiteNoise für statische Dateien und separate Deployments für Celery-Worker. ConfigMaps und Secrets verwalten die Konfiguration, während Liveness- und Readiness-Probes die Verfügbarkeit sicherstellen.


Django ist eines der verbreitetsten Python-Frameworks für Webanwendungen. Der Betrieb auf Kubernetes bringt Skalierbarkeit, Rolling Updates und automatisches Self-Healing. Dieser Guide zeigt den vollständigen Weg vom Dockerfile bis zum laufenden Cluster.

Multi-Stage Dockerfile für Django

Ein optimiertes Dockerfile trennt Build- und Runtime-Phase. So bleibt das finale Image klein und enthält keine Build-Tools.

# Build-Stage
FROM python:3.12-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Runtime-Stage
FROM python:3.12-slim

RUN groupadd -r django && useradd -r -g django django

WORKDIR /app

COPY --from=builder /install /usr/local
COPY . .

RUN python manage.py collectstatic --noinput

USER django

EXPOSE 8000

CMD ["gunicorn", "myproject.wsgi:application", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "3", \
     "--timeout", "120"]

Wichtige Details: Der --no-cache-dir-Flag bei pip spart Speicher im Image. Gunicorn ersetzt den Django-Entwicklungsserver und bietet Multi-Worker-Processing. Die Anzahl der Worker richtet sich nach der Formel (2 × CPU-Kerne) + 1.

Statische Dateien mit WhiteNoise

Django serviert in Production keine statischen Dateien selbst. WhiteNoise löst das ohne separaten Nginx:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    # ... weitere Middleware
]

STATIC_URL = '/static/'
STATIC_ROOT = '/app/staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

WhiteNoise komprimiert Dateien automatisch mit Gzip und Brotli und setzt Cache-Header. Für die meisten Django-Projekte reicht das aus.

Konfiguration mit ConfigMap und Secret

Umgebungsvariablen gehören nicht ins Image. Kubernetes verwaltet sie über ConfigMaps und Secrets:

apiVersion: v1
kind: ConfigMap
metadata:
  name: django-config
data:
  DJANGO_SETTINGS_MODULE: "myproject.settings.production"
  ALLOWED_HOSTS: "*.kubernetes-administration.de"
  DB_HOST: "postgres-service"
  DB_PORT: "5432"
  CELERY_BROKER_URL: "redis://redis-service:6379/0"
---
apiVersion: v1
kind: Secret
metadata:
  name: django-secret
type: Opaque
stringData:
  SECRET_KEY: "dein-geheimer-schluessel-hier"
  DB_PASSWORD: "sicheres-passwort"

In settings.py lesen Sie die Werte per os.environ:

import os

SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': os.environ.get('DB_HOST', 'localhost'),
        'PORT': os.environ.get('DB_PORT', '5432'),
        'PASSWORD': os.environ['DB_PASSWORD'],
    }
}

Django Deployment mit Health Checks

Das Deployment referenziert ConfigMap und Secret und definiert Liveness- und Readiness-Probes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: django-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: django-web
  template:
    metadata:
      labels:
        app: django-web
    spec:
      containers:
        - name: django
          image: registry.example.com/django-app:1.0.0
          ports:
            - containerPort: 8000
          envFrom:
            - configMapRef:
                name: django-config
            - secretRef:
                name: django-secret
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          livenessProbe:
            httpGet:
              path: /healthz/
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /ready/
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: django-service
spec:
  selector:
    app: django-web
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP

Für die Health-Check-Endpoints genügt eine einfache Django-View:

# health/views.py
from django.http import JsonResponse
from django.db import connection

def healthz(request):
    return JsonResponse({"status": "ok"})

def ready(request):
    try:
        connection.ensure_connection()
        return JsonResponse({"status": "ready"})
    except Exception:
        return JsonResponse({"status": "not ready"}, status=503)

Die Readiness-Probe prüft die Datenbankverbindung. Solange die DB nicht erreichbar ist, leitet der Service keinen Traffic an den Pod.

Celery-Worker als separates Deployment

Asynchrone Tasks gehören in eigene Pods. Celery-Worker teilen sich das gleiche Image, starten aber mit einem anderen Befehl:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: celery-worker
spec:
  replicas: 2
  selector:
    matchLabels:
      app: celery-worker
  template:
    metadata:
      labels:
        app: celery-worker
    spec:
      containers:
        - name: celery
          image: registry.example.com/django-app:1.0.0
          command: ["celery", "-A", "myproject", "worker",
                    "--loglevel=info", "--concurrency=4"]
          envFrom:
            - configMapRef:
                name: django-config
            - secretRef:
                name: django-secret
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          livenessProbe:
            exec:
              command:
                - celery
                - -A
                - myproject
                - inspect
                - ping
            initialDelaySeconds: 30
            periodSeconds: 60

Celery-Worker brauchen typischerweise mehr Ressourcen als Web-Pods. Die exec-Probe prüft, ob der Worker noch auf Celery-Kommandos reagiert. Skalieren Sie Worker unabhängig von den Web-Pods je nach Queue-Länge.

KomponenteCPU RequestMemory RequestReplicas
Django Web250m256Mi3
Celery Worker500m512Mi2
Celery Beat100m128Mi1

Datenbank-Migrationen ausführen

Migrationen laufen als Kubernetes Job vor dem Deployment:

apiVersion: batch/v1
kind: Job
metadata:
  name: django-migrate
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: registry.example.com/django-app:1.0.0
          command: ["python", "manage.py", "migrate", "--noinput"]
          envFrom:
            - configMapRef:
                name: django-config
            - secretRef:
                name: django-secret
      restartPolicy: Never
  backoffLimit: 3

Integrieren Sie den Job in Ihre CI/CD-Pipeline, sodass Migrationen automatisch vor jedem Deployment laufen.

FAQ

Warum Gunicorn statt dem Django-Entwicklungsserver?

Der Django-Entwicklungsserver ist single-threaded und nicht für Production gedacht. Gunicorn startet mehrere Worker-Prozesse, verarbeitet Requests parallel und kann bei einem Crash einzelne Worker automatisch neu starten.

Wie viele Gunicorn-Worker sollte ich konfigurieren?

Die Faustregel lautet (2 × CPU-Kerne) + 1. Bei einem Pod mit 500m CPU-Limit sind 2-3 Worker sinnvoll. Zu viele Worker verbrauchen unnötig RAM und führen zu CPU-Throttling.

Brauche ich einen separaten Nginx vor Gunicorn?

Mit WhiteNoise für statische Dateien und einem Kubernetes Ingress Controller ist ein separater Nginx in den meisten Fällen nicht notwendig. Der Ingress übernimmt TLS-Terminierung und Routing.

Wie deploye ich Django-Migrationen ohne Downtime?

Führen Sie Migrationen als Kubernetes Job vor dem Deployment aus. Verwenden Sie nur additive Migrationen (neue Spalten mit Default-Werten), damit alte und neue Code-Versionen gleichzeitig funktionieren.

Wie skaliere ich Celery-Worker automatisch?

Nutzen Sie KEDA (Kubernetes Event-Driven Autoscaler) mit einem Redis- oder RabbitMQ-Trigger. KEDA skaliert Worker basierend auf der Queue-Länge, nicht auf CPU-Auslastung.

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