Building this blog on.....Civo K3S

Building this blog on.....Civo K3S

What better way to start learning a new technology than to use it for something productive, i.e. this blog! So i've decided to build this blog, not once, not twice, not even three times! but six times! I'll explain.....

Having waded into the world of Kubernetes, I thought what better way to learn than to try and get a basic ghost application working on each cloud provider, potentially even trying to run them all (OK maybe that's a bit ambitious but let's see!). Focusing on getting the basics right in each platform (web/database/storage) and using modern deployment technologies like Terraform to automate where possible!

So kicking things off in the "Building this blog in..." series is Civo!

I first heard about Civo when it was mentioned by OpenFaas founder Alex Ellis on twitter. He mentioned how Civo was providing "The world's first managed kubernetes service powered by K3S!" Having followed one of Alex's blogs on building a K3S cluster using Raspberry Pi's I decided this would be a pretty interesting to apply my learning in a new environment and also hopefully provide some valuable feedback to Civo in the process.

At the time of writing Civo are only allowing a limited number of people to join it's #kube100beta (https://www.civo.com/kube100). Fortunately I managed to persuade the guys at Civo that if they wanted someone to test the platform to breaking point, I was the man for the job!

Getting up and running in Civo managed kubernetes is outside the scope of this blog post and to be honest is well documented on the official pages:
Getting Started With Kubernetes or have a look at the excellent guide by Alex The Worlds First Managed K3S

Building Ghost

I will run through the setup of Ghost on Civo, step by step. I would recommend using a real domain name throughout, it will make the SSL bit later easier. Otherwise you will probably end up ripping it all down and starting again. Fortunately with Kubernetes that's not as painful as it sounds, i've had to do it quite a few times writing this!

Step 1 - Storage

To ensure my blog did not vanish like a...(sorry), I had to make sure there was persistent storage available for both the database and the ghost configuration/theme files. Civo Kubernetes does not have a native storage provider but I didn't need to look very far to find one:

The Civo marketplace provides the essential tools for Kubernetes and Longhorn by Rancher is one of them. Having not used Longhorn before the installation was as easy as clicking the icon! Give it a few minutes to create the pods, you can check on their progress with:

kubectl get pods -n longhorn-system

Environment Setup:

First let's download config file of the cluster you will be working with. Open your favourite terminal and navigate to a sensible directory.

Once downloaded you can set your session to use this:

export KUBECONFIG="path_to_config_file"

You can verify you are connected to the right cluster by doing a quick check of the nodes:

kubectl get nodes

And checking these against the cluster in the web interface, you wouldn't want to be connected to the wrong cluster!

To speed up the process I've provided some yaml files to create parts of the config. You may want to download and create these manually.

Creating storage for MYSQL

You can use the command below to quickly deploy the PVC (Persistent Volume Claim):

kubectl apply -f https://raw.githubusercontent.com/keithhubner/CIVO-K3S/master/vol-mysql.yml

You should be able to view the PV (Persistent Volume) and PVC

kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
pvc-e14f138c-d070-4552-9cda-c2c37aad7680   5Gi        RWO            Delete           Bound    default/mysql-pv-claim   longhorn                112m
kubectl get pvc

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pv-claim   Bound    pvc-e14f138c-d070-4552-9cda-c2c37aad7680   5Gi        RWO            longhorn       113m

Creating the storage for Ghost

Again I have a yaml file ready to go to create this for you, or feel free to download and create this file yourself.

kubectl apply -f https://raw.githubusercontent.com/keithhubner/CIVO-K3S/master/vol-ghost.yml

Again check the PV and PVC have been setup correctly using the above commands, should look a bit like this:

kubectl get pv

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
pvc-e14f138c-d070-4552-9cda-c2c37aad7680   5Gi        RWO            Delete           Bound    default/mysql-pv-claim   longhorn                116m
pvc-ffbf04ab-295d-420e-85ce-7ba25e5ea7cd   5Gi        RWO            Delete           Bound    default/ghost-pv-claim   longhorn                75m

kubectl get pvc 

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mysql-pv-claim   Bound    pvc-e14f138c-d070-4552-9cda-c2c37aad7680   5Gi        RWO            longhorn       114m
ghost-pv-claim   Bound    pvc-ffbf04ab-295d-420e-85ce-7ba25e5ea7cd   5Gi        RWO            longhorn       72m

Starting up MYSQL

Before deploying mysql, it's a good idea to create a kubernetes secret to store the root password:

First we need to generate the password in base64, change this for your password:

echo -n some_text_to_encode | base64

You will get an output in base64:

c29tZV90ZXh0X3RvX2VuY29kZQ==

Copy the code below to a file called mysql-pass.yml (make sure you change the password for the base64 value given above)

apiVersion: v1
kind: Secret
metadata:
  name: mysql-pass
type: Opaque
data:
  password: c29tZV90ZXh0X3RvX2VuY29kZQ==
kubectl apply -f mysql-pass.yml

You should get a confirmation that the secret was created and you can check the secret has been stored:

secret/mysql-pass created
kubectl get secrets

NAME                  TYPE                                  DATA   AGE
default-token-zcdl6   kubernetes.io/service-account-token   3      98m
mysql-pass            Opaque                                1      53m

We will then reference this secret when both creating the server and also connecting to it from ghost, no passwords in code, hurrah!

Deploying MYSQL

You can use the command below to quickly deploy mysql:

kubectl apply -f https://raw.githubusercontent.com/keithhubner/CIVO-K3S/master/mysql-ghost.yml

After a few minutes you should see that the ghost-mysql pod is now running:

kubectl get pods

