Kubernetes Easily with Rancher: Ghost CMS (Part 6)

calendar_month 1 Ağustos 2019

Kubernetes easily with Rancher: Ghost CMS is the sixth and thus final part of the “Kubernetes easily with Rancher” series. In this post, I describe the installation and commissioning of Ghost.

In “Kubernetes easily with Rancher: Ghost CMS” we not only learn about commissioning via Helm. I also demonstrate a simple deployment via YAML in this article.

What is Ghost

Ghost is a modern publishing platform. Ghost started with a Kickstarter campaign, positioning itself as a simple open-source blogging platform alternative. By now it’s still far from the complexity of WordPress, but it’s also no longer just a pure blogging platform.

Ghost has developed into an interesting alternative for various purposes. It proudly claims to be #1 in the headless NodeJS CMS market – which I can’t say anything about.

I use Ghost as a pure blogging platform, and not even in a team. So I can’t add my two cents to everything Ghost promises beyond that. As a blogging platform, I’m very satisfied with Ghost and can recommend it without hesitation.

So this article is only about Ghost in passing. The topic is the commissioning within a Rancher Kubernetes cluster.

Rancher, Kubernetes, check! Install Ghost?

In this article, we install Ghost together once via the catalog (Helm chart) and once with a standard deployment.

The Helm variant creates 2 pods – one for Ghost and one for the database (MySQL or MariaDB). The standard deployment variant, on the other hand, only creates one pod.

Requirements

The attentive reader should have read and implemented all 5 preceding parts by now. We need an RKE cluster, Let’s Encrypt Cert Manager, storage, and enthusiasm.

Instead of a storage solution like Gluster, OpenEBS, Longhorn, Ceph etc., managed storage from Azure etc. can of course be used – and should be in my opinion. We’ll need a volume.

Ghost offers the ability to send emails, for example to invite team members. If this functionality is desired, the mail mailbox credentials should be available.

The functionalities… What do I want?

That’s easy to explain. The following points are important to me:

  • Automatic certificate request
  • Outgoing emails
  • Upload size of 50 MB per file
  • Operation with SQLite

With deployment via Helm and the subsequent follow-up work, I was able to solve the first 3 points. Unfortunately, it wasn’t possible for me to do without the SQL database in this variant. The chart has it firmly integrated. Which is why I’ll present a second variant with a standard deployment here.

Ghost in RKE with Helm

Deploying Ghost via Helm was certainly not an easy task. After several attempts I succeeded, only to find out that I still had to make adjustments here and there.

After this experience, I’ve recorded all important settings in a values.yml file.

With this form of deployment, we provision a pod with two containers via the Helm chart: our Ghost software on one side and the database container on the other – in this chart it’s the MariaDB container.

Deployment with Helm

The installation can be done quite easily in Rancher. To do this, the experienced user/admin switches to the intended project and selects “Apps” from the catalog apps and clicks “Launch”.

In this view all charts are displayed; to narrow the display, you can either filter on the top right or use the search box to narrow the results.

We want the Ghost chart, so we type “Ghost” in the search box and click “View Details”, which takes us to the chart form.

In the “Configuration Options” section, you enter the name and the intended namespace. The chart settings can be entered either as key-value in the form or provided via YAML.

In this example we use a YAML file. The attentive user must adjust the values before applying.

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_size>Gi"
persistence:
  storageClass: "<storage_class>"
  size: "<storage_size>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>"

Since I love examples, below is one filling in the placeholders:

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"

By clicking “Read from File”, the content can now be pasted into the console.

By confirming – further down – the deployment is now started.

Follow-up Work… Certificate and Routing

Since Ghost is now installed via Helm, we can get started right away… Nope, we can’t. The load balancer via Helm is, let’s say, not suitable for production use. Routing works, but without SSL encryption – which is mandatory nowadays. The points where we need to make changes are not many and can be done via the UI.

New Ingress – Layer 7

I’ve already documented the procedure for this result in another post: Rancher 2x and Let’s Encrypt. The method here is analogous to the information in the linked post.

To create a new ingress entry, we switch to “Load Balancing” in the UI – please note that we’re working in the same project here. After clicking on “Add Ingress”, the input form awaits us.

The inputs should be self-explanatory. There’s actually only one point to mention here. We delete the pre-filled Target Backend entry by clicking the minus symbol. Then we click “Add Service” and select the Ghost service. That’s all.

After saving this ingress, we now need to set the certificate-relevant entries. To do this, we go back into our ingress – via Edit – and expand SSL/TLS Certificates and Labels & Annotations respectively.

Next we need to edit the YAML of our ingress and add the secretName.

Done! That would complete commissioning via Helm… not really.

The Maximum Upload Size

At the latest when we want to use a new template for our Ghost environment, we’ll run into this error.

This is not a limitation of Ghost, but comes from the ingress itself and that’s where we also need to make changes. To do this, we switch back to our Load Balancing view and edit the ingress.

We influence the upload size through another annotation. At this point, we set 50 megabytes as the maximum upload size:

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

Of course, this input could have been made in the previous chapter – Let’s Encrypt certificate request – but the drama behind it would have been lost. Are we done now? Unfortunately no – the outgoing emails don’t work automatically either – even if these were specified in the values. To ensure this function, we now need to edit our workload. To do this, we switch to the Workloads listing and click on our workload.

Within the workload detail view, we first need to select the three dots and click “Edit”. After the page has reloaded, we can maintain the required information under “Environment Variables”. Then we confirm by clicking “Upgrade” – further down on the page.

Now we’re done! :)

Ghost in RKE with Standard Deployment

In the Helm chart I used, it’s not possible to run Ghost without a database container or external database. For a “one-person blog”, the installation with SQLite is perfectly sufficient. This saves us a container, a volume, and software that needs to be maintained.

The YAML Files

The deployment consists of multiple YAML files: a Service, an Ingress, a PersistentVolumeClaim, and a Deployment. An example can be found below.

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

The Deployment

The deployment is quite simple. In the target project, we navigate to the Workloads listing. At this point, we click on “Import YAML” at the top and paste the contents of our YAML file.