Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive ยท Kubernetes

On-Prem Platform: Design and Node Setup

Design a cloud-style platform on servers you own: a three-node MicroK8s cluster with load balancing, ingress, storage options, namespaces, quotas and network policies.

4 min read

  • MicroK8s 1.32
  • Ubuntu 24.04 LTS
  • MetalLB
  • Calico
  • Docker Engine 28

Goal: reproduce a cloud-style platform (load balancer, ingress, managed-database feel, monitoring) on servers you own, for an application that cannot or should not run in a public cloud.

Versions in use on this date. MicroK8s 1.32, which carries Kubernetes 1.32, was announced on 16 December 2024 and is the newest stable track on 7 April 2025. The nodes run Ubuntu 24.04 LTS. MicroK8s runs containers with containerd, so Docker Engine 28 (released February 2025) is needed only on the build or CI machine, not on the cluster nodes.

Target architecture

flowchart TB
  U[Users and office network] --> FW[Firewall and internal DNS]
  FW --> VIP[MetalLB virtual IP]
  VIP --> ING[Ingress controller<br/>TLS termination]
  subgraph CL[MicroK8s cluster of three nodes]
    ING --> APP[apps namespace<br/>services and workers]
    APP --> DATA[data namespace<br/>MongoDB MySQL PostgreSQL Redis]
    APP --> CE[cloud-emulation namespace<br/>LocalStack]
    DATA --> ST[Storage layer<br/>hostpath or replicated]
  end
  ZBX[Zabbix server<br/>separate VM] -.->|agent 2 and API checks| CL
  DATA -->|dumps and snapshots| BK[Backup target<br/>NAS or object storage]
Layer Choice Reason
Nodes Three servers, each 8 vCPU, 32 GB RAM, 500 GB NVMe (example sizing) Three voters give a highly available control plane that survives one node failure
Datastore Built-in dqlite, clustered across the three nodes MicroK8s uses dqlite instead of etcd, and forms an HA control plane when three or more nodes join
Networking Calico (the MicroK8s default CNI) Supports network policies
Load balancer MetalLB addon with a reserved address range On-premises clusters have no cloud load balancer, so LoadBalancer services need MetalLB
Ingress MicroK8s ingress addon (NGINX-based) Terminates TLS and routes by host name
Storage hostpath-storage to start; a replicated option for important data Hostpath volumes live on one node and are not highly available

Prepare each node

Static IPs and DNS names first, then, on every Ubuntu 24.04 node:

sudo apt update && sudo apt full-upgrade -y
sudo swapoff -a && sudo sed -i '/ swap / s/^/#/' /etc/fstab
sudo timedatectl set-ntp true
sudo snap install microk8s --classic --channel=1.32/stable
sudo usermod -aG microk8s $USER && newgrp microk8s
microk8s status --wait-ready

Open the cluster ports between nodes only (the MicroK8s documentation lists them: API server 16443, kubelet 10250, cluster agent 25000, dqlite 19001, and others), and publish only 80 and 443 on the ingress addresses.

Form the cluster

On the first node, generate a join token; on each of the others, run the join command it prints:

# node 1
microk8s add-node
# node 2 and node 3 (use a fresh token for each)
microk8s join 10.20.0.11:25000/<token>
# check
microk8s kubectl get nodes
microk8s status    # high-availability: yes once three nodes are in

Then enable the addons:

microk8s enable dns hostpath-storage metrics-server
microk8s enable metallb:10.20.0.200-10.20.0.220
microk8s enable ingress
microk8s enable cert-manager

Storage decision

hostpath-storage is fast and simple, but a pod using it is tied to one node, and a node failure means that data is unavailable until the node returns. For databases that need to survive a node loss, use one of:

Option Use when
Database-level replication (primary plus replicas on different nodes, each on hostpath) The database software handles replication; the simplest dependable option
Longhorn You want replicated block volumes managed by Kubernetes
Rook-Ceph (a MicroK8s addon exists) You have at least three nodes with spare raw disks and operational capacity for Ceph

Write down what is not highly available, so nobody discovers it during an outage.

Namespaces, quotas and network policy

Separate apps, data, cloud-emulation and monitoring, each with limits, and deny traffic by default:

apiVersion: v1
kind: Namespace
metadata:
  name: data
  labels: { pod-security.kubernetes.io/enforce: baseline }
---
apiVersion: v1
kind: ResourceQuota
metadata: { name: data-quota, namespace: data }
spec:
  hard: { requests.cpu: "8", requests.memory: 24Gi, persistentvolumeclaims: "20" }
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny-ingress, namespace: data }
spec:
  podSelector: {}
  policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-from-apps, namespace: data }
spec:
  podSelector: {}
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: apps }

Decisions to write down first

  • IP plan: node addresses, the MetalLB range, DNS names, and pod and service CIDRs that do not clash with the office network.
  • Disk layout: fast SSD for databases, separate volume for logs.
  • Backups: target, schedule, retention and a tested restore. MicroK8s can back up its datastore with microk8s dbctl backup, but application data needs its own dumps.
  • Ownership: who renews certificates, patches nodes (snap refresh channel policy) and answers alerts.
  • Upgrades: rolling, one node at a time, with the cluster checked between nodes.