Kubernetes einfach mit Rancher: Ghost CMS (Part 6)

calendar_month 1. August 2019 27 Wörter

Kubernetes einfach mit Rancher: Ghost CMS ist der sechste und somit der letzte Part der Kubernetes einfach mit Rancher Serie. In diesem Beitrag beschreibe ich die Installation und die Inbetriebnahme von Ghost.

In Kubernetes einfach mit Rancher: Ghost CMS lernen wir nicht nur die Inbetriebnahme per Helm. Ich demonstriere in diesem Artikel auch ein einfaches Deployment per YAML.

Was ist Ghost

Ghost ist eine moderne Publikationsplattform. Den Anfang machte Ghost mit einer Kickstarter-Kampagne und bewirbte sich als einfache Open-Source Blogging-Plattform-Alternative. Mittlerweile ist man zwar immer noch weit von der Komplexität von WordPress entfernt, aber man ist auch nicht mehr die reine Blogging-Plattform.

Ghost hat sich zu einer interessanten Alternative für verschiedene Zwecke entwickelt. Selbst schmückt man sich mit der Aussage, dass man die Nummer 1 im Headless-NodeJS-CMS-Markt ist – wozu ich überhaupt nichts sagen kann.

Ich verwende Ghost als reine Blogging-Plattform, und das nicht einmal im Team. Daher kann ich zu allem, was Ghost darüber hinaus verspricht, kein bisschen meinen Senf abgeben. Als Blogging-Plattform bin ich mit Ghost sehr zufrieden und kann es ohne Bedenken weiterempfehlen.

So, dieser Artikel soll nur am Rande Ghost behandeln. Das Thema ist die Inbetriebnahme innerhalb von einem Rancher Kubernetes Cluster.

Rancher, Kubernetes, check! Ghost installieren?

In diesem Artikel – Kubernetes einfach mit Rancher: Ghost CMS – installieren wir gemeinsam Ghost einmal über den Katalog (Helm Chart) und zum anderen mit einem Deployment.

Die Variante mit Helm erstellt 2 Pods, einmal für Ghost und zum anderen für die Datenbank (MySQL oder MariaDB). Die Standard-Deployment-Variante dahingegen nur einen Pod.

Requirements

Der aufmerksame Leser sollte bis hier alle vorhergehenden 5 Teile gelesen und umgesetzt haben. Wir brauchen einen RKE Cluster, Let’s Encrypt Cert Manager, Storage und Lust.

Anstatt einer Storage-Lösung wie Gluster, OpenEBS, Longhorn, Ceph etc. kann natürlich – und sollte meiner Meinung nach auch – Managed Storage von Azure etc. verwendet werden. Wir werden ein Volume brauchen.

Ghost bietet die Möglichkeit, E-Mails zu verschicken, um z.B. Team-Mitglieder einzuladen. Sofern diese Funktionalität gewünscht ist, sollten die Mail-Postfach-Anmeldeinformationen vorliegen.

Die Funktionalitäten… Was möchte ich?

Das ist ganz einfach erklärt. Folgende Punkte sind für mich wichtig:

  • Automatische Zertifikats-Anforderung
  • Ausgehende E-Mails
  • Upload-Größe von 50 MB pro Datei
  • Betrieb mit SQLite

Mit der Bereitstellung über Helm und den darauf folgenden Nacharbeiten konnte ich die ersten 3 Punkte lösen. Leider war es mir nicht möglich, in dieser Variante auf die SQL-Datenbank zu verzichten. Der Chart hat diese fest eingebunden. Weshalb ich hier eine zweite Variante mit einem Standard-Deployment vorstellen werde.

Ghost in RKE mit Helm

Ghost über Helm bereitzustellen war jedenfalls keine einfache Aufgabe. Nach mehreren Anläufen gelang es mir doch, nur um dann festzustellen, dass ich noch hier und da Anpassungen vornehmen muss.

Nach dieser Erfahrung habe ich alle wichtigen Einstellungen in einer values.yml-Datei festgehalten.

Mit dieser Form der Bereitstellung stellen wir per Helm-Chart einen Pod mit zwei Containern bereit: zum einen unsere Ghost-Software und zum anderen der Datenbank-Container – bei diesem Chart ist es der MariaDB-Container.

Bereitstellung mit Helm

Die Installation kann ganz einfach in Rancher ausgeführt werden. Hierfür reicht es aus, dass der gewiefte Benutzer/Admin in das vorgesehene Projekt wechselt und durch Auswahl von „Apps” zu den Katalog-Apps wechselt und auf „Launch” klickt.

In dieser Ansicht werden alle Charts angezeigt, um die Anzeige einzuschränken kann rechts oben entweder gefiltert oder über die Searchbox die Ergebnismenge eingeschränkt werden.

