Files
homelab-docs/docs/storage.md
Tuan-Dat Tran 6bfdf2c674 docs: add writeup of the 14 -> 7 worker node consolidation
Narrative account of the August 2026 migration: why fourteen small agents
cost more than seven large ones, the rebuild failures that made the first
pair take three times its budget, and the faulty Ethernet cable behind
them that had been silently downshifting a host to 100 Mbit for nine days.

Also records the operational gotchas worth knowing: Longhorn evicts at
disk level rather than node level, draining does not preserve replicas,
rebuilds can report progress while transferring nothing, GitOps selfHeal
reverts temporary drain workarounds, and cloud images panic on the first
boot after a resize.

Updates node counts in README and storage.md, which the post would
otherwise contradict, and adds a deep-dives index so the docs/ pages are
reachable from the README at all.
2026-08-04 09:52:28 +02:00

46 lines
2.6 KiB
Markdown

# Storage
## Overview
Three storage tiers, each doing a different job:
| Tier | System | Access | Used by |
|------|--------|--------|---------|
| Distributed block | Longhorn | RWO + RWX | All stateful K8s workloads |
| Relational | CloudNativePG | In-cluster Postgres | Immich |
| Network file | NFS (bare-metal) | NFS mount | Jellyfin media library |
---
## Longhorn
Longhorn gives distributed block storage across all 7 agent nodes. Each volume is replicated (default: 3 replicas) across different nodes, using the local disk on each agent (256 GB each), with soft anti-affinity and auto-balance enabled so replicas actively spread out rather than pile up.
RWO (ReadWriteOnce) covers most services. RWX (ReadWriteMany) is used where multiple pods need access to the same volume. Snapshots and backups are available through the Longhorn UI — backups target a Hetzner Storage Box over CIFS, so a full node-loss scenario doesn't also mean a data-loss scenario.
Control plane nodes are tainted `NoSchedule` — Longhorn manager tolerates this and runs everywhere, but user workloads stay on agent nodes.
A recurring annoyance: containerd's image layer cache on `/var/lib/rancher/k3s/agent/containerd/` grows unbounded and can eat tens of GB, which Longhorn then reads as node-level disk pressure and stops scheduling. A systemd timer (`node_cleanup` Ansible role) prunes it daily with `crictl rmi --prune` and vacuums journal logs down to 500 MB, staggered by a random delay so it doesn't hit every node at once.
---
## CloudNativePG
CloudNativePG manages HA PostgreSQL clusters as Kubernetes resources — one `Cluster` per app (Immich, Vaultwarden, N8n, Paperless, LiteLLM), each backed by Longhorn PVCs. Vaultwarden runs a 3-instance HA cluster with automatic failover; the rest run single-instance. CNPG handles streaming replication, failover, and scheduled backups via Barman Cloud to S3-compatible storage, with WAL compression and point-in-time recovery.
---
## NFS
`aya01` is a dedicated bare-metal NFS server. Jellyfin mounts the share from `docker-host11` to access movies, TV shows, and music. Keeping the media library on a separate host means the Jellyfin VM can be rebuilt without touching the data.
NFS is not used for K8s workloads — Longhorn handles all PVC-backed storage.
---
## Secrets
Kubernetes secrets go through Sealed Secrets (Bitnami). The workflow: create a regular `Secret`, encrypt it with `kubeseal` using the cluster's public key into a `SealedSecret`, then commit that to Git. Only the in-cluster controller can decrypt it.
Ansible secrets (VM credentials, API tokens) are encrypted with Ansible Vault and live in `vars/group_vars/*/secrets_*.yaml`.