K3s Cluster in a Weekend – Part 4 – Deploying an App
I decided to deploy a wiki application to the Kubernetes cluster to have a place to store documentation for research, new projects, and existing system architectures. I found BookStatck to be simple to use and provided a nice test app to get running in the K3s cluster.
BookStack will run in two pods each with dedicated storage. A Pod consisted of one or more Containers and is the smallest unit of work in Kubernetes. While use cases do exist for multiple Containers in a Pod, normally, only one is assigned. I will be creating a Pod for BookStacks backend SQL database as well as a Pod for the frontend web application. These Pods will each have dedicated storage, provisioned with Longhorn.
MariaDB will be used for the SQL database. A PersistentVolumeClaim will be used to allocated disk space.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
storage: bookstack-db-storage
name: bookstack-db-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: longhorn
A second PVC will be used for the BookStack's frontend. This storage will be for file uploads and attachments.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
storage: bookstack-storage
name: bookstack-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: longhorn
The configuration accessModes: ReadWriteOnce instructs the cluster that only one worker node is allowed to mount and write to this volume.
Apply the yaml configuration with kubectl.
control-01:~/apps/bookstack$ kubectl apply -f bookstack-db-storage.yaml -f bookstack-storage.yaml
Each Pod will be managed as a Deployment. Deployments allow Pods to be managed to a desired state and allows applications to be scaled up or down.
Managing these Pods in this way will allow a more complete understanding even my use case of BookStack does not require a high-availability setup with scaling options.
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: bookstack
partOf: bookstack
name: bookstack-frontend
namespace: apps
spec:
replicas: 1
selector:
matchLabels:
app: bookstack
template:
metadata:
labels:
app: bookstack
partOf: bookstack
spec:
containers:
- env:
- name: APP_URL
value: https://wiki.internal.hrck.net
- name: DB_DATABASE
value: bookstackapp
- name: DB_HOST
value: bookstack-db-svc
- name: DB_PASS
value: my_bookstack_db_password
- name: DB_USER
value: bookstack
- name: PGID
value: "1000"
- name: PUID
value: "1000"
image: lscr.io/linuxserver/bookstack
name: bookstacks
ports:
- containerPort: 80
volumeMounts:
- name: bs-frontend-storage
mountPath: /config
restartPolicy: Always
volumes:
- name: bs-frontend-storage
persistentVolumeClaim:
claimName: bookstack-storage
Let's break down the Deployment manifest starting with replicas: 1 and continuing with selector: ... . This configuration tells K3s that only one Pod named with a label of app: bookstack. If the value of replicas were set to 3, two more bookstack Pods would be created based on the information in the section template:.
Container applications can be configured with environment variables. Here, I have used env: to define the SQL username and password for BookStack to use, as well as other app specific configurations. An image: lscr.io/linuxserver/mariadb is also configured to inform K3s what container must be fetched.
Skipping over volumeMounts: ... briefly to describe the sections titled volumes: ... which is how storage provisioned with the previous manifests are assigned to this Pod. I configure the persistentVolumeClaim named bookstack-storage to reference the PVC provisioned on Longhorn. The volume can then be mounted through the name it has been give (bs-frontend-storage) in the section for volumeMounts: .... In addition, montPath is set to describe the filesystem path where the volume will be mounted inside the container.
A second deployment for MariaDB can be setup using a manifest like this:
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: mariadb
partOf: bookstack
name: bookstack-db
spec:
replicas: 1
selector:
matchLabels:
app: mariadb
partOf: bookstack
template:
metadata:
labels:
app: mariadb
partOf: bookstack
spec:
containers:
- env:
- name: MYSQL_DATABASE
value: bookstackapp
- name: MYSQL_PASSWORD
value: mysql_password
- name: MYSQL_ROOT_PASSWORD
value: mysql_root_password
- name: MYSQL_USER
value: bookstack
- name: PGID
value: "1000"
- name: PUID
value: "1000"
- name: TZ
value: UTC
image: lscr.io/linuxserver/mariadb
name: bookstacks-db
volumeMounts:
- mountPath: /config
name: bookstacks-db-storage
restartPolicy: Always
volumes:
- name: bookstacks-db-storage
persistentVolumeClaim:
claimName: bookstacks-db-storage
control-01:~/apps/bookstack$ kubectl apply -f bookstack-db-deployment.yaml -f bookstack-deployment.yaml
There should now be two Pod running for BookStack, bookstack-frontend and bookstack-db. We can check with kubectrl get pod -l partOf=bookstack
Both Pods can communicate with each other, but neither is accessible outside the K3s cluster. This is where Kubernetes (K3s in this case) can be though of as a software defined datacenter. Everything is racked, stacked and patched inside the cluster, but traffic is not allowed in or out.
We will explore how to expose services outside the cluster in Part 5.