Implementation of a Prototype Kubernetes-Based Cluster for Scalable Web-Based WordPress Deployment using K3s on Raspberry Pis
Tags: devops, kubernetes, k3s
Published: 2024-02-04 09:19:54

Welcome to an exhilarating journey where we unlock the secrets of building a scalable infrastructure using Kubernetes. In this comprehensive guide, we'll navigate the nuances of setting up a robust cluster to host a WordPress site. Buckle up as we explore the implementation process using five Raspberry Pis, a router, and a switch, with the lightweight K3s as our chosen Kubernetes distribution.

![PI Cluster Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5svz6u8f9cjcfgw1uf27.jpeg)



## Setting the Stage: Objectives

Our primary goal is crystal clear – to create a powerhouse infrastructure that effortlessly scales to host a WordPress site. This guide will walk you through every twist and turn, from the initial Raspberry Pi setup to deploying and scaling your WordPress application.


Before diving into the implementation, ensure you have the following:

1. Hardware:
  - Five Raspberry Pis(You can follow along with one Raspberry PI A similar setup can be found in this [youtube video](https://youtu.be/X9fSMGkjtug?si=uO4kdF_pWGnpcUSu))
  - Router and Switch
  - SD Cards for Raspberry Pis
  - Ethernet Cables
  - Power Supply for Raspberry Pis
  - External Drive (for storing disk images)

2. Software:
  - Win32 Disk Imager (for disk imaging)
  - K3s binary for ARM architecture (compatible with Raspberry Pi)
  - Raspberry Pi OS Lite 64-bit

3. Knowledge:
  - Basic understanding of Linux environments
  - Basic understanding of Kubernetes with essential commands
  - Fundamental networking knowledge

## Table of Contents

1. [Setting Up Raspberry Pis](#1-setting-up-raspberry-pis)
   - 1.1 [Hardware Preparation](#11-hardware-preparation)
   - 1.2 [Operating System Installation](#12-operating-system-installation)
   - 1.3 [Configuring Network Settings](#13-configuring-network-settings)
   - 1.4 [Enabling SSH Access](#14-enabling-ssh-access)

2. [Configuring the Master Node](#2-configuring-the-master-node)
   - 2.1 [Installing K3s on the Master Node](#21-installing-k3s-on-the-master-node)
   - 2.2 [Verifying K3s Installation](#22-verifying-k3s-installation)
   - 2.3 [Configuring Master Node Components](#23-configuring-master-node-components)

3. [Setting Up Worker Nodes](#3-setting-up-worker-nodes)
   - 3.1 [Installing K3s on Worker Nodes](#31-installing-k3s-on-worker-nodes)
   - 3.2 [Joining Worker Nodes to the Cluster](#32-joining-worker-nodes-to-the-cluster)
   - 3.3 [Verifying Worker Node Status](#33-verifying-worker-node-status)

4. [Network Configuration](#4-network-configuration)
   - 4.1 [Configuring Raspberry Pi Networking](#41-configuring-raspberry-pi-networking)
   - 4.2 [Ensuring Network Connectivity](#42-ensuring-network-connectivity)
   - 4.3 [Troubleshooting Network Issues](#43-troubleshooting-network-issues)

5. [Persistent Volume Implementation](#5-persistent-volume-implementation)
   - 5.1 [Creating Persistent Volumes](#51-creating-persistent-volumes)
   - 5.2 [Configuring Persistent Volume Claims](#52-configuring-persistent-volume-claims)
   - 5.3 [Testing Persistent Volumes](#53-testing-persistent-volumes)

6. [Deploying WordPress on Kubernetes](#6-deploying-wordpress-on-kubernetes)
   - 6.1 [Creating WordPress Deployment YAML](#61-creating-wordpress-deployment-yaml)
   - 6.2 [Configuring Service for WordPress](#62-configuring-service-for-wordpress)
   - 6.3 [Verifying WordPress Deployment](#63-verifying-wordpress-deployment)

7. [Scaling the WordPress Deployment](#7-scaling-the-wordpress-deployment)
   - 7.1 [Horizontal Pod Autoscaling](#71-horizontal-pod-autoscaling)
   - 7.2 [Testing Autoscaling](#72-testing-autoscaling)
   - 7.3 [Analyzing Scalability](#73-analyzing-scalability)

8. [Monitoring and Observability](#8-monitoring-and-observability)
   - 8.1 [Implementing Prometheus for Monitoring](#81-implementing-prometheus-for-monitoring)
   - 8.2 [Grafana Dashboard Configuration](#82-grafana-dashboard-configuration)
   - 8.3 [Observing Cluster Metrics](#83-observing-cluster-metrics)

## 1. Setting Up Raspberry Pis

### 1.1 Hardware Preparation

Before we dive into the technical details, let's ensure that we have everything ready. Power up your Raspberry Pis and ensure they are properly connected to the network.

![PI Cluster showing the set up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cjudeg9fw793pc74njko.jpg)

### 1.2 Operating System Installation

The first step is to get the Raspberry Pi OS up and running. To do this, follow these steps:

1. Download the [Raspberry Pi Imager](https://www.raspberrypi.org/software/) and install it on your computer.
2. Insert the SD card into your computer using an SD card reader.
3. Open the Raspberry Pi Imager and choose the Raspberry Pi OS Lite 64-bit version.
4. Select the inserted SD card as the storage location.
5. Click on "Write" to start the installation process.

Once the process is complete, eject the SD card safely and insert it into the Raspberry Pi.
![Raspberry Pi Imager software](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmmcncc1wx6uxe3r6uj6.jpeg)

### 1.3 Configuring Raspberry Pi Networking

Now, let's configure the network settings on each Raspberry Pi:

1. **Boot up the Raspberry Pi:**
   - Connect a monitor, keyboard, and mouse to the Raspberry Pi.
   - Power it up and follow the on-screen instructions to set up the Raspberry Pi OS.

2. **Open the Terminal:**
   - Once the Raspberry Pi OS is booted, open the terminal.

3. **Configure Network Settings:**
   - Use the following command to edit the network configuration file:
     ```bash
     sudo nano /etc/network/interfaces
     ```
   - Update the file with your desired network settings. For example:
     ```bash
     auto eth0
     iface eth0 inet static
     address 192.168.1.2
     netmask 255.255.255.0
     gateway 192.168.1.1
     ```

4. **Save and Exit:**
   - Save the changes by pressing `Ctrl + X`, then `Y` to confirm, and finally `Enter` to exit.

5. **Restart the Network:**
   - Restart the network interface to apply the changes:
     ```bash
     sudo systemctl restart networking
     ```

### 1.4 Enabling SSH Access

Enabling SSH on each Raspberry Pi is essential for remote access. Here's how you can do it:

1. **Open the Raspberry Pi Configuration:**
   - Run the following command:
     ```bash
     sudo raspi-config
     ```
   - Navigate to `Interfacing Options` and enable `SSH`.

2. **Restart the Raspberry Pi:**
   - After enabling SSH, restart the Raspberry Pi to apply the changes:
     ```bash
     sudo reboot
     ```

With these steps completed, you've successfully set up the Raspberry Pis for further configuration.

## 2. Configuring the Master Node

### 2.1 Installing K3s on the Master Node

Now, let's move on to configuring the master node. Follow these steps:

1. **Log into the Master Pi:**
   - Open a terminal or use SSH to log into the master Raspberry Pi.

2. **Download and Install K3s:**
   - Use the following command to download and install K3s:
     ```bash
     curl -sfL https://get.k3s.io | sh -
     ```

3. **Verify K3s Installation:**
   - Once the installation is

 complete, verify that K3s is running:
     ```bash
     sudo k3s kubectl get nodes
     ```
During installation, I got this error

![Error Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/baqcmv0hotge4wfqkmdk.jpeg)

Kubernetes requires “cgroup_memory=1 cgroup_enabled=memory” added to the cmdline.txt file of my pi’s for the installation to work. As this is missing the installation initially failed.

After adding this, I was able to install Kubernetes on the master node


￼

### 2.2 Configuring Master Node Components

With K3s installed, configure the master node components:

1. **Export Kubeconfig:**
   - Export the Kubeconfig file to enable kubectl commands:
     ```bash
     export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
     ```

2. **Check Nodes:**
   - Ensure that the master node is ready:
     ```bash
     sudo k3s kubectl get nodes
     ```
![kubectl get nodes screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6rz9owc7an4tom239o1.jpeg)

3. **Install Helm:**
   - Helm is a package manager for Kubernetes. Install Helm using:
     ```bash
     sudo snap install helm --classic
     ```

   - Initialize Helm:
     ```bash
     helm init
     ```

   - Verify the Helm installation:
     ```bash
     helm version
     ```

With the master node configured, you're ready to proceed to the next stage.

## 3. Setting Up Worker Nodes

### 3.1 Installing K3s on Worker Nodes

Expand your cluster by setting up worker nodes. Here's how:

1. **Log into Each Worker Pi:**
   - Use SSH to log into each worker Raspberry Pi.

2. **Download and Install K3s:**
   - Similar to the master node, install K3s on each worker node:
     ```bash
     curl -sfL https://get.k3s.io | K3S_URL=https://<master-node-ip>:6443 K3S_TOKEN=<node-token> sh -
     ```

   - Replace `<master-node-ip>` with the IP of your master node and `<node-token>` with the token obtained from the master node:
     ```bash
     sudo cat /var/lib/rancher/k3s/server/node-token
     ```

### 3.2 Joining Worker Nodes to the Cluster

After installing K3s on each worker node, join them to the cluster:

1. **Obtain Master Node Token:**
   - On the master node, retrieve the token:
     ```bash
     sudo cat /var/lib/rancher/k3s/server/node-token
     ```

2. **Join Worker to Cluster:**
   - On each worker node, join the cluster using the token:
     ```bash
     curl -sfL https://get.k3s.io | K3S_URL=https://<master-node-ip>:6443 K3S_TOKEN=<node-token> sh -
     ```

### 3.3 Verifying Worker Node Status

Check if the worker nodes have successfully joined the cluster:

1. **On the Master Node:**
   - Run the following command to verify the status of worker nodes:
     ```bash
     sudo k3s kubectl get nodes
     ```

   - Ensure all nodes are in the 'Ready' state.

![Node status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7t8wl64ptwqi97zr44qr.jpeg)
With the worker nodes in place, your Kubernetes cluster is shaping up. Now, let's delve into network configuration.

## 4. Network Configuration

### 4.1 Configuring Raspberry Pi Networking

A well-configured network is crucial for the seamless operation of your Kubernetes cluster. Follow these steps to ensure optimal network settings:

1. **Check Network Interfaces:**
   - Verify the available network interfaces on each Raspberry Pi:
     ```bash
     ip a
     ```

   - Identify the primary network interface (e.g., eth0) for configuration.

2. **Edit Network Configuration File:**
   - Open the network configuration file for editing:
     ```bash
     sudo nano /etc/network/interfaces
     ```

3. **Configure Static IP Address:**
   - Add the following lines to set a static IP address (adjust values based on your network):
     ```plaintext
     auto eth0
     iface eth0 inet static
     address 192.168.1.2
     netmask 255.255.255.0
     gateway 192.168.1.1
     ```

   - Save and exit the editor (press `Ctrl + X`, then `Y`, and `Enter`).

4. **Restart Networking:**
   - Apply the changes by restarting the network service:
     ```bash
     sudo systemctl restart networking
     ```
This can also be set up on your router. Irrespective of how you set the Static IPs this step is important as a change of IP while working can prevent the nodes from communicating with each other.

The screenshot below shows a change in the status of my pods with this being the cause.

![Status error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5fd6mev0craqz4m3ewzl.jpeg)

Using this command helped me to see the cause of this issue 
```bash
sudo kubectl describe pod <pod_name>
```

### 4.2 Ensuring Network Connectivity

Verify that each Raspberry Pi can communicate with others in the network:

1. **Ping Test:**
   - From one Raspberry Pi, ping another using their static IP addresses:
     ```bash
     ping 192.168.1.3
     ```

   - Replace the IP address with the actual address of the target Raspberry Pi.

2. **SSH Connectivity:**
   - Ensure SSH connectivity between Raspberry Pis:
     ```bash
     ssh pi@192.168.1.3
     ```

   - Use the IP address of the target Raspberry Pi.

### 4.3 Troubleshooting Network Issues

If you encounter network issues, consider the following troubleshooting steps:

1. **Check Configuration Files:**
   - Review the network configuration files on each Raspberry Pi.

2. **Firewall Settings:**
   - Ensure that firewalls on Raspberry Pis are not blocking necessary ports.

3. **Router Configuration:**
   - Check router settings to ensure it allows communication between devices.

4. **Node Discovery:**
   - Verify that each node can discover others in the cluster.

With a well-configured network, your Kubernetes cluster is ready to conquer the world. In the next section, we'll explore persistent volume implementation.

## 5. Persistent Volume Implementation

To ensure data persistence and availability, we'll set up persistent volumes (PVs) in our Kubernetes cluster.

### 5.1 Creating Persistent Volumes

On the master node, create persistent volumes for data storage:

1. **Create PV YAML File:**
   - Create a YAML file (e.g., pv.yaml) with the PV configuration. Here's an example for a local storage PV:
     ```yaml
     apiVersion: v1
     kind: PersistentVolume
     metadata:
       name: pv-local
     spec:
       capacity:
         storage: 1Gi
       accessModes:
         - ReadWriteOnce
       hostPath:
         path: "/mnt/data"
     ```

2. **Apply the PV Configuration:**
   - Apply the configuration to create the PV:
     ```bash
     sudo k3s kubectl apply -f pv.yaml
     ```

### 5.2 Configuring Persistent Volume Claims

Now, let's configure persistent volume claims (PVCs) for your WordPress deployment:

1. **Create PVC YAML File:**
   - Create a YAML file (e.g., pvc.yaml) with the PVC configuration. Here's an example:
     ```yaml
     apiVersion: v1
     kind: PersistentVolumeClaim
     metadata:
       name: pvc-local
     spec:
       accessModes:
         - ReadWriteOnce
       resources:
         requests:
           storage: 1Gi
     ```

2. **Apply the PVC Configuration:**
   - Apply the configuration to create the PVC:
     ```bash
     sudo k3s kubectl apply -f pvc.yaml
     ```

### 5.3 Testing Persistent Volumes

Verify that persistent volumes are functioning as expected:

1. **Check PV Status:**
   -

 Verify the status of the persistent volume:
     ```bash
     sudo k3s kubectl get pv
     ```

   - Ensure that the PV is in the 'Bound' state.

2. **Check PVC Status:**
   - Verify the status of the persistent volume claim:
     ```bash
     sudo k3s kubectl get pvc
     ```

   - Ensure that the PVC is in the 'Bound' state.

3. **Test Data Persistence:**
   - Deploy a test pod that uses the persistent volume:
     ```yaml
     apiVersion: v1
     kind: Pod
     metadata:
       name: test-pod
     spec:
       containers:
         - name: test-container
           image: busybox
           command: ["/bin/sh", "-c", "echo Hello Kubernetes! > /mnt/data/test-file && sleep 3600"]
           volumeMounts:
             - name: storage
               mountPath: "/mnt/data"
       volumes:
         - name: storage
           persistentVolumeClaim:
             claimName: pvc-local
     ```

   - Check if the test pod is running and verify the contents of the persistent volume.

With persistent volumes in place, your Kubernetes cluster is now equipped with reliable storage capabilities. The next step is to deploy WordPress on Kubernetes.

## 6. Deploying WordPress on Kubernetes

Let's dive into the exciting phase of deploying WordPress on your Kubernetes cluster.

### 6.1 Creating WordPress Deployment YAML

Create a YAML file (e.g., wordpress.yaml) to define the WordPress deployment:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wordpress
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
        - name: wordpress
          image: wordpress:latest
          env:
            - name: WORDPRESS_DB_HOST
              value: mysql-service
            - name: WORDPRESS_DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: password
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress-service
spec:
  selector:
    app: wordpress
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer
```

Apply the configuration to create the WordPress deployment:

```bash
sudo k3s kubectl apply -f wordpress.yaml
```

### 6.2 Configuring Service for WordPress

Expose the WordPress service to the external world:

1. **Check Service Status:**
   - Verify that the WordPress service is running:
     ```bash
     sudo k3s kubectl get services
     ```

   - Note the external IP assigned to the service.

2. **Access WordPress:**
   - Open a web browser and navigate to the external IP of the WordPress service.

   - Complete the WordPress installation steps.

### 6.3 Verifying WordPress Deployment

Confirm the successful deployment of WordPress:

1. **Check Deployment Status:**
   - Verify the status of the WordPress deployment:
     ```bash
     sudo k3s kubectl get deployments
     ```

   - Ensure the desired number of replicas is running.

2. **Verify Pods:**
   - Check the pods associated with the WordPress deployment:
     ```bash
     sudo k3s kubectl get pods
     ```

   - Ensure the pods are in the 'Running' state.

With WordPress up and running, it's time to explore how to scale your deployment for increased traffic.

## 7. Scaling the WordPress Deployment

Kubernetes makes scaling a breeze. Let's explore how to scale your WordPress deployment dynamically.

### 7.1 Horizontal Pod Autoscaling

Enable horizontal pod autoscaling for the WordPress deployment:

1. **Create HPA YAML File:**
   - Create a YAML file (e.g., hpa.yaml) for the horizontal pod autoscaler:
     ```yaml
     apiVersion: autoscaling/v2beta2
     kind: HorizontalPodAutoscaler
     metadata:
       name: wordpress-hpa
     spec:
       scaleTargetRef:
         apiVersion: apps/v1
         kind: Deployment
         name: wordpress
       minReplicas: 1
       maxReplicas: 5
       metrics:
         - type: Resource
           resource:
             name: cpu
             targetAverageUtilization: 80
     ```

2. **Apply the HPA Configuration:**
   - Apply the configuration to create the horizontal pod autoscaler:
     ```bash
     sudo k3s kubectl apply -f hpa.yaml
     ```

### 7.2 Testing Autoscaling

Simulate increased load on your WordPress site to trigger autoscaling:

1. **Generate Load:**
   - Use a tool like Apache Benchmark to simulate increased traffic:
     ```bash
     ab -n 10000 -c 10 http://<wordpress-service-ip>/
     ```

   - Replace `<wordpress-service-ip>` with the IP of your WordPress service.

2. **Monitor Autoscaling:**
   - Check the status of the horizontal pod autoscaler:
     ```bash
     sudo k3s kubectl get hpa
     ```

   - Monitor the number of replicas based on the defined metrics.

### 7.3 Analyzing Scalability

Review metrics and logs to analyze the scalability of your WordPress deployment:

1. **Check Metrics:**
   - Examine the metrics gathered by the horizontal pod autoscaler:
     ```bash
     sudo k3s kubectl describe hpa wordpress-hpa
     ```

2. **Review Logs:**
   - Analyze logs for individual pods to identify any performance issues:
     ```bash
     sudo k3s kubectl logs <pod-name>
     ```

   - Replace `<pod-name>` with the name of a WordPress pod.

With autoscaling in place, your WordPress deployment can dynamically adapt to varying workloads. Let's now focus on monitoring and observability.

## 8. Monitoring and Observability

A robust monitoring setup ensures that you stay informed about the health and performance of your Kubernetes cluster. Let's implement monitoring using Prometheus and visualize data with Grafana.

### 8.1 Implementing Prometheus for Monitoring

Deploy Prometheus to gather and store cluster metrics:

1. **Create Prometheus YAML File:**
   - Create a YAML file (e.g., prometheus.yaml) for Prometheus deployment:
     ```yaml
     apiVersion: v1
     kind: Service
     metadata:
       name: prometheus-service
     spec:
       selector:
         app: prometheus
       ports:
         - protocol: TCP
           port: 9090
           targetPort: 9090
       type: LoadBalancer
     ---
     apiVersion: apps/v1
     kind: Deployment
     metadata:
       name: prometheus
     spec:
       replicas: 1
       selector:
         matchLabels:
           app: prometheus
       template:
         metadata:
           labels:
             app: prometheus
         spec:
           containers:
             - name: prometheus
               image: prom/prometheus
               ports:
                 - containerPort: 9090
               args:
                 - "--config.file=/etc/prometheus/prometheus.yml"
               volumeMounts:
                 - name: prometheus-config
                   mountPath: /etc/prometheus
           volumes:
             - name: prometheus-config
               configMap:
                 name: prometheus-config
     ```

2. **Apply the Prometheus Configuration:**
   - Apply the configuration to create the Prometheus deployment:
     ```bash
     sudo k3s kubectl apply -f prometheus.yaml
     ```

### 8.2 Grafana Dashboard Configuration

Set up Grafana to visualize Prometheus metrics:

1. **Create Grafana YAML File:**
   - Create a YAML file (e.g., grafana.yaml) for Grafana deployment:
     ```yaml
     apiVersion: v1
     kind: Service
     metadata:
       name: grafana-service
     spec:
       selector:
         app: grafana
       ports:
         - protocol: TCP
           port: 3000
           targetPort: 3000
       type: LoadBalancer
     ---
     apiVersion: apps/v1
     kind: Deployment
     metadata:
       name: grafana
     spec:
       replicas: 1
       selector:
         matchLabels:
           app: grafana
       template:
         metadata:
           labels:
             app: grafana
         spec:
           containers:
             - name: grafana
               image: grafana/grafana
               ports:
                 - containerPort: 3000
               env:
                 - name: GF_SECURITY_ADMIN_PASSWORD
                   value: "admin"
                 - name: GF_SECURITY_ADMIN_USER
                   value: "admin"
                 - name: GF_SECURITY_ALLOW_EMBEDDING
                   value: "true"
     ```

2. **Apply the Grafana Configuration:**
   - Apply the configuration to create the Grafana deployment:
     ```bash
     sudo k3s kubectl apply -f grafana.yaml
     ```

3. **Access Grafana:**
   - Access the Grafana dashboard using a web browser and the external IP of the Grafana service.

4. **Configure Prometheus as a Data Source:**
   - Log in to Grafana (default credentials: admin/admin).
   - Add Prometheus as a data source with the URL: `http://prometheus-service:9090`.

5. **Import Kubernetes Dashboard:**
   - Import the official Kubernetes dashboard for Grafana.

With Prometheus and Grafana in place, your Kubernetes cluster is now equipped with powerful monitoring and visualization capabilities.

## Conclusion

Congratulations on completing the implementation of a prototype Kubernetes-based cluster for scalable web-based WordPress deployment. This journey covered everything from setting up Raspberry Pis to deploying, scaling, and monitoring your WordPress application.

As you continue to explore the dynamic world of Kubernetes, remember that this guide serves as a solid foundation. Feel free to adapt and enhance your cluster based on evolving requirements. Embrace the scalability, flexibility, and resilience that Kubernetes brings to your web-based applications.

May your Kubernetes journey be filled with seamless deployments, effortless scalability, and a robust infrastructure that stands the test of time. Happy clustering!


Here are some resources I found helpful:
https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/
https://docs.k3s.io/storage
YouTube playlist - https://youtube.com/playlist?list=PL9ti0-HuCzGbI4MdxgODTbuzEs0RS12c2&si=34d3StsvzwqnHc0j