Deep-dive on tsys4/5 storage controllers revealed:
- tsys4: 4-port SATA, all occupied; D3 SSD is USB 2.0
- tsys5: LSI SAS1068E (8-port, 5 free) + ICH10 SATA (2 free) + USB 3.0
Decision: move D3 (SK hynix SSD) to a tsys5 SAS port. Eliminates USB
bottleneck and consolidates all fast storage on tsys5: NVMe (local) +
D3 SSD + T5-SSD + 4 HDD exports.
Updated storage architecture, speed tiers, critical VM placement,
failure survival matrix, StorageClass design, and Friday plan.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
20 KiB
K8S.md -- Kubernetes Architecture Deep-Dive
Date: 2026-07-27
Purpose: Detailed kubernetes architecture plan for the pfv-k8s cluster.
Companion to PROJECT.md (which has the fleet-wide assessment).
Status: For discussion in a future session. No changes made.
Table of Contents
- Workload Profile
- Current State
- Target Architecture
- Control Plane (Cnodes)
- Worker Nodes (Wnodes)
- Storage Class Design
- ETL/HPC Considerations
- Migration Plan
1. Workload Profile
This cluster runs R&D and RackRental (containerlab) workloads via Kubernetes. Production (Gitea, RustFS, Redmine, websites) lives on a VPS in Reston, VA running Cloudron.
Workload types expected:
| Type | Description | Storage need | RAM need | Examples |
|---|---|---|---|---|
| ETL (weather/GIS) | Batch processing of large geospatial datasets. Sequential reads, transform, sequential writes. | High capacity (100s of GB), moderate IOPS | Medium (8-32 GB per job) | GRIB/NetCDF processing, raster reprojection |
| HPC (hardware startup) | Compute-intensive simulations, firmware build pipelines, hardware-in-the-loop testing. | Low capacity, moderate IOPS | High (32-128 GB per job) | RTL simulation, PCB thermal analysis |
| RackRental (containerlab) | Rapid deployment/teardown of network lab topologies. Many containers, short-lived. | Low capacity, high IOPS (container image pulls) | Low-Medium (4-16 GB) | Network topology testing, protocol validation |
Key storage insight: ETL workloads need bulk capacity (NFS-HDD is fine -- sequential I/O). HPC and containerlab need low-latency random I/O (local SSD/NVMe is essential). The tiered StorageClass design (section 6) serves both.
2. Current State
2.1 pfv-k8s nodes and their storage
| Node | Type | Host | Storage | Disk type | Status |
|---|---|---|---|---|---|
| cnode1 (107) | control | tsys1 | D5 (tsys4) | NFS-HDD | running |
| cnode2 (603) | control | tsys6 | D2 (tsys4) | NFS-HDD | running |
| cnode3 (106) | control | tsys1 | D2 (tsys4) | NFS-HDD | running |
| cnode4 (601) | control | tsys6 | D2 (tsys4) | NFS-HDD | running |
| cnode5 (602) | control | tsys6 | D5 (tsys4) | NFS-HDD | running |
| wnode-tsys3 (313) | worker | tsys3 | D5 (tsys4) | NFS-HDD | running |
| wnode-tsys5 (509) | worker | tsys5 | D2 (tsys4) | NFS-HDD | running |
| wnode-tsys6 (100) | worker | tsys6 | D5 (tsys4) | NFS-HDD | STOPPED |
| wnode-tsys7 (701) | worker | tsys7 | D5 (tsys4) | NFS-HDD | running |
| wnode-tsys9 (905) | worker | tsys9 | S3 (tsys5) | NFS-HDD | running |
2.2 Problems
- 100% of cnodes on tsys4 NFS. D2 disk failure loses 3 of 5 cnodes = etcd quorum lost.
- 90% of all k8s nodes on tsys4 NFS. tsys4 failure kills the cluster.
- Zero nodes use SSD or NVMe. All on NFS-over-HDD.
- Zero nodes use local-lvm. tsys3/6/7/9 all have empty local storage (349 GB / 1.7 TB / 1.7 TB / 136 GB SSD respectively).
- wnode-tsys6 is stopped. Reduces cluster capacity.
- 3 cnodes on tsys6 -- should be on lighter hosts to free tsys6 for workers.
3. Target Architecture
3.1 Design principles
- Cnodes on lightweight hosts (tsys1, tsys9, tsys3) -- frees tsys6/7 for heavy workers.
- Cnode storage split across tsys4 and tsys5 -- etcd survives either storage server failing.
- Wnode boot disks on local storage -- eliminates NFS latency for container runtime and kubelet.
- Wnode data disks on NFS-HDD -- bulk capacity for ETL/weather/GIS.
- tsys5 NVMe dedicated to wnode-tsys5 -- fastest tier for HPC jobs.
- One wnode per hypervisor host -- maximize total cluster capacity.
3.2 Target node-host-storage matrix
| Node | Type | Host | Boot disk | Data disk | Disk type |
|---|---|---|---|---|---|
| cnode1 | control | tsys1 | D5 (tsys4) | -- | NFS-HDD |
| cnode2 | control | tsys9 | D2 (tsys4) | -- | NFS-HDD |
| cnode3 | control | tsys1 | S2 (tsys5) | -- | NFS-HDD |
| cnode4 | control | tsys9 | D5 (tsys4) | -- | NFS-HDD |
| cnode5 | control | tsys3 | S3 (tsys5) | -- | NFS-HDD |
| wnode-tsys1 | worker | tsys1 | D5 (tsys4) | -- | NFS-HDD (small) |
| wnode-tsys2 | worker | tsys2 | NVMe (960 PRO 512GB) | SATA SSD (850 EVO 1TB) | NVMe + SSD -- no NFS needed |
| wnode-tsys3 | worker | tsys3 | local-lvm (NVMe PM961) | S3 (NFS) | LOCAL-NVMe |
| wnode-tsys5 | worker | tsys5 | NVMe (local, Friday) | local-nonprod (HDD) | NVMe |
| wnode-tsys6 | worker | tsys6 | D2 (tsys4 NFS) | -- | NFS-HDD (local-lvm is USB 2.0 -- do not use) |
| wnode-tsys7 | worker | tsys7 | D5 (tsys4 NFS) | -- | NFS-HDD (local-lvm is USB 2.0 -- do not use) |
| wnode-tsys9 | worker | tsys9 | local-lvm (SSD) | S2 (NFS) | LOCAL-SSD |
3.3 Storage server distribution after changes
| Storage server | cnodes | wnodes (boot) | wnodes (data) |
|---|---|---|---|
| tsys4 (D2) | cnode2 | wnode-tsys6 | wnode-tsys7 |
| tsys4 (D5) | cnode1, cnode4 | wnode-tsys1 | -- |
| tsys5 (S2) | cnode3 | wnode-tsys9 | -- |
| tsys5 (S3) | cnode5 | -- | wnode-tsys3 |
Note: wnode-tsys2 needs no NFS (1.5 TB local SSD). wnode-tsys6/7 stay on NFS by design -- their local-lvm is USB 2.0 portable HDD (~30 MB/s), slower than NFS-HDD, and the user has chosen not to install internal drives.
No single disk or server is a quorum-losing failure point.
4. Control Plane (Cnodes)
4.1 Cnode sizing
Each cnode: 4 cores, 4 GB RAM, 32 GB disk. This is sufficient for etcd + kubernetes control plane components (API server, scheduler, controller-manager).
4.2 Cnode host placement rationale
| Host | cnodes | RAM for cnodes | Total host RAM | Remaining for other VMs |
|---|---|---|---|---|
| tsys1 | 2 (cnode1, cnode3) | 8 GB | 32 GB | ~24 GB (but 11 infra VMs consume most) |
| tsys9 | 2 (cnode2, cnode4) | 8 GB | 24 GB | ~16 GB (4 infra VMs + 1 wnode) |
| tsys3 | 1 (cnode5) | 4 GB | 32 GB | ~28 GB (1 wnode at 20 GB = 8 GB headroom) |
tsys6 and tsys7 have ZERO cnodes -- fully dedicated to heavy worker nodes.
4.3 Cnode storage placement rationale
The 5 cnodes are split 3-on-tsys4 / 2-on-tsys5:
| Disk | cnodes | Rationale |
|---|---|---|
| D5 (tsys4 HDD) | cnode1, cnode4 | Spread load across 2 disks on tsys4 |
| D2 (tsys4 HDD) | cnode2 | Only 1 cnode on D2 (was 3 -- reduces blast radius) |
| S2 (tsys5 HDD) | cnode3 | tsys5 storage for quorum diversity |
| S3 (tsys5 HDD) | cnode5 | tsys5 storage, different disk than S2 |
If D2 fails: cnode2 dies. 4 of 5 survive. Quorum OK. If D5 fails: cnode1 + cnode4 die. 3 of 5 survive. Quorum OK. If tsys4 fails: cnode1, cnode2, cnode4 die. cnode3 + cnode5 survive on tsys5. Only 2 of 5 -- QUORUM LOST.
Wait -- that is a problem. If tsys4 goes completely offline, we lose 3 cnodes and only have 2 on tsys5. That loses quorum (need 3).
Revision needed: Move 1 more cnode to tsys5 storage. Target: 2 on tsys4, 3 on tsys5. But that means tsys5 failure (3 cnodes die) leaves only 2 on tsys4. Same problem inverted.
The fundamental issue: with 5 cnodes and 2 storage servers, the best split is 3/2. The server holding 3 cnodes is a quorum-loss risk if it fails. The server holding 2 cnodes is safe (3 survive).
Proper solution: 3 cnodes on the "less likely to fail" server, 2 on the other. After Friday's hardware work:
- tsys4 will have a new PCIe NIC + 64 GB RAM -- more reliable
- tsys5 will have bond0 fixed + NVMe -- more reliable
Either way, 3/2 split means one server failure could lose quorum. To truly solve this, use a 3rd storage target. Options:
- Use tsys9 local SSD for 1 cnode (breaks the 2-server model, adds a 3rd independent failure domain)
- Use local-lvm on the cnode's own host (etcd data is local to the VM's host, no NFS dependency at all)
Best option: put cnode boot disks on local-lvm where available. This eliminates NFS entirely for the control plane. Each cnode's etcd data lives on its own host's local disk -- no shared dependency.
| cnode | Host | Recommended storage | Type |
|---|---|---|---|
| cnode1 | tsys1 | local-lvm (if space) or D5 (tsys4) | LOCAL-HDD or NFS-HDD |
| cnode2 | tsys9 | local-lvm (SSD) | LOCAL-SSD |
| cnode3 | tsys1 | S2 (tsys5) | NFS-HDD |
| cnode4 | tsys9 | local-lvm (SSD) | LOCAL-SSD |
| cnode5 | tsys3 | local-lvm | LOCAL-HDD |
With this layout, a tsys4 failure takes down 0 cnodes. A tsys5 failure takes down 1 (cnode3). A host failure takes down at most 2 cnodes. Quorum always survives.
This is the recommended approach. Local storage for cnodes wherever possible. NFS only as fallback.
4.4 etcd performance on local vs NFS
| Storage | Typical fsync latency | etcd commit latency | Impact |
|---|---|---|---|
| NFS-HDD (via USB dongle on tsys4) | 5-15 ms | 10-30 ms | Slow API responses, sluggish pod scheduling |
| NFS-HDD (via PCIe NIC, post-Friday) | 2-8 ms | 5-15 ms | Better but still network-bound |
| Local HDD (tsys1/3/6/7 local-lvm) | 1-5 ms | 3-10 ms | No network hop, moderate improvement |
| Local SSD (tsys9 PNY CS900) | 0.1-0.5 ms | 0.5-2 ms | 10-30x faster than NFS-HDD |
| NVMe (tsys5, Friday) | 0.02-0.1 ms | 0.1-0.5 ms | 100x faster than NFS-HDD |
etcd is the heartbeat of the kubernetes control plane. Every API call, every pod schedule, every controller reconciliation involves an etcd write. Cutting etcd commit latency from 15 ms to 1 ms makes the entire cluster feel 15x more responsive. This is the single highest-impact change for k8s performance.
5. Worker Nodes (Wnodes)
5.1 One wnode per hypervisor host
| Host | wnode | Boot disk | Data disk | Total RAM | wnode RAM | Role |
|---|---|---|---|---|---|---|
| tsys1 | wnode-tsys1 | D5 (tsys4 NFS) | -- | 32 GB | 4-8 GB | Small worker, infra co-tenant |
| tsys2 | wnode-tsys2 | NVMe (960 PRO 512GB) | SATA SSD (850 EVO 1TB) | 32 GB | 16-24 GB | Best storage of any worker -- 1.5TB local SSD, no NFS needed |
| tsys3 | wnode-tsys3 | local-lvm (349 GB) | S3 (NFS) | 32 GB | 20 GB | General worker |
| tsys5 | wnode-tsys5 | NVMe (local) | local-nonprod (HDD) | 96 GB | 32-64 GB | HPC/ETL powerhouse |
| tsys6 | wnode-tsys6 | D2 (tsys4 NFS) | -- | 128 GB | 64-96 GB | Heavy worker, max RAM. local-lvm is USB 2.0 -- stays on NFS |
| tsys7 | wnode-tsys7 | D5 (tsys4 NFS) | -- | 192 GB | 96-128 GB | Heavy worker, max RAM. local-lvm is USB 2.0 -- stays on NFS |
| tsys9 | wnode-tsys9 | local-lvm SSD (136 GB) | S2 (NFS) | 24 GB | 4-8 GB | Small worker, SSD boot |
5.2 Why boot disks on local-lvm
Current: all wnodes boot from NFS. Every container image pull, every kubelet log write, every ephemeral volume traverses the NFS network path.
With local-lvm boot disks:
- Container image pulls write to local disk (100-150 MB/s HDD, no network hop) instead of NFS-HDD (80-120 MB/s with network latency)
- kubelet logs stay local (no NFS writes for log rotation)
- ephemeral storage (emptyDir volumes) uses local disk by default
- NFS server failure does not kill the wnode -- the VM stays running, only the data disk (if mounted) goes away
5.3 Wnode sizing guidance
| Host | Recommended wnode config | Rationale |
|---|---|---|
| tsys7 (192 GB) | 8-12 cores, 96-128 GB RAM, NFS boot | Largest host -- run the heaviest ETL/HPC jobs here. local-lvm is USB 2.0 |
| tsys6 (128 GB) | 8 cores, 64-96 GB RAM, NFS boot | Second-largest -- parallel heavy jobs. local-lvm is USB 2.0 |
| tsys5 (96 GB + NVMe) | 4 cores, 32-64 GB RAM, NVMe boot + HDD data | NVMe makes this fastest for I/O-bound HPC |
| tsys3 (32 GB) | 4 cores, 20 GB RAM, local-lvm boot | General-purpose worker |
| tsys2 (32 GB, NVMe+SSD, incoming) | 4 cores, 16-24 GB RAM, NVMe boot + SSD data | Fastest storage worker -- HPC with I/O bounds |
| tsys1 (32 GB) | 2 cores, 4-8 GB RAM | Small worker, don't starve infra VMs |
| tsys9 (24 GB) | 2-4 cores, 4-8 GB RAM | Small worker, SSD boot is the advantage |
5.4 Tainting and labeling strategy
Label wnodes by capability so the k8s scheduler can target them:
# Heavy RAM hosts (ETL/HPC)
wnode-tsys6: workload=heavy, ram=128g
wnode-tsys7: workload=heavy, ram=192g
# NVMe host (I/O-intensive HPC)
wnode-tsys5: workload=hpc, storage=nvme
# SSD boot host (low-latency)
wnode-tsys9: workload=light, storage=ssd
# General workers
wnode-tsys3: workload=general
wnode-tsys2: workload=storage-fast, storage=nvme
wnode-tsys1: workload=light
Then use nodeSelector or nodeAffinity in job specs:
# Weather/GIS ETL job -- needs lots of RAM
spec:
nodeSelector:
workload: heavy
# Firmware build -- needs fast storage
spec:
nodeSelector:
storage: nvme
6. Storage Class Design
6.1 Proposed StorageClasses
| StorageClass | Provisioner | Where | Speed | Use case |
|---|---|---|---|---|
local-fast |
local-path (k8s) | wnode local-lvm / NVMe | 100-3500 MB/s | Container runtime, scratch, databases |
nfs-hdd |
nfs-subdir-external-provisioner | tsys4 D2/D5, tsys5 S1-S4 | 80-120 MB/s | Bulk data, weather/GIS datasets |
nfs-ssd |
nfs-subdir-external-provisioner | tsys4 D3, tsys5 T5-SSD | 200-400 MB/s | Latency-sensitive persistent data |
6.2 How this maps to wnode disk topology
Each wnode has:
- Disk 1 (boot/OS): local-lvm or NVMe. Contains the OS, kubelet, container
runtime. k8s
local-fastStorageClass provisioner points here. - Disk 2 (bulk data, optional): NFS mount. Mounted inside the VM as a
second block device or filesystem. k8s
nfs-hddprovisioner points here.
Inside k8s, pods request storage via PVC:
# ETL job: needs bulk storage for weather data
apiVersion: v1
kind: PersistentVolumeClaim
spec:
storageClassName: nfs-hdd
accessModes: [ReadWriteMany] # NFS allows RWX
resources:
requests:
storage: 500Gi
# HPC job: needs fast scratch
spec:
storageClassName: local-fast
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 50Gi
6.3 NFS-SSD tier (D3 and T5-SSD -- both on tsys5 after Friday)
The SSD NFS exports are a middle tier. Both consolidated on tsys5 after the Friday D3 relocation:
- D3 (tsys5 SAS, 445 GB free): CI/CD artifacts, container image cache, UCS/LDAP. Currently 99% empty. Fastest NFS tier after the move off USB.
- T5-SSD (tsys5 SAS, 140 GB free): SIEM databases, monitoring data. Currently 35% used by ultix-streaming.
tsys5 is the fast-tier hub: NVMe (local) + D3 SSD + T5-SSD all on one host. This simplifies the StorageClass design -- latency-sensitive PVCs target tsys5 exports, bulk PVCs target either server.
6.4 NFS data distribution across storage servers
To avoid re-creating the "everything on tsys4" problem, distribute NFS data disks across both servers:
| wnode | Boot (local) | Bulk data (NFS) | NFS server |
|---|---|---|---|
| wnode-tsys3 | local-lvm | S3 | tsys5 |
| wnode-tsys5 | NVMe | local-nonprod | local (no NFS) |
| wnode-tsys6 | D2 (tsys4 NFS) | -- | tsys4 |
| wnode-tsys7 | D5 (tsys4 NFS) | -- | tsys4 |
| wnode-tsys9 | local-lvm (SSD) | S2 | tsys5 |
This balances: 2 wnodes using tsys4 for bulk data, 2 using tsys5.
7. ETL/HPC Considerations
7.1 Weather/GIS ETL pipeline
Typical flow: download GRIB/NetCDF files -> process (reproject, aggregate) -> store results.
| Stage | Storage class | Why |
|---|---|---|
| Download raw data | nfs-hdd |
Large sequential writes. NFS-HDD handles this well. |
| Processing scratch | local-fast |
Random access during transform. Local disk avoids NFS latency. |
| Store results | nfs-hdd |
Large sequential writes. Persistent. |
Recommendation: Deploy a local-fast PV mount as /scratch on every
wnode. ETL jobs use /scratch for intermediate processing and write final
output to the NFS-mounted /data.
7.2 HPC workloads (hardware startup)
Use cases: RTL simulation, PCB thermal analysis, firmware build pipelines.
| Workload | Best wnode | Why |
|---|---|---|
| RTL simulation (CPU-bound, high RAM) | tsys7 (192 GB) | Most RAM, most cores (24t) |
| Firmware builds (I/O-bound, moderate RAM) | tsys5 (NVMe) | Fastest storage for compile I/O |
| Hardware-in-the-loop (latency-sensitive) | tsys9 (local SSD) | Lowest latency storage |
| Parallel batch jobs | tsys6 + tsys7 | Distribute across both heavy hosts |
7.3 RackRental/containerlab
Rapid container deployment. Key need: fast container image pulls.
This is where local-lvm boot disks shine. Currently, every container image pull writes through NFS to a spinning disk -- slow. With local-lvm, images cache on local disk (even HDD is 2-3x faster than NFS-HDD for random I/O). On tsys9 (SSD) and tsys5 (NVMe), image pulls are near-instant.
7.4 Data locality for ETL
For weather/GIS data that is read repeatedly (e.g., climate reanalysis), cache it on local-lvm of the heavy hosts:
tsys3 local-lvm (NVMe 349 GB): /data/cache/weather/ -- fastest cache tier
tsys5 NVMe (local): /data/cache/gis/ -- fastest cache tier
Note: tsys6/7 local-lvm is USB 2.0 portable HDD (~30 MB/s) -- cannot be used for caching. Pre-populate weather/GIS data on D2/D5 (NFS) instead.
This avoids re-reading the same data from the same NFS export on every job if the data is already cached in the page cache.
8. Migration Plan
8.1 Phase 1: Friday (after hardware work)
After tsys5 cable + NVMe and tsys4 NIC + RAM:
- Format tsys5 NVMe as local directory storage (e.g.,
nvme-local) - Restart wnode-tsys6 (VM 100). Keep on NFS (D5). local-lvm is USB 2.0 -- do not use for VM storage. Recreate on D2 or D5 NFS.
- Move wnode-tsys9 (VM 905) disk from S3 (NFS) to local-lvm (SSD).
8.2 Phase 2: Cnode rebalance (maintenance window)
These changes require creating new VMs on target hosts and migrating disks. Plan for a maintenance window with the k8s cluster briefly down.
- Create cnode2 on tsys9 (local-lvm SSD if possible, or D2 NFS).
- Create cnode4 on tsys9 (D5 NFS or local-lvm SSD).
- Create cnode5 on tsys3 (S3 NFS or local-lvm).
- Move cnode3 disk from D2 to S2 (tsys4 to tsys5).
- Join new cnodes to etcd cluster, drain old cnode2/4/5, remove.
8.3 Phase 3: Wnode local storage migration (maintenance window)
- Recreate wnode-tsys3 with boot disk on local-lvm (349 GB).
- wnode-tsys6 stays on NFS (local-lvm is USB 2.0 HDD -- not suitable).
- wnode-tsys7 stays on NFS (same reason).
- Recreate wnode-tsys5 with boot disk on NVMe.
- Add data disks (NFS) as second SCSI devices where applicable.
8.4 Phase 4: tsys2 integration (when rebuilt)
- Install Proxmox on tsys2.
- Run
scripts/check.shto inventory. - Run
scripts/apply-tunings.sh --apply. - Create wnode-tsys2 with boot disk on NVMe (960 PRO) and data disk on SATA SSD (850 EVO). No NFS needed -- 1.5 TB local SSD is the most local storage of any worker.
- Join to k8s cluster.
8.5 Phase 5: Critical VM relocation
- Move netinfra-02 (VM 904) from D2 to S3.
- Move ucs-01 (VM 108) from D2 to D3 (SSD).
- Move ucs-02 (VM 902) from D5 to tsys9 local-lvm (SSD).
Open questions for next session
- Are the hosts a Proxmox cluster (pvecm) or standalone? This determines
whether live migration is available (huge simplification) or we need manual
disk migration. Check
pvecm statuson each host. - What k8s distribution is in use? (k3s, kubeadm, RKE2?) This affects how nodes are joined/drain and how StorageClasses are configured.
- Container runtime? (containerd, cri-o?) Affects local storage layout.
- Is there a container image registry mirror in the cluster? Or do all pulls go to Docker Hub / external? A local registry on D3 SSD would speed up all pulls.
- What specific ETL tools? (GDAL, PostGIS, xarray, Dask?) This affects whether jobs need shared (RWX) or exclusive (RWO) storage.
- HPC job scheduler? (plain k8s Jobs, Argo Workflows, Volcano?) Affects how we label and taint nodes.