Wir möchten in die Ghost-Chart, also tippen wir „Ghost” in die Searchbox und klicken auf „View Details”, womit wir in die Chart-Maske wechseln.

In dem „Configuration Options”-Abschnitt gibt man den Namen sowie den vorgesehenen Namespace an. Die Chart-Einstellungen können entweder als Key-Value in der Maske eingegeben oder per YAML bereitgestellt werden.

In diesem Beispiel verwenden wir eine YAML-Datei. Der aufmerksame Benutzer muss die Werte vor dem Anwenden anpassen.

ghostHost: "<blog_url>"
ghostUsername: "<user>"
ghostPassword: "<pass>"
ghostEmail: "<user_mail>"
ghostBlogTitle: "<blog title>"
allowEmptyPassword: "no"
mariadb:
  rootUser:
    password: "<mariadb_pass>"
  master:
    persistence:
      enabled: "true"
      storageClass: "<storage_class>"
      accessMode: "ReadWriteOnce"
      size: "<mariadb_größe>Gi"
persistence:
  storageClass: "<storage_class>"
  size: "<storage_größe>Gi"
resources:
  requests:
    memory: "512Mi"
    cpu: "300m"
ingress:
  enabled: "true"
  certManager: "true"
  annotations: [{kubernetes.io/ingress.class: "nginx"}, {certmanager.k8s.io/cluster-issuer: "letsencrypt-prod"}]
  hosts:
  -
    name: "<blog_url>"
    path: "/"
  tls: "true"
  tlsSecret: "<lets_encrypt_cert_secret_name>"

Da ich Beispiele liebe, findet sich unten ein Beispiel, um die Placeholder beispielhaft zu befüllen:

ghostHost: "ghost.ak8s.de"
ghostUsername: "admin"
ghostPassword: "eins2Drei4@Ausrufezeichen"
ghostEmail: "aytac@kirmizi.online"
ghostBlogTitle: "smart blog"
allowEmptyPassword: "no"
mariadb:
  rootUser:
    password: "40302010"
  master:
    persistence:
      enabled: "true"
      storageClass: "longhorn"
      accessMode: "ReadWriteOnce"
      size: "2Gi"
persistence:
  storageClass: "longhorn"
  size: "2Gi"
resources:
  requests:
    memory: "512Mi"
    cpu: "300m"
ingress:
  enabled: "true"
  certManager: "true"
  annotations: [{kubernetes.io/ingress.class: "nginx"}, {certmanager.k8s.io/cluster-issuer: "letsencrypt-prod"}]
  hosts:
  -
    name: "ghost.ak8s.de"
    path: "/"
  tls: "true"
  tlsSecret: "ghost-ak8s-de-crt"

Durch Klicken auf „Read from File” kann nun der Inhalt in die Konsole kopiert werden.

Durch Bestätigen – weiter unten – wird nun die Bereitstellung gestartet.

Nacharbeiten… Zertifikat und Routing

Da nun Ghost per Helm installiert ist, können wir gleich loslegen… Nope, können wir nicht. Der Loadbalancer über Helm ist, sagen wir mal, nicht für einen produktiven Einsatz geeignet. Routing funktioniert zwar, aber ohne SSL-Verschlüsselung – was heutzutage Pflicht ist. Die Stellen, an denen wir Hand anlegen müssen, sind nicht viele und können über die Oberfläche getätigt werden.

Neuer Ingress – Layer 7

Die Vorgehensweise für dieses Ergebnis habe ich bereits in einem anderen Beitrag festgehalten: Rancher 2x und Let’s Encrypt. Das Verfahren hier ist analog zu den Informationen in dem verlinkten Beitrag.

Für die Erstellung eines neuen Ingress-Eintrags wechseln wir in der Oberfläche auf „Load Balancing” – achtet bitte darauf, dass hier im selben Projekt gearbeitet wird. Nachdem wir auf „Add Ingress” geklickt haben, erwartet uns die Eingabeoberfläche.

Die Eingaben sollten selbsterklärend sein. Tatsächlich gibt es hier nur einen Punkt zu erwähnen. Das vorhandene Target-Backend löschen wir durch das Klicken auf das Minus-Symbol. Danach klicken wir auf „Add Service” und wählen den Ghost-Service aus. Das war es auch schon.

Nachdem wir diesen Ingress abgespeichert haben, müssen wir nun die zertifikat-relevanten Einträge setzen. Dafür wechseln wir wieder in unseren Ingress – über Edit – und erweitern jeweils SSL/TLS Certificates und Labels & Annotations.

Als nächstes müssen wir das YAML von unserem Ingress bearbeiten und den secretName hinzufügen.

