In this tutorial we will deploy ghost backend with NFS Storage for our SQLite database and using a config map for our config.js
configuration and make use of Letsencrypt certificates for our Traefik Ingress.
First create the directory where we will be saving our sqlite database on our NFS server:
$ mkdir -p /data/kubernetes-volumes/pistack-blog
I will deploy the application using a single deployment.yml
manifest, but let's break it down in segments.
First we define our persistent volume, by supplying the NFS Server, Path and persistent volume claim:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pistack-blog-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path: /pistack-blog
server: 192.168.0.4
persistentVolumeReclaimPolicy: Retain
claimRef:
namespace: default
name: pistack-blog-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pistack-blog-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Next we want to reference our config.js
as a config map:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pistack-blog-config-js-cm
data:
config.js: |
var path = require('path'),
config;
config = {
production: {
url: 'http://blog.pistack.co.za',
mail: {},
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost.db')
},
debug: false
},
server: {
host: '0.0.0.0',
port: '2368'
}
},
};
module.exports = config;
Next our deployment, where we will define the docker image, with container info such as environment, volumes where we will reference our persistent volume and config maps:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pistack-blog
labels:
app: pistack-blog
category: blog
spec:
replicas: 2
selector:
matchLabels:
app: pistack-blog
template:
metadata:
labels:
app: pistack-blog
category: blog
spec:
containers:
- name: ghost
image: alexellis2/ghost-on-docker:armv6
env:
- name: NODE_ENV
value: production
ports:
- containerPort: 2368
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
volumeMounts:
- name: pistack-blog-vol
mountPath: /var/www/ghost/content/data
- name: pistack-blog-config-js-cm-vol
mountPath: /var/www/ghost/config.js
subPath: config.js
readOnly: true
volumes:
- name: pistack-blog-vol
persistentVolumeClaim:
claimName: pistack-blog-pvc
# https://medium.com/swlh/quick-fix-mounting-a-configmap-to-an-existing-volume-in-kubernetes-using-rancher-d01c472a10ad
- configMap:
items:
- key: config.js
path: config.js
name: pistack-blog-config-js-cm
name: pistack-blog-config-js-cm-vol
Next our service, which is our internal service load balancer, where we are defining the service load balancer on port 80
and the target port which will be the container port of 2368
:
---
apiVersion: v1
kind: Service
metadata:
name: pistack-blog
namespace: default
spec:
ports:
- name: http
targetPort: 2368
port: 80
selector:
app: pistack-blog
category: blog
Next our ingress, here we want to reference the letsencrypt cluster issuer resource from our previous post on cert-manager and define the host rule and service port we defined in our service resource:
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: pistack-blog
namespace: default
annotations:
kubernetes.io/ingress.class: traefik
ingress.kubernetes.io/ssl-redirect: "true"
traefik.backend.loadbalancer.stickiness: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- secretName: blog-pistack-co-za-tls
hosts:
- blog.pistack.co.za
rules:
- host: blog.pistack.co.za
http:
paths:
- path: /
backend:
serviceName: pistack-blog
servicePort: http
The full deployment.yml
:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pistack-blog-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
nfs:
path: /pistack-blog
server: 192.168.0.4
persistentVolumeReclaimPolicy: Retain
claimRef:
namespace: default
name: pistack-blog-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pistack-blog-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pistack-blog-config-js-cm
data:
config.js: |
var path = require('path'),
config;
config = {
production: {
url: 'http://blog.pistack.co.za',
mail: {},
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost.db')
},
debug: false
},
server: {
host: '0.0.0.0',
port: '2368'
}
},
};
module.exports = config;
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pistack-blog
labels:
app: pistack-blog
category: blog
spec:
replicas: 2
selector:
matchLabels:
app: pistack-blog
template:
metadata:
labels:
app: pistack-blog
category: blog
spec:
containers:
- name: ghost
image: alexellis2/ghost-on-docker:armv6
env:
- name: NODE_ENV
value: production
ports:
- containerPort: 2368
resources:
requests:
cpu: 100m
memory: 50Mi
limits:
cpu: 100m
memory: 50Mi
volumeMounts:
- name: pistack-blog-vol
mountPath: /var/www/ghost/content/data
- name: pistack-blog-config-js-cm-vol
mountPath: /var/www/ghost/config.js
subPath: config.js
readOnly: true
volumes:
- name: pistack-blog-vol
persistentVolumeClaim:
claimName: pistack-blog-pvc
# https://medium.com/swlh/quick-fix-mounting-a-configmap-to-an-existing-volume-in-kubernetes-using-rancher-d01c472a10ad
- configMap:
items:
- key: config.js
path: config.js
name: pistack-blog-config-js-cm
name: pistack-blog-config-js-cm-vol
---
apiVersion: v1
kind: Service
metadata:
name: pistack-blog
namespace: default
spec:
ports:
- name: http
targetPort: 2368
port: 80
selector:
app: pistack-blog
category: blog
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: pistack-blog
namespace: default
annotations:
kubernetes.io/ingress.class: traefik
ingress.kubernetes.io/ssl-redirect: "true"
traefik.backend.loadbalancer.stickiness: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- secretName: blog-pistack-co-za-tls
hosts:
- blog.pistack.co.za
rules:
- host: blog.pistack.co.za
http:
paths:
- path: /
backend:
serviceName: pistack-blog
servicePort: http
Then deploy the application:
$ kubectl apply -f deployment.yml
Verify that everything has checked in:
$ kubectl get ingress,deployment,pods
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.extensions/pistack-blog <none> blog.pistack.co.za 192.168.0.119 80, 443 6h49m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/pistack-blog 2/2 2 2 6h49m
NAME READY STATUS RESTARTS AGE
pod/pistack-blog-7cddc5b979-vl9vq 1/1 Running 0 6h49m
pod/pistack-blog-7cddc5b979-94f27 1/1 Running 0 6h49m
And test the application:
$ curl -IL blog.pistack.co.za
HTTP/1.1 301 Moved Permanently
Content-Type: text/html; charset=utf-8
Location: https://blog.pistack.co.za/
Vary: Accept-Encoding
Date: Sun, 18 Oct 2020 17:17:09 GMT
HTTP/2 200
cache-control: public, max-age=0
content-type: text/html; charset=utf-8
date: Sun, 18 Oct 2020 17:17:10 GMT
etag: W/"3487-5otB527Y4FGgz4In/rRIfQ"
vary: Accept-Encoding
vary: Accept-Encoding
x-powered-by: Express
content-length: 13447