NAME                            READY   STATUS    RESTARTS   AGE
ghost-mysql-797694cfb8-zqktc    1/1     Running   0          53m

It's worth checking that mysql is up and running and accepting connections (change the pod name to match your result from above):

kubectl logs ghost-mysql-x

Deploying Ghost

Before we deploy ghost, we need to setup Cert Manager to allow us to use TLS for our blog.

Using cert-manager and deploying it in Civo is pretty straight forward! As with Longhorn, you can deploy it directly from the Civo marketplace:

Check it's started properly:

kubectl get pods -n cert-manager
NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-cainjector-54c4796c5d-zwbnz   1/1     Running   0          104m
cert-manager-55fff7f85f-jzpxn              1/1     Running   0          104m
cert-manager-webhook-77ccf5c8b4-2ggnz      1/1     Running   1          104m

Because Cert Manager uses your domain records to verify ownership, you will need to alter your public DNS to point to the external IP address of one of your nodes (I appreciate a load balancer would be better here! Something i'll be writing about in a future blog!).

The first thing we need to do is create a provider yml file. Copy the file below into a new file called provider.yml making sure you edit the email address to you own.

apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: your@email.co.uk
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: traefik

Next apply this file:

kubectl apply -f provider.yml

You will see:

clusterissuer.cert-manager.io/letsencrypt-prod created

You can use the copy the code below to a file called ghost.yml, replacing yourbloghere domain with your own.

apiVersion: v1
kind: Service
metadata:
  name: ghost-svc
  labels:
    app: ghost
    tier: frontend
spec:
  selector:
    app: ghost
    tier: frontend
  ports:
  - protocol: TCP
    port: 2368
    targetPort: 2368  
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ghost-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: "traefik"
    ingress.kubernetes.io/ssl-redirect: "true"
  labels:
    app: ghost
spec:
  tls:
  - hosts:
    - www.yourbloghere.com
    - yourbloghere.com
    secretName: letsencrypt-prod
  rules:
  - host: www.yourbloghere.com
    http:
      paths:
      - path: /
        backend:
          serviceName: ghost-svc
          servicePort: 2368
  - host: yourbloghere.com
    http:
      paths:
      - path: /
        backend:
          serviceName: ghost-svc
          servicePort: 2368        
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ghost-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ghost
      tier: frontend
  template:
    metadata:
      labels:
        app: ghost
        tier: frontend
    spec:
  #    securityContext:
  #      runAsUser: 1000
  #      runAsGroup: 50
      containers:
      - name: blog
        image: ghost
        imagePullPolicy: Always
        ports:
        - containerPort: 2368
        env:
        - name: url
          value: https://www.yourbloghere.com
        - name: database__client
          value: mysql
        - name: database__connection__host
          value: ghost-mysql
        - name: database__connection__user
          value: root
        - name: database__connection__password
          valueFrom:
            secretKeyRef:
              name: mysql-pass
              key: password
        - name: database__connection__database
          value: ghost
        volumeMounts:
        - mountPath: /var/lib/ghost/content
          name: ghost-vol
      volumes:
        - name: ghost-vol
          persistentVolumeClaim:
            claimName: ghost-pv-claim

kubectl apply -f ghost.yml

You should see the following:

service/ghost-svc created
ingress.networking.k8s.io/ghost-ingress created
deployment.apps/ghost-deploy created

Again check the that it is running OK:

kubectl get pods

NAME                            READY   STATUS    RESTARTS   AGE
ghost-mysql-797694cfb8-zqktc    1/1     Running   0          64m
ghost-deploy-868ff9bfd5-z82t4   1/1     Running   0          44s

You can verify that both mysql and ghost are ready to rock with some simple commands. You can use the commands below, making sure to change the pod names:

kubectl logs ghost-mysql-x
kubectl logs ghost-deploy-x

You will get lots of output, but should show the following:

2019-10-28 21:51:08 1 [Note] Server socket created on IP: '::'.
2019-10-28 21:51:08 1 [Warning] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
2019-10-28 21:51:08 1 [Warning] 'proxies_priv' entry '@ root@ghost-mysql-797694cfb8-zqktc' ignored in --skip-name-resolve mode.
2019-10-28 21:51:08 1 [Note] Event Scheduler: Loaded 0 events
2019-10-28 21:51:08 1 [Note] mysqld: ready for connections.
Version: '5.6.46'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
mysql
[2019-10-28 22:54:10] INFO Ghost is running in production...
[2019-10-28 22:54:10] INFO Your site is now available on https://www.yourbloghere.com/
[2019-10-28 22:54:10] INFO Ctrl+C to shut down
[2019-10-28 22:54:10] INFO Ghost boot 1.959s
ghost

You can also check if the certificate has been issued properly by using:

kubectl describe cert

All being well you should see the cert issued:

Status:
  Conditions:
    Last Transition Time:  2019-10-28T22:53:50Z
    Message:               Certificate is up to date and has not expired
    Reason:                Ready
    Status:                True
    Type:                  Ready
  Not After:               2020-01-26T20:52:57Z
Events:                    <none>

Now we can see if it's running, exciting!

Hopefully your DNS records have updated by now, if not just edit your host file to point to the new domain using an IP address of one of the nodes from the Civo web interface.

All being well you should see your shiny new blog!

Congratulations! Your new blog is ready!

If you have had any issues with this guide or would like any more detail on the process please provide feedback. Thanks for taking the time to read all the way to the end!

Special thanks so Kai, Andy and all the guys at Civo for helping me get this done, also thanks to Alex Ellis for his advice and feedback.

Until next time, happy Kubing (literally just made that up this second).

Show Comments