Zum Inhalt springen

Eine Django-SaaS deployen

Hello-World-Deployments lassen alles weg, was Produktion schwierig macht. Dieser Guide deployt UpCheck, eine echte, quelloffene Django-SaaS (ein Uptime-Monitor mit Registrierung, Hintergrund-Checks und öffentlichen Statusseiten), von Anfang bis Ende: Datenbank, Hintergrund-Worker, geplante Tasks, Migrationen, HTTPS. Das Setup lässt sich auf fast jede Django-, Rails- oder Laravel-Anwendung übertragen.

Das ist der Weg von Hand, mit kubectl und helm. Auf einem PaaSbox-Cluster mit dem Add-on PaaSbox Platform (im Labor gebaut, noch nicht veröffentlicht) ersetzt ihn eine einzige SaaSApplication: Sie betreibt die vier Rollen, legt Postgres und einen Redis-kompatiblen Cache (Valkey) an, führt vor jedem Release die Migrationen aus und fordert das Zertifikat an. Schritt 2 des Lernpfads, Deine erste App deployen, deployt UpCheck auf diese Weise.

Ein Container-Image (ghcr.io/mitja/upcheck) übernimmt per Command-Override vier Rollen:

RolleWas sie ausführtKubernetes-Objekte
webgunicorn + WhiteNoiseDeployment + Service
workerCelery-Worker (die eigentlichen Uptime-Checks)Deployment
beatCelery Beat (plant die Checks)Deployment, eine Replica
migratemanage.py migrate + Task-Bootstrapeinmaliger Job, pro Release

Dazu PostgreSQL über den CloudNativePG-Operator und ein Redis mit einer Instanz als Celery-Broker. Die Manifeste liegen im Repo unter deploy/ als kustomize-Base mit Overlays (kind für lokal, paasbox für Produktion).

  • Einen Kubernetes-Cluster; ein einzelner cpx32-Node reicht für UpCheck. Dazu kubectl, helm und eine Domain, die dir gehört.
  • Das Repo: git clone https://github.com/mitja/upcheck && cd upcheck

Ohne das Plattform-Add-on folgst du den Schritten unten mit vier Änderungen. Das Overlay deploy/overlays/paasbox/ des Repos wurde für einen Cluster mit ingress-nginx und Hetzner-Volumes geschrieben:

  • Schritt 1: Installiere nur den Operator CloudNativePG. Lass ingress-nginx weg: Traefik bedient schon die Ports 80 und 443 des Servers, und der Cluster legt keinen Hetzner Load Balancer an. Lass die Helm-Installation von cert-manager und deploy/cluster-issuer.yaml weg: Schalte stattdessen das cert-manager-Add-on an, wie in Eine App mit Ingress und TLS veröffentlichen.
  • Schritt 2: Richte den A-Eintrag auf die öffentliche IPv4-Adresse des Servers, die Adresse, auf die der API-Name des Clusters auflöst.
  • ingress.yaml im Overlay: Setz ingressClassName: traefik und die Annotation cert-manager.io/cluster-issuer: letsencrypt-production.
  • kustomization.yaml im Overlay: Zwei Patches verlangen die Storage-Class hcloud-volumes, einer für Postgres, einer für Redis. Schalte das Add-on Hetzner volumes an (hcloud-csi), oder ändere beide auf local-lvm-thin, die eigene Platte des Servers.

Drei Add-ons, die ohnehin jeder Produktions-Cluster braucht. Der Ingress-und-TLS-Guide erklärt jedes im Detail:

Terminal-Fenster
# CloudNativePG operator
kubectl apply --server-side -f \
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.30/releases/cnpg-1.30.0.yaml
# ingress-nginx — provisions a Hetzner load balancer (on your Hetzner bill)
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.service.annotations."load-balancer\.hetzner\.cloud/location"=nbg1
# cert-manager + a Let's Encrypt issuer (edit the email in the file first)
helm upgrade --install cert-manager cert-manager \
--repo https://charts.jetstack.io \
--namespace cert-manager --create-namespace \
--set crds.enabled=true
kubectl apply -f deploy/cluster-issuer.yaml
Terminal-Fenster
kubectl -n ingress-nginx get svc ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'

Lege einen A-Eintrag für deinen Host an, der auf diese IP zeigt, und ändere den Hostnamen in deploy/overlays/paasbox/ von upcheck.paasbox.com auf deine eigene Domain.

