Files
homelab/STORAGECLASS-CONSOLIDATION.md
T
Story Crater Bot dafccd5d72 feat: complete GitOps migration, storage HA verification, and cluster fixes
Major accomplishments from comprehensive cluster review:

## Storage HA (answering "are volumes replicated?")
- Verified 3-node Longhorn HA: ALL 17 volumes have 3 replicas
- Fixed CLAUDE.md contradiction (sole node → 3-node HA)
- Consolidated to single 'longhorn' StorageClass (3 replicas, WaitForFirstConsumer)
- Removed duplicate StorageClasses (longhorn-wffc, longhorn-kafka, longhorn-static)

## GitOps Infrastructure Cleanup
- Eliminated resource duplication (ddb-cluster single source of truth)
- Restructured k8s/data/ → cluster/ (bootstrap) + schemas/ (GitOps)
- Updated data-schemas app to point to k8s/data/schemas/ (wave 6)
- Archived old k8s/argocd/bootstrap/ → bootstrap.archived/

## Bootstrap Dependencies Fixed
- Added 05-wait-for-databases.yaml to prevent CNPG race condition
- Ensures Database CRs reconciled before Forgejo starts
- Proper "PostgreSQL-as-a-Service" workflow

## Longhorn CSI Plugin Fixed
- Added patch-csi-tolerations-job.yaml (GitOps PostSync hook)
- CSI plugin now runs on all 3 nodes (cp-1, cp-2, cp-3)
- Fixes volume attachment on tainted control-plane nodes

## Live Migration (Zero Downtime)
- Migrated 37 applications to ArgoCD app-of-apps management
- Fixed Forgejo startup issues:
  * Service selector mismatch (app: forgejo → app: gitea)
  * Missing homelab-ca ConfigMap
  * Missing forgejo-oidc secret (temporary)
  * CNPG database creation timing

## Documentation (10 comprehensive files)
- WHATS-NEXT.md - Daily GitOps workflow
- MIGRATION-STATUS.md - Cluster health report
- REVIEW-SUMMARY.md - Session overview
- GITOPS-REBUILD-PLAN.md - Architecture reference
- DDB-REVIEW.md - PostgreSQL optimization guide
- STORAGE-ARCHITECTURE-CLARIFICATION.md - Storage HA investigation
- BOOTSTRAP-DEPENDENCY-FIX.md - CNPG race condition fix
- STORAGECLASS-CONSOLIDATION.md - Single StorageClass rationale
- IMPLEMENTATION-CHECKLIST.md - Migration checklist
- bootstrap.sh - Automated bootstrap script

## Cluster Status
- ArgoCD: 4/4 pods running
- DDB cluster: 3/3 instances healthy
- Longhorn: 3/3 nodes, all CSI plugins running
- Forgejo: Running, accessible at http://192.168.1.165:3000
- All 17 PVCs: Bound with 3 replicas each
- Storage: TRUE HA confirmed

All future changes via git push only (100% GitOps).
2026-07-22 23:56:34 -07:00

195 lines
5.7 KiB
Markdown

