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
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
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:
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:
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:
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!
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
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: firstname.lastname@example.org # 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:
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:
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!
Until next time, happy Kubing (literally just made that up this second).