Advanced Search
Search Results
228 total results found
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...
Lessons on lab storage
Pick storage per workload. Shared app data → NFS. Databases → local disk on their own VM. One size does not fit all. NFSv4, single port, subnet-scoped. Smallest firewall footprint, simplest mental model. A default StorageClass keeps manifests clean. Apps shou...
The bare-metal LoadBalancer problem
Ask Kubernetes for a Service of type LoadBalancer in the cloud and you get a real load balancer with a real IP, because the cloud provider has a controller watching for exactly that. On bare metal — a home lab — nobody's listening. The service just sits there ...
MetalLB in L2 mode
MetalLB has two modes: BGP (it speaks routing protocols to your network gear) and L2 (it answers ARP for the service IPs). For a flat single-subnet lab, L2 is the obvious pick — no router configuration, it just works on the local network. The config is two sma...
Why an API gateway, not just an ingress
Once you can get an external IP, you need something to route HTTP to the right app and, ideally, to add the cross-cutting concerns every API needs — auth, rate limiting, request shaping. A plain ingress controller (like ingress-nginx) does routing well. An API...
Kong, backed by PostgreSQL
Kong can run config-only ("DB-less") or backed by a database. I chose DB-backed, against the existing PostgreSQL server, for two reasons: it exercises the database layer the lab already has, and it's the mode that supports runtime configuration and the admin w...
Securing the admin surface (the loopback trick)
Kong's Admin API is powerful — it can reconfigure the whole gateway — and the open-source edition has no built-in authentication on it (that's a paid feature). So you must not expose it casually. Two honest options: Leave it internal. The ingress controller t...
TLS lives at the edge, not in the gateway
A natural instinct is to give Kong a certificate and let it serve HTTPS. I did that first — and then remembered the lab already terminates TLS at the edge (pfSense/HAProxy, with a wildcard cert). Doing it in both places is redundant and causes a redirect loop:...
The build half of the platform
A cluster runs containers; it doesn't build them. Something has to turn a git push into an image sitting in a registry, ready to deploy. That's CI/CD, and in this lab it's two pieces working together: git push -> Gitea (git.example.com) ...
The Gitea Actions runner
Gitea ships a CI system that speaks the same workflow syntax as GitHub Actions. To actually run jobs you register a runner — and that's a dedicated VM here (GIT-Runner, 10.100.100.11). Why a separate VM rather than running builds in the cluster? Builds are bur...
Docker-out-of-Docker, and custom job images
Jobs run inside containers, but the jobs themselves need to build containers. The classic tangle is "Docker inside Docker." The cleaner approach used here is Docker-out-of-Docker: the runner mounts the host's Docker socket into the job container, so docker bui...
Pulling private images (the bit people trip on)
Here's a subtle thing. Those custom job images live in the private registry, which requires authentication. So how does the runner pull them? It doesn't do anything special — it asks the host's Docker engine to pull, and Docker uses the credentials in the runn...
A pipeline, end to end
A workflow file in a repo (.gitea/workflows/build.yml) is all it takes. Because the job image already has the tools, the workflow is short: name: build on: { push: { branches: [main] } } jobs: build: runs-on: ubuntu-24.04 steps: - uses: actions...
Lessons on the build pipeline
Give builds their own box. Bursty, disk-heavy, sometimes privileged — you don't want that sharing fate with your cluster workloads. Docker-out-of-Docker over Docker-in-Docker. Mount the socket; skip the privileged nested daemon. Bake a real job image. Tools-i...