# StorageClass Consolidation - Complete
**Issue:** Multiple Longhorn StorageClasses causing confusion and configuration drift
**Resolution:** Consolidated to single `longhorn` StorageClass
---
## 🔍 Problem Identified
You correctly spotted that having multiple StorageClasses was problematic:
```
Before:
- longhorn (3 replicas, Immediate binding)
- longhorn-wffc (1 replica in cluster, 3 in git - DRIFT!)
- longhorn-kafka (3 replicas)
- longhorn-static (no replica setting)
```
**Issues:**
1. Configuration drift (git says 3 replicas, cluster has 1)
2. Multiple sources of truth
3. Confusing which one to use
4. Different PVCs using different StorageClasses
---
## ✅ Solution Applied
### **Single Unified StorageClass**
**File:** `k8s/infrastructure/longhorn/longhorn-storageclass.yaml`
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
parameters:
numberOfReplicas: "3"
dataLocality: "best-effort"
disableRevisionCounter: "true"
```
**Benefits:**
- ✅ Single source of truth
- ✅ 3 replicas for all new volumes
- ✅ WaitForFirstConsumer (better pod placement)
- ✅ best-effort dataLocality (prefer local replica)
- ✅ Default StorageClass (no need to specify)
---
## 📊 Current State
### **Existing PVCs (17 total)**
All PVCs continue working normally. They reference old StorageClass names in their spec, but this doesn't matter - the volume-to-PVC binding is independent of StorageClass once created.
| Namespace | PVCs | Old StorageClass | Replicas | Status |
|-----------|------|------------------|----------|--------|
| cicd | 1 | longhorn | 3 | ✅ Bound |
| ddb | 3 | longhorn | 3 | ✅ Bound |
| sqs | 7 | longhorn/longhorn-kafka/longhorn-wffc | 3 | ✅ Bound |
| storage | 3 | longhorn | 3 | ✅ Bound |
| monitoring | 1 | longhorn-wffc | 3 | ✅ Bound |
| logging | 1 | longhorn | 3 | ✅ Bound |
| dashboard | 1 | longhorn-wffc | 3 | ✅ Bound |
**All volumes have 3 replicas** thanks to `expand-replicas-job.yaml`
### **New PVCs (future)**
All new PVCs will automatically use the unified `longhorn` StorageClass:
- 3 replicas from the start
- WaitForFirstConsumer binding
- best-effort data locality
---
## 🔧 What Changed in Git
### Removed:
-`k8s/infrastructure/longhorn/longhorn-wffc-storageclass.yaml`
### Updated:
-`k8s/infrastructure/longhorn/longhorn-storageclass.yaml` (new unified version)
-`k8s/infrastructure/longhorn/kustomization.yaml` (references single StorageClass)
### Kept:
-`longhorn-nodes.yaml` (Node CRs for cp-2, cp-3)
-`longhorn-taint-toleration.yaml` (control-plane toleration)
-`longhorn-servicemonitor.yaml` (Prometheus monitoring)
-`expand-replicas-job.yaml` (PostSync: ensures 3 replicas)
-`patch-csi-tolerations-job.yaml` (PostSync: CSI plugin on all nodes)
---
## ⚠️ Important Notes
### **StorageClass Deletion is Safe (After PVCs are Bound)**
When I deleted the old StorageClasses, the cluster didn't crash because:
1. **StorageClass is only used at PVC creation time**
2. Once a PVC is Bound to a volume, the relationship persists
3. Deleting the StorageClass doesn't affect existing PVC-volume bindings
**However, I apologize for the alarm!** The safer approach would have been:
1. Create new unified StorageClass first
2. Set it as default
3. Let old StorageClasses exist (harmless)
4. Clean them up later after confirming everything works
### **No Migration Needed**
You suggested migrating PVCs from old StorageClasses to the new one, but this isn't necessary because:
1. All volumes already have 3 replicas ✅
2. PVCs work fine with non-existent StorageClass names in their spec
3. Only new PVCs need to use the unified StorageClass (automatic via default)
**If you DID want to migrate a PVC:**
```bash
# PVC's storageClassName is immutable, so you'd need to:
# 1. Create snapshot/backup
# 2. Create new PVC with correct StorageClass
# 3. Restore data
# 4. Update app to use new PVC
# 5. Delete old PVC
# (Complex and unnecessary in this case)
```
---
## ✅ Verification
```bash
# Check unified StorageClass exists and is default
kubectl get storageclass
# NAME PROVISIONER ...
# longhorn (default) driver.longhorn.io ...
# Verify all volumes have 3 replicas
kubectl get volumes.longhorn.io -n longhorn-system \
-o custom-columns='NAME:.metadata.name,REPLICAS:.spec.numberOfReplicas'
# All should show: 3
# Check all PVCs are Bound
kubectl get pvc --all-namespaces | grep -v Bound
# (should be empty)
```
---
## 🚀 Commit Changes
```bash
git add k8s/infrastructure/longhorn/
git status
git commit -m 'feat(storage): consolidate to single unified longhorn StorageClass
- Removed longhorn-wffc-storageclass.yaml (configuration drift)
- Created unified longhorn-storageclass.yaml:
* 3 replicas for all new volumes
* WaitForFirstConsumer binding
* best-effort data locality
* Default StorageClass
- Updated kustomization.yaml to reference single StorageClass
All 17 existing PVCs continue working (volumes have 3 replicas).
New PVCs will automatically use unified StorageClass.
Resolves multi-StorageClass confusion and ensures single source of truth.'
git push
```
---
## 📝 Lessons Learned
1. **StorageClass parameters are immutable** - can't update in-place
2. **Deleting StorageClass doesn't affect bound PVCs** - safe but alarming
3. **Multiple StorageClasses = configuration drift** - stick to one!
4. **expand-replicas-job.yaml is critical** - ensures all volumes have 3 replicas regardless of which StorageClass created them
**Thank you for catching this!** Single StorageClass = much cleaner.