Building a Blog on Civo K3s
Introduction
What better way to start learning a new technology than to use it for something productive? This guide will get you up and running with a Ghost
blog on Civo's managed Kubernetes platform. It will quickly go over Civo Kubernetes and getting started, followed by a step-by-step guide for configuring your blog.
Getting Up and Running With Kubernetes
At the time of writing Civo are only allowing a limited number of people to join the #KUBE100 Beta. There are still spaces on the beta, so it's definitely worth registering your interest.
To keep this guide focused to deploying Ghost, it will not cover setting up Kubernetes. To get up and running in Civo managed Kubernetes, take a look at some of the guides here, or Alex Ellis's post on his blog detailing his experience getting started. Once you have a cluster running, you can get started with deploying Ghost with the section below.
Make sure you also have kubectl
installed, as we will be managing the cluster using it.
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 seems!
Environment Setup:
First, let's download the configuration file of the cluster you will be working with. You will find this on the cluster information page on the Civo dashboard, like in the image below:
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!
Step 1 - Storage
To ensure the blog does 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 provides stateful storage through an application:
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, assuming you have kubectl
set up and pointing at your cluster as per the set-up instructions:
kubectl get pods -n longhorn-system
NAME READY STATUS RESTARTS AGE
svclb-longhorn-frontend-4z4zp 0/1 Pending 0 93s
longhorn-manager-mgwrt 1/1 Running 0 94s
longhorn-ui-7bd887cd87-qkng2 1/1 Running 0 93s
engine-image-ei-3827e67c-sthtc 1/1 Running 0 57s
instance-manager-e-d25cac90 1/1 Running 0 8s
instance-manager-r-225aa2b5 1/1 Running 0 8s
longhorn-driver-deployer-576bc5f794-ssfb9 0/1 PodInitializing 0 93s
To speed up the process I've provided links below to some yaml files to create parts of the config. You may want to download and create these manually. They are provided for reference only.
Step 2 - 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 (Persistent Volume Claim):
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
Step 3 - Creating the storage for Ghost
Again I have a yaml file ready to go to create this for you. If you want to, feel free to download and edit 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. They 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
Step 4 - 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!
Step 5 - 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
Step 6 - 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:
Alternatively, if you are using the Civo CLI you just need to run:
$ civo applications add cert-manager
Added cert-manager v0.11.0 to Kubernetes cluster
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 your 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
Then, apply this file to your cluster:
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 to the ones returned by kubectl:
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)
[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
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. You can reach us on the Civo Community Slack or using the intercom chat on any civo.com page. Thanks!
A version of this post was originally posted on Keith's own blog, Technically Interesting.