Schiwrigkeiten
Worauf es bei einem Deployment von Ghost in Kubernetes ankommt
Kubernetes bietet eine robuste Plattform für Anwendungen, aber es erfordert ein Verständnis seiner Kernkonzepte. Für Ghost sind diese besonders wichtig:
- Deployment (
Deployment,Pod,ReplicaSet):- Zweck: Definiert, wie viele Instanzen (Pods) deines Ghost-Anwendung laufen sollen und wie sie aktualisiert werden (z.B. Rolling Updates). Ein Pod ist die kleinste Bereitstellungseinheit und enthält deinen Ghost-Container.
- Für Ghost: Du definierst ein Deployment für den Ghost-Container (z.B.
ghost:5-alpine). Das Deployment sorgt dafür, dass immer die gewünschte Anzahl an Ghost-Pods läuft und ersetzt sie bei Ausfall.
- Service (
Service- ClusterIP, NodePort, LoadBalancer):- Zweck: Stellt eine stabile Netzwerkidentität für eine Gruppe von Pods bereit. Services ermöglichen es anderen Anwendungen (z.B. deiner Datenbank) und externen Clients, auf Ghost zuzugreifen, ohne die dynamischen Pod-IPs kennen zu müssen.
- Für Ghost: Dein
ghost-servicevom TypClusterIPbietet eine interne, stabile IP. Dies ist der Anlaufpunkt für HAProxy oder deinen Ingress Controller.
- Dauerhafter Speicher (
PersistentVolumeClaim - PVC,PersistentVolume - PV):- Zweck: Ghost speichert wichtige Daten (hochgeladene Bilder, Themes, Datenbank-Backups im
/content-Verzeichnis) ab. Pods sind ephemer (kurzlebig); ihre Daten gehen bei Neustart verloren. PVCs/PVs stellen sicher, dass diese Daten dauerhaft gespeichert werden, unabhängig vom Lebenszyklus des Pods. - Für Ghost: Du definierst ein
PersistentVolumeClaim(z.B.ghost-pvc), das dann von deinem Ghost-Deployment im Pfad/var/lib/ghost/contentgemountet wird. Dies ist absolut entscheidend, damit deine Blog-Inhalte nicht verschwinden.
- Zweck: Ghost speichert wichtige Daten (hochgeladene Bilder, Themes, Datenbank-Backups im
- Konfiguration (
ConfigMapund Umgebungsvariablen):ConfigMap: Dient zum Speichern nicht-sensibler Konfigurationsdaten. Du hast versucht, deineconfig.production.jsonals ConfigMap zu mounten. Dies ist gut für generische Einstellungen wieurl,paths,logging.- Umgebungsvariablen (
envin Deployment): Ghost kann viele seiner Einstellungen auch über Umgebungsvariablen lesen. - Für Ghost & Dein E-Mail-Problem: Die große Erkenntnis war hier, dass Ghost bestimmte Einstellungen (insbesondere die Mail-Konfiguration, wie
mail__fromundmail__options__*) zuverlässiger über flache Umgebungsvariablen im Pod erhält, selbst wenn sie in einer gemountetenconfig.production.jsonenthalten sind. Diese ENV-Variablen überschreiben oder werden gegenüber den Datei-Einstellungen priorisiert. Das war der entscheidende Fix für dein E-Mail-Problem.
- Geheimnisse (
Secret):- Zweck: Zum sicheren Speichern sensibler Daten wie Datenbank-Passwörter, API-Keys oder SMTP-Zugangsdaten. Secrets werden verschlüsselt (Base64-kodiert) im Cluster abgelegt.
- Für Ghost: Du verwendest Secrets, um die Datenbankzugangsdaten (
MYSQL_USER,MYSQL_PASSWORD,MYSQL_DATABASE) sicher an den Ghost-Pod zu übergeben (valueFrom: secretKeyRef). Die SMTP-Passwörter könnten auch hier gespeichert und als ENV-Variablen referenziert werden, obwohl du sie im Moment direkt als Werte in den ENV-Variablen hast.
- Eingangs-Controller (
Ingress- optional, aber empfohlen):- Zweck: Verwalten des externen Zugriffs auf Services innerhalb des Clusters, typischerweise HTTP/S-Routing basierend auf Hostname oder Pfad. Benötigt einen Ingress Controller (z.B. Nginx Ingress Controller, Traefik).
- Für Ghost: Wenn du mehrere Domains oder komplexe Routing-Regeln hättest, wäre ein Ingress ideal. In deinem Fall fungiert HAProxy auf dem Host als externer "Ingress", der den Traffic an den Ghost-Service weiterleitet.
- Datenbank:
- Wichtigkeit: Ghost benötigt eine relationale Datenbank (MySQL oder PostgreSQL). In einem Produktions-Setup ist es Best Practice, eine externe, verwaltete Datenbank (z.B. Cloud SQL, AWS RDS) zu verwenden, anstatt die Datenbank als Pod im selben Cluster zu betreiben, da Datenbanken ihre eigenen Komplexitäten bei Hochverfügbarkeit und Backup haben. In Testumgebungen wie k3s ist eine lokale MySQL-Installation als Kubernetes Service aber üblich.
- Gesundheitsprüfungen (
LivenessundReadiness Probes):- Zweck: Kubernetes nutzt diese, um den Gesundheitszustand deiner Pods zu überwachen.
Livenessprüft, ob ein Container noch läuft.Readinessprüft, ob ein Container bereit ist, Traffic zu empfangen. - Für Ghost: Du definierst HTTP-Checks auf den Ghost-Port (standardmäßig 2368), um sicherzustellen, dass der Ghost-Prozess nicht nur läuft, sondern auch Anfragen beantworten kann.
- Zweck: Kubernetes nutzt diese, um den Gesundheitszustand deiner Pods zu überwachen.
- Ressourcen (
LimitsundRequests):- Zweck: Definieren die minimal benötigten (
requests) und maximal erlaubten (limits) CPU- und Speichermengen für einen Container. Wichtig für die Cluster-Stabilität und die Planung der Workloads. - Für Ghost: Eine gute Praxis, um zu verhindern, dass Ghost zu viele Ressourcen verbraucht und andere Pods beeinträchtigt, oder um sicherzustellen, dass es genug Ressourcen für einen stabilen Betrieb hat.
- Zweck: Definieren die minimal benötigten (
II. Ghost Deployment in CasaOS / Docker
Ein CasaOS/Docker-Setup ist in der Regel einfacher und direkter, da es weniger Abstraktionsebenen gibt:
- Container-Laufzeitumgebung:
- Docker: Ghost läuft direkt in einem oder mehreren Docker-Containern.
- CasaOS: Bietet eine grafische Oberfläche (GUI) und einen "App Store", der im Hintergrund Docker-Container und oft Docker Compose nutzt, um Anwendungen bereitzustellen. Es abstrahiert die direkten Docker-Befehle für den Benutzer.
- Container-Definition:
- Docker: Du definierst deine Dienste oft in einer
docker-compose.yml-Datei. Diese Datei listet die Container (Ghost, MySQL), deren Images, Ports, Volumes und Umgebungsvariablen auf. - CasaOS: Nutzt entweder vordefinierte App-Templates oder ermöglicht es dir, deine
docker-compose.yml(oder ähnliche Konfigurationen) über die GUI zu importieren/definieren.
- Docker: Du definierst deine Dienste oft in einer
- Persistenz (
Volumes):- Docker: Du verwendest Docker "named volumes" (z.B.
ghost-content:/var/lib/ghost/content) oder "bind mounts", um Daten persistent zu speichern. Diese sind direkt an den Docker-Host gebunden. - CasaOS: Verwaltet diese Volumes oft im Hintergrund und zeigt sie dir in der GUI an.
- Docker: Du verwendest Docker "named volumes" (z.B.
- Konfiguration (Umgebungsvariablen und Konfigurationsdateien):
- Umgebungsvariablen: Dies ist der entscheidende Punkt! In Docker-Umgebungen (und von vielen Container-Anwendungen wie Ghost erwartet) werden Konfigurationen sehr häufig und effektiv direkt über Umgebungsvariablen übergeben. Das ist die "flache" Struktur, die du in der CasaOS-GUI gesehen hast (z.B.
mail_from,mail_options_host). - Konfigurationsdateien: Du kannst auch Dateien wie
config.production.jsonin den Container mounten, aber wie du gelernt hast, können Umgebungsvariablen diese Einstellungen überschreiben oder prioritär behandeln.
- Umgebungsvariablen: Dies ist der entscheidende Punkt! In Docker-Umgebungen (und von vielen Container-Anwendungen wie Ghost erwartet) werden Konfigurationen sehr häufig und effektiv direkt über Umgebungsvariablen übergeben. Das ist die "flache" Struktur, die du in der CasaOS-GUI gesehen hast (z.B.
- Netzwerk (
Port MappingundBridge Networks):- Docker: Du definierst
port mappings(z.B.-p 80:2368), um Container-Ports an Host-Ports zu binden. Container im selbendocker-composeStack können über ihre Service-Namen kommunizieren (z.B. Ghost verbindet sich mitmysqlals Hostname). - CasaOS: Verwaltet die Port-Weiterleitung für dich, oft über seine GUI.
- Docker: Du definierst
III. Die entscheidende Erkenntnis & der Unterschied
Der Hauptunterschied liegt in der Abstraktion und dem Grad der Orchestrierung.
- Docker/CasaOS ist wie ein einzelner Dirigent für ein kleines Orchester. Du sagst jedem Musiker (Container) direkt, was er spielen soll und wo er sitzen soll. Konfigurationen werden oft direkt an den Musiker gegeben.
- Kubernetes ist wie ein Team von Dirigenten für ein riesiges Symphonieorchester. Du gibst Anweisungen an das Team (Deployment), das dann die Musiker (Pods) organisiert, ihre Stabilität sicherstellt, Netzwerke aufbaut und dafür sorgt, dass sie sich untereinander und mit der Außenwelt verständigen können. Konfigurationen können auf verschiedene Arten übergeben werden (ConfigMaps, Secrets, Umgebungsvariablen), was Flexibilität, aber auch Komplexität mit sich bringt.
Die "from"-Lösung aus CasaOS/Docker war so wertvoll, weil sie eine Ghost-spezifische Eigenheit aufgedeckt hat: Ghost scheint bestimmte Konfigurationen (insbesondere die Mail-Settings) zu bevorzugen oder ausschließlich über flache Umgebungsvariablen zu verarbeiten, selbst wenn das config.production.json vorhanden ist.
- In Docker/CasaOS war es intuitiv, diese flachen ENV-Variablen zu setzen.
- In Kubernetes ist man oft geneigt, alles in eine ConfigMap zu packen und zu mounten. Der Fehler lag nicht in der YAML-Syntax der ConfigMap selbst, sondern darin, wie Ghost die Prioritäten beim Laden seiner Konfiguration setzt.
Indem du die Mail-Konfiguration in Kubernetes auch als Umgebungsvariablen im Deployment hinterlegt hast, hast du das Verhalten von Ghost im Docker-Setup nachgebildet und somit das Problem gelöst.
Das ist ein klassisches Beispiel dafür, wie das Verständnis des Anwendungsverhaltens (hier Ghost) in verschiedenen Umgebungen (Docker vs. Kubernetes) entscheidend sein kann, um Konfigurationsprobleme zu lösen!