3. Secrets anlegen (außerhalb des Repos, nie committet)

Abschnitt betitelt „3. Secrets anlegen (außerhalb des Repos, nie committet)“
Terminal-Fenster
kubectl create namespace upcheck
kubectl -n upcheck create secret generic upcheck-secrets \
--from-literal=SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')"

Das ist das Minimum. UpCheck wickelt seine Abrechnung über Polar ab, und ohne die optionalen POLAR_*-Keys laufen alle Konten im Free-Plan, was für einen Walkthrough richtig ist. Beachte, was hier nicht steht: kein Datenbank-Passwort. CNPG erzeugt das Secret pg-app selbst, und die App verwendet dessen uri-Key als DATABASE_URL.

4. Deployen, und Migrationen als eigenen Schritt ausführen

Abschnitt betitelt „4. Deployen, und Migrationen als eigenen Schritt ausführen“
Terminal-Fenster
kubectl apply -k deploy/overlays/paasbox
# wait for Postgres, then migrate
kubectl -n upcheck wait --for=condition=Ready cluster/pg --timeout=420s
kubectl -n upcheck delete job upcheck-migrate --ignore-not-found
kubectl apply -f deploy/overlays/paasbox/migrate-job.yaml
kubectl -n upcheck wait --for=condition=complete job/upcheck-migrate --timeout=300s

Der Migrate-Job ist bewusst nicht Teil der Kustomization: Kubernetes-Jobs sind unveränderlich, ein „alles anwenden“ kann sie also nicht erneut ausführen. Löschen-dann-anwenden pro Release ist das zuverlässige Muster. Der Job führt manage.py migrate aus und registriert den Beat-Schedule, und die Deployments für web/worker/beat starten, sobald das Schema existiert.

Terminal-Fenster
kubectl -n upcheck get pods
curl -I https://upcheck.example.com # your domain: valid Let's Encrypt cert + 200

Öffne die Seite, registriere dich, lege einen Monitor für example.com an und sieh zu, wie der Worker ihn nach Zeitplan prüft. Das ist das komplette SaaS: Web-Schicht, asynchrone Worker, Scheduler, Datenbank, TLS.

Die CI veröffentlicht bei jedem Commit ghcr.io/mitja/upcheck:latest und :sha-<commit>. Für reproduzierbare Deploys pinnst du newTag in overlays/paasbox/kustomization.yaml (und das Image in dessen migrate-job.yaml) auf einen sha-Tag, dann:

Terminal-Fenster
kubectl apply -k deploy/overlays/paasbox
kubectl -n upcheck delete job upcheck-migrate --ignore-not-found
kubectl apply -f deploy/overlays/paasbox/migrate-job.yaml
kubectl -n upcheck rollout restart deploy/web deploy/worker deploy/beat

Wie du das aus der CI statt vom Laptop ausführst, zeigt der Guide Aus GitHub Actions deployen: dieselben Befehle mit einer kurzlebigen kubeconfig.

Die UpCheck-Manifeste sind eine Vorlage, die du kopieren kannst: ein Image mit Command-Overrides pro Rolle, ein CNPG-Cluster mit dem generierten Secret pg-app, Secrets außerhalb des Repos angelegt, ein Migrationsschritt als unveränderlicher Job und ein Ingress mit der Annotation cert-manager.io/cluster-issuer. Tausche Image, Hostname und Umgebungsvariablen aus, und derselbe deploy/-Baum betreibt die meisten Web-Anwendungen.

Zwei Fußnoten: Setze Resource-Requests auf jeder Rolle (die Manifeste im Repo tun das; nur so kann der Scheduler mehr Workloads auf einem kleinen Cluster unterbringen), und wenn du mit dem Walkthrough fertig bist, räum auf (unten) oder lösche den Cluster, statt seine Server weiter abrechnen zu lassen.

Terminal-Fenster
kubectl delete namespace upcheck
helm -n cert-manager uninstall cert-manager
helm -n ingress-nginx uninstall ingress-nginx # deletes the Hetzner load balancer

Auf einem PaaSbox-Cluster reicht die erste Zeile. Schalte das cert-manager-Add-on auf dem Reiter Add-ons des Clusters aus, wenn nichts anderes es nutzt.