Fertig! Somit wären wir mit der Inbetriebnahme über Helm fertig… nicht wirklich.

Die maximale Upload-Größe

Spätestens wenn wir ein neues Template für unsere Ghost-Umgebung verwenden möchten, laufen wir in diesen Fehler.

Das ist keine Einschränkung von Ghost, sondern kommt von Ingress selber und genau dort müssen wir auch Hand anlegen. Dafür wechseln wir wieder in unsere Load-Balancing-Ansicht und editieren den Ingress.

Die Upload-Größe beeinflussen wir durch eine weitere Annotation. An dieser Stelle legen wir 50 Megabyte als maximale Upload-Größe fest:

nginx.ingress.kubernetes.io/proxy-body-size: 50m

Natürlich hätte man diese Eingabe auch im Kapitel davor – Let’s Encrypt Zertifikats-Anforderung – tätigen können, wobei die Dramatik dahinter untergegangen wäre. Sind wir nun fertig? Leider nein, die ausgehenden E-Mails funktionieren leider nicht automatisch – auch wenn diese in den Values angegeben wurden. Um diese Funktion zu gewährleisten, müssen wir nun unseren Workload editieren. Hierfür wechseln wir in die Workloads-Auflistung und klicken unseren Workload an.

Innerhalb der Workload-Detail-Ansicht müssen wir zuerst die drei Böbberl anwählen und „Edit” klicken. Nachdem die Seite neu geladen hat, können wir unterhalb von „Environment Variables” die benötigten Informationen pflegen. Anschließend bestätigen wir durch das Klicken auf „Upgrade” – weiter unten auf der Seite.

Jetzt sind wir fertig! :).

Ghost in RKE mit Standard-Deployment-Verfahren

In der von mir verwendeten Helm-Chart ist es nicht möglich, Ghost ohne einen Datenbank-Container oder externe Datenbank zu betreiben. Für einen „Ein-Mann-Blog” reicht die Installation mit SQLite vollkommen aus. Wir sparen uns damit einen Container, ein Volume und eine Software, die gewartet werden muss.

Die YAML-Dateien

Die Bereitstellung besteht aus mehreren YAML-Dateien: einem Service, einem Ingress, einem PersistentVolumeClaim und einem Deployment. Unten findet sich ein Beispiel.

apiVersion: v1
kind: Service
metadata:
  name: test-ko-ghost
  labels:
    app: ghost
spec:
  ports:
  - name: ghost
    port: 2368
    protocol: TCP
    targetPort: 2368
  selector:
    app: ghost
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    certmanager.k8s.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: 50m
  generation: 4
  name: test-ko-ghost
spec:
  rules:
  - host: ghost.ak8s.de
    http:
      paths:
      - backend:
          serviceName: test-ko-ghost
          servicePort: 2368
        path: /
  tls:
  - hosts:
    - ghost.ak8s.de
    secretName: ghost-ak8s-de-crt
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-ko-ghost-volume
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
  storageClassName: longhorn
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: test-ko-ghost
  labels:
    app: ghost
spec:
  selector:
    matchLabels:
      app: ghost
  strategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: ghost
    spec:
      containers:
      - env:
        - name: ALLOW_EMPTY_PASSWORD
          value: "no"
        - name: GHOST_EMAIL
          value: aytac@kirmizi.online
        - name: GHOST_HOST
          value: ghost.ak8s.de
        - name: GHOST_PORT_NUMBER
          value: "80"
        - name: GHOST_PROTOCOL
          value: http
        - name: GHOST_USERNAME
          value: EinUser
        - name: mail__from
          value: aytac_blog@kirmizi.online
        - name: mail__options__auth__pass
          value: "40302010"
        - name: mail__options__auth__user
          value: aytac_blog@kirmizi.online
        - name: mail__options__host
          value: mail.kirmizi.online
        - name: mail__options__port
          value: "587"
        - name: mail__transport
          value: SMTP
        - name: url
          value: https://ghost.ak8s.de
        image: ghost:latest
        imagePullPolicy: Always
        name: test-ko-ghost
        ports:
        - containerPort: 2368
          name: ghost
          protocol: TCP
        volumeMounts:
        - mountPath: /var/lib/ghost/content
          name: test-ko-ghost-volume
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      terminationGracePeriodSeconds: 30
      volumes:
      - name: test-ko-ghost-volume
        persistentVolumeClaim:
          claimName: test-ko-ghost-volume

Die Bereitstellung

Die Bereitstellung ist ganz einfach. In dem Ziel-Projekt navigieren wir in die Workloads-Auflistung. An dieser Stelle klicken wir oben auf „Import YAML” und kopieren den Inhalt unserer YAML-Datei hinein.