Advanced Search
Search Results
190 total results found
How a request reaches an app
Say you open https://app.example.com in a browser. Here's the whole journey: browser | https://app.example.com v DNS -> 203.0.113.10 (public IP on the gateway) | v pfSense / HAProxy terminates TLS (wildcard *.example.com), | ...
The address plan and naming
A small, boring, consistent address plan saves you a surprising amount of grief. Here's the whole thing. Network: 10.100.100.0/24, private, no internet-facing addresses. Address Role 10.100.100.1 pfSense — the gateway/router for the subnet 10.100.100....
Service map
Every box and what it does, with a pointer to the book that covers it in depth. VM Address What runs there Covered in pfSense .1 NAT, HAProxy, edge TLS Edge Networking with pfSense Jump host .254 SSH bastion The SSH Bastion GIT-Server .2 Gitea Self-...
Why kubeadm (and not k3s or a managed service)
There are easier ways to get a Kubernetes cluster. k3s is a single binary. A cloud provider will hand you one in ten minutes. I deliberately used kubeadm anyway. The reason is the goal. This lab exists to understand the platform, and to show that understanding...
Preparing the nodes
Before kubeadm will touch a machine, the machine has to be ready. The same prep runs on all four nodes: each node |- swap OFF (kubelet refuses to run with swap by default) |- kernel modules: overlay, br_netfilter |- sysctl: net...
Bootstrapping the control plane
With the master node prepped, one command brings the control plane to life: kubeadm init \ --apiserver-advertise-address=10.100.100.7 \ --pod-network-cidr=192.168.0.0/16 Two flags matter: --apiserver-advertise-address pins the API server to the node's pr...
Joining the workers (and the firewall that blocked them)
Each worker joins with the command kubeadm init printed: kubeadm join 10.100.100.7:6443 --token <REDACTED> \ --discovery-token-ca-cert-hash sha256:<REDACTED> The first time I ran this across the three workers, all three failed with: couldn't validate the id...
The network layer: Calico, installed from a manifest
Kubernetes deliberately ships without a network. You choose a CNI plugin and install it. I used Calico, applied from its plain manifest: kubectl apply -f https://.../calico/<version>/manifests/calico.yaml A couple of deliberate choices: Manifest, not the ope...
Keeping workloads off the control plane
By default kubeadm puts a taint on the control-plane node: node-role.kubernetes.io/control-plane:NoSchedule A taint is a "keep out" sign. NoSchedule means: don't place a pod here unless that pod explicitly tolerates this taint. The control plane's own compone...
Why size things at all?
It's a home lab. Why not just give every VM "plenty" of everything and move on? Because the constraint is real, and pretending it isn't is how you wake up to an out-of-memory kill at 3am. One host has a fixed amount of RAM, CPU, and disk. Every VM you start wr...
The host, and the numbers
The whole lab runs on a single Proxmox host with, roughly: 64 CPU threads ~125 GiB usable RAM A few hundred GB of fast local disk for the OS, plus a multi-TB ZFS pool for VM disks CPU stays comfortable — 64 threads is far more than the VMs will ever simultan...
The thing that quietly eats your RAM: ZFS ARC
There's a second tenant on the host besides the VMs: ZFS. ZFS uses a chunk of RAM as a read cache called the ARC (Adaptive Replacement Cache), and by default it will happily grow to use up to half (or more) of the machine's memory. That's fine on a dedicated s...
Making room: right-sizing live VMs
The cluster needed real memory: a control-plane node, three workers at 24 GiB each, a build runner, and later three database VMs. Adding all of that naively would have blown past physical RAM. So before adding, I went looking for slack in what already existed....
Lessons from sizing this lab
The short version, for anyone doing the same on their own host: Find your binding constraint first. Here it's RAM, every time. CPU overcommits fine; disk is cheap. Plan against the thing that actually runs out. No swap means no second chances. On a hypervisor...
Why NFS for cluster storage
Pods are disposable; their data shouldn't be. Kubernetes solves that with PersistentVolumes — storage that outlives the pod using it. The question is what backs those volumes. For a lab, NFS is a pragmatic, honest choice: It's ReadWriteMany — the same volume ...
The NFS server
A dedicated VM (K8s-NFS, 10.100.100.12) does one job: export a directory. K8s-NFS (10.100.100.12) nfs-kernel-server export: /srv/nfs/k8s -> 10.100.100.0/24 (rw, no_root_squash) firewall: allow tcp/2049 from 10.100.100.0/24 (NFSv4) The export is s...
The CSI driver and the default StorageClass
A bare NFS export is static — you'd have to hand-create a PersistentVolume for every claim. The CSI driver for NFS automates that: when a pod asks for storage, the driver creates a subdirectory on the export and wires up the volume on the fly. That's dynamic p...
Proving it actually works
Storage you haven't tested is a rumour. The check is a throwaway claim: kubectl apply -f - <<'YAML' apiVersion: v1 kind: PersistentVolumeClaim metadata: { name: nfs-dyn-test } spec: accessModes: [ReadWriteMany] resources: { requests: { storage: 1Gi } } YAM...