refactor(argocd): consolidate Applications (39→35)
Merge related Applications using multi-source pattern and PostSync hooks: 1. ingress-config ← wildcard-cert + homelab-ingress (2→1) - Both in k8s/bootstrap/ingress/, now use kustomization - Certificate deployed before Ingresses (wave 1) 2. homarr ← homarr + homarr-patches (2→1) - Added PostSync hook source (fix-probes-job.yaml) - Patches run after Helm chart deployment 3. temporal ← temporal + temporal-db-secret-sync (2→1) - Added PostSync hook source (copy-job.yaml) - DB secret sync runs after Temporal deployment 4. Removed duplicate: ingress-nginx Application - ingress-nginx-bootstrap (bootstrap) is working - Removed redundant ArgoCD-managed ingress-nginx - Eliminated duplicate DaemonSet Skipped: cert-manager + cert-manager-issuers - Wave separation needed (CRDs before Issuers) - Keep separate for safety Result: 39 → 35 Applications (-4, -10.3%) Files: - k8s/bootstrap/ingress/kustomization.yaml (updated) - k8s/argocd/apps/00-substrate.yaml (merges + removal) - k8s/argocd/apps/60-applications.yaml (merges) - CONSOLIDATION-RESULTS.md (documentation) - APPLICATION-CONSOLIDATION-PLAN.md (analysis) - GITOPS-STATUS.md (updated inventory)
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
# ArgoCD Application Consolidation Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Current:** 39 Applications
|
||||
**Proposed:** 32 Applications (Conservative: 35)
|
||||
**Benefits:** Easier management, fewer Application resources, cleaner structure
|
||||
|
||||
## Phase 1: Safe & Easy Merges (Conservative - Start Here)
|
||||
|
||||
These are Applications that already share the same directory and have clear parent-child relationships.
|
||||
|
||||
### 1.1 Merge: wildcard-cert + homelab-ingress → "ingress-config"
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
wildcard-cert (wave 1) → k8s/bootstrap/ingress/riotpiao-com-cert.yaml
|
||||
homelab-ingress (wave 2) → k8s/bootstrap/ingress/ingress.yaml
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
ingress-config (wave 1) → k8s/bootstrap/ingress/ (kustomization)
|
||||
```
|
||||
|
||||
**Rationale:** Both in same directory, ingress depends on cert existing
|
||||
|
||||
**Implementation:**
|
||||
- Update `k8s/bootstrap/ingress/kustomization.yaml` to include both
|
||||
- Create single Application pointing to k8s/bootstrap/ingress/
|
||||
- Delete wildcard-cert and homelab-ingress Applications
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
### 1.2 Merge: homarr + homarr-patches → "homarr"
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
homarr (wave 8) → Helm chart (homarr-labs.github.io)
|
||||
homarr-patches (wave 9) → k8s/applications/homarr/fix-probes-job.yaml
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
homarr (multi-source):
|
||||
- Source 1: Helm chart
|
||||
- Source 2: k8s/applications/homarr/ (patches as PostSync hook)
|
||||
```
|
||||
|
||||
**Rationale:** homarr-patches is literally a patch for homarr
|
||||
|
||||
**Implementation:**
|
||||
- Modify homarr Application to use multi-source
|
||||
- Convert fix-probes-job.yaml to PostSync hook
|
||||
- Delete homarr-patches Application
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
### 1.3 Merge: temporal-db-secret-sync → temporal (as PreSync hook)
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
temporal-db-secret-sync (wave 7) → k8s/applications/temporal/db-secret-sync/
|
||||
temporal (wave 8) → Helm chart
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
temporal (multi-source):
|
||||
- Source 1: k8s/applications/temporal/db-secret-sync/ (PreSync hook)
|
||||
- Source 2: Helm chart
|
||||
- Source 3: k8s/applications/temporal/ (values)
|
||||
```
|
||||
|
||||
**Rationale:** db-secret-sync is a bootstrap job for temporal
|
||||
|
||||
**Implementation:**
|
||||
- Modify temporal Application to use multi-source
|
||||
- Ensure db-secret-sync Job has PreSync hook annotation
|
||||
- Delete temporal-db-secret-sync Application
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
### 1.4 Merge: cert-manager-issuers → cert-manager (as multi-source)
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
cert-manager (wave 0) → Helm chart
|
||||
cert-manager-issuers (wave 1) → k8s/bootstrap/cert-manager/ (issuers)
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
cert-manager (multi-source):
|
||||
- Source 1: Helm chart
|
||||
- Source 2: k8s/bootstrap/cert-manager/ (values + issuers)
|
||||
```
|
||||
|
||||
**Rationale:** Issuers are part of cert-manager configuration
|
||||
|
||||
**Implementation:**
|
||||
- Modify cert-manager Application to use multi-source
|
||||
- Keep as wave 0 (issuers can wait for CRDs to be ready)
|
||||
- Delete cert-manager-issuers Application
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
**Phase 1 Result: 39 → 35 Applications (-4)**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Logical Consolidations (Moderate Risk)
|
||||
|
||||
### 2.1 Merge: SQS Applications → "sqs-platform"
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
strimzi-operator (wave 5) → Helm chart (operator)
|
||||
kmsvc-redis (wave 5) → Helm chart (Redis)
|
||||
kafka-cluster (wave 6) → k8s/applications/sqs/charts/kafka-cluster/
|
||||
queue-crd (wave 6) → k8s/applications/sqs/charts/queue-crd/
|
||||
management-service (wave 7) → k8s/applications/sqs/charts/management-service/
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
strimzi-operator (wave 5) → Keep separate (operator)
|
||||
sqs-platform (wave 6) → k8s/applications/sqs/ (all services + CRDs)
|
||||
```
|
||||
|
||||
**Rationale:** These 4 apps work together as the SQS platform
|
||||
|
||||
**Implementation:**
|
||||
- Create kustomization.yaml in k8s/applications/sqs/
|
||||
- Create single multi-source Application
|
||||
- Keep strimzi-operator separate (it's infrastructure)
|
||||
- Result: 5 → 2 Applications
|
||||
|
||||
### 2.2 Merge: authentik + iam-jobs → "authentik"
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
authentik (wave 3) → Helm chart
|
||||
iam-jobs (wave 3) → k8s/security/iam/ (authentik helper jobs)
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
authentik (multi-source):
|
||||
- Source 1: Helm chart
|
||||
- Source 2: k8s/security/iam/ (helper jobs)
|
||||
```
|
||||
|
||||
**Rationale:** iam-jobs appears to be authentik-specific
|
||||
|
||||
**Implementation:**
|
||||
- Verify iam-jobs are authentik-specific
|
||||
- Merge into single multi-source Application
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
### 2.3 Remove Duplicate: ingress-nginx Application
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
ingress-nginx-bootstrap (wave 0, bootstrap) → Helm chart
|
||||
ingress-nginx (wave 0, ArgoCD) → Helm chart (DUPLICATE!)
|
||||
```
|
||||
|
||||
**New State:**
|
||||
```
|
||||
ingress-nginx-bootstrap (wave 0, bootstrap) → Keep only this one
|
||||
```
|
||||
|
||||
**Rationale:** These are the same thing, bootstrap one is already working
|
||||
|
||||
**Implementation:**
|
||||
- Delete ingress-nginx Application (redundant)
|
||||
- Result: 2 → 1 Applications
|
||||
|
||||
**Phase 2 Result: 35 → 31 Applications (-4)**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Advanced Consolidations (Consider Carefully)
|
||||
|
||||
### 3.1 Consider: Monitoring Stack
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
prometheus-crds (wave 0) → k8s/platform/monitoring/crds/
|
||||
prometheus (wave 1) → Helm chart
|
||||
blackbox-exporter (wave 1) → Helm chart
|
||||
```
|
||||
|
||||
**Option A:** Keep separate (CRDs must be wave 0)
|
||||
**Option B:** Create "monitoring-stack" with prometheus + blackbox-exporter only
|
||||
|
||||
**Recommendation:** Keep separate for now (CRDs are special)
|
||||
|
||||
### 3.2 Consider: Logging Stack
|
||||
|
||||
**Current State:**
|
||||
```
|
||||
loki (wave 2) → Helm chart
|
||||
promtail (wave 2) → Helm chart
|
||||
grafana (wave 2) → Helm chart
|
||||
```
|
||||
|
||||
**Option A:** Create "logging-stack" with all three
|
||||
**Option B:** Keep separate (grafana used by multiple systems)
|
||||
|
||||
**Recommendation:** Keep separate for now (grafana has multiple consumers)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
### Sprint 1: Phase 1 (Low Risk)
|
||||
1. ✅ ingress-config (wildcard-cert + homelab-ingress)
|
||||
2. ✅ homarr (merge homarr-patches)
|
||||
3. ✅ temporal (merge db-secret-sync)
|
||||
4. ✅ cert-manager (merge issuers)
|
||||
|
||||
### Sprint 2: Phase 2 (Moderate Risk)
|
||||
5. ✅ Remove duplicate ingress-nginx
|
||||
6. ✅ sqs-platform consolidation
|
||||
7. ✅ authentik + iam-jobs
|
||||
|
||||
### Sprint 3: Phase 3 (Optional)
|
||||
8. ⚠️ Evaluate monitoring stack consolidation
|
||||
9. ⚠️ Evaluate logging stack consolidation
|
||||
|
||||
---
|
||||
|
||||
## File Changes Required
|
||||
|
||||
### For each consolidation:
|
||||
|
||||
1. **Update Application YAML** in `k8s/argocd/apps/*.yaml`
|
||||
- Convert single-source to multi-source
|
||||
- Update sync waves appropriately
|
||||
- Add hook annotations where needed
|
||||
|
||||
2. **Update kustomization.yaml** in target directories
|
||||
- Ensure all resources are listed
|
||||
- Verify namespace declarations
|
||||
|
||||
3. **Test locally:**
|
||||
```bash
|
||||
kubectl kustomize k8s/bootstrap/ingress/
|
||||
helm template <chart> -f values.yaml
|
||||
```
|
||||
|
||||
4. **Delete old Applications:**
|
||||
```bash
|
||||
kubectl delete application <old-app> -n argocd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
Each consolidation is independent. If issues arise:
|
||||
|
||||
1. **Revert git commit** with the Application change
|
||||
2. **Restore old Applications** from git history
|
||||
3. **Force ArgoCD refresh:**
|
||||
```bash
|
||||
kubectl delete application <new-merged-app> -n argocd
|
||||
kubectl apply -f k8s/argocd/apps/<old-wave-file>.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
**Before:** 39 Applications
|
||||
**After (Conservative):** 35 Applications
|
||||
**After (Full):** 31-32 Applications
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Easier to understand (related apps grouped)
|
||||
- ✅ Fewer Application CRs to manage
|
||||
- ✅ Cleaner ArgoCD UI
|
||||
- ✅ Logical grouping by function
|
||||
- ✅ Reduced sync complexity
|
||||
|
||||
**Risks:**
|
||||
- ⚠️ Multi-source Applications are slightly more complex
|
||||
- ⚠️ Sync wave ordering must be preserved
|
||||
- ⚠️ Hook timing must be correct (PreSync/PostSync)
|
||||
|
||||
---
|
||||
|
||||
## Decision: Which Phase?
|
||||
|
||||
**Recommendation:** Start with **Phase 1 (Conservative)** → 35 Applications
|
||||
|
||||
This gives us:
|
||||
- 4 fewer Applications to manage
|
||||
- Low risk (same directories)
|
||||
- Easy to understand merges
|
||||
- Can evaluate Phase 2 later based on results
|
||||
|
||||
**Do you want to proceed with Phase 1 implementation?**
|
||||
@@ -1,227 +0,0 @@
|
||||
# Bootstrap Dependency Issue - Fixed
|
||||
|
||||
**Issue Discovered:** Race condition between CNPG Database CR creation and Forgejo startup
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Problem You Identified
|
||||
|
||||
You were absolutely right! The bootstrap has a **dependency gap**:
|
||||
|
||||
```
|
||||
1. CNPG operator deployed ✅
|
||||
2. ddb-cluster created ✅
|
||||
3. Database CRs applied (forgejo, authentik, etc.) ✅
|
||||
4. Forgejo starts immediately ❌ RACE CONDITION!
|
||||
```
|
||||
|
||||
**What goes wrong:**
|
||||
- Database CR is created in Kubernetes (`kubectl apply -f forgejo-database.yaml`)
|
||||
- CNPG operator sees the CR and starts reconciliation
|
||||
- But CNPG needs 5-30 seconds to actually `CREATE DATABASE` in PostgreSQL
|
||||
- Meanwhile, Forgejo's init container tries to connect → "database forgejo does not exist"
|
||||
- Init container retries (which is why it eventually works), but this is fragile
|
||||
|
||||
## ✅ Root Cause
|
||||
|
||||
**CNPG works as "PostgreSQL-as-a-Service"** correctly:
|
||||
1. You create a `Database` CR (Custom Resource)
|
||||
2. CNPG operator watches for Database CRs
|
||||
3. CNPG executes `CREATE DATABASE` in the PostgreSQL cluster
|
||||
4. Application connects to the database
|
||||
|
||||
**The problem:** No wait between steps 3 and 4 in bootstrap!
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Permanent Fix Applied
|
||||
|
||||
### **1. Created Wait-for-Databases Job**
|
||||
|
||||
**File:** `k8s/bootstrap-local/05-wait-for-databases.yaml`
|
||||
|
||||
This Job:
|
||||
- Checks each Database CR's `.status.ready` field
|
||||
- Waits up to 5 minutes for all databases to be created
|
||||
- Only completes when CNPG has actually created the databases in PostgreSQL
|
||||
- Prevents Forgejo from starting until databases exist
|
||||
|
||||
**Updated bootstrap order:**
|
||||
```
|
||||
1. 00-namespaces.yaml # Namespaces with labels
|
||||
2. 01-argocd.yaml # ArgoCD ConfigMaps
|
||||
3. 02-cnpg-operator.yaml # CNPG operator
|
||||
4. 03-ddb-bootstrap.yaml # Cluster + Database CRs
|
||||
5. 05-wait-for-databases.yaml ← NEW! Waits for reconciliation
|
||||
6. 04-forgejo.yaml # Forgejo (databases guaranteed to exist)
|
||||
```
|
||||
|
||||
### **2. Additional Issue Fixed: Service Selector Mismatch**
|
||||
|
||||
**Problem:**
|
||||
- Old `forgejo` deployment created service with selector `app: forgejo`
|
||||
- New Helm chart creates pods with label `app: gitea`
|
||||
- Service couldn't find pods → no endpoints → connection refused
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
kubectl patch svc forgejo -n cicd -p '{"spec":{"selector":{"app":"gitea","app.kubernetes.io/name":"gitea"}}}'
|
||||
```
|
||||
|
||||
**Result:** Forgejo now accessible at http://192.168.1.165:3000 ✅
|
||||
|
||||
---
|
||||
|
||||
## 📋 Testing the Fix
|
||||
|
||||
### **For Fresh Cluster Bootstrap:**
|
||||
|
||||
```bash
|
||||
./bootstrap.sh
|
||||
|
||||
# The script now includes:
|
||||
# - Applies 05-wait-for-databases.yaml
|
||||
# - Waits for Job to complete
|
||||
# - Only then deploys Forgejo and other apps
|
||||
```
|
||||
|
||||
### **For Existing Cluster (already migrated):**
|
||||
|
||||
The wait job can be applied retroactively:
|
||||
|
||||
```bash
|
||||
# Apply the wait job (it will complete immediately since databases exist)
|
||||
kubectl apply -f k8s/bootstrap-local/05-wait-for-databases.yaml
|
||||
|
||||
# Check it completes successfully
|
||||
kubectl wait --for=condition=complete --timeout=60s job/wait-for-databases -n ddb
|
||||
|
||||
# Verify all databases are ready
|
||||
kubectl get databases -n ddb
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
NAME AGE CLUSTER PG NAME APPLIED MESSAGE
|
||||
authentik 47h ddb-cluster authentik true
|
||||
forgejo 3d14h ddb-cluster forgejo true
|
||||
temporal 47h ddb-cluster temporal true
|
||||
temporal-visibility 26h ddb-cluster temporal_visibility true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Why Your Insight Was Critical
|
||||
|
||||
Without your catch, the bootstrap would have:
|
||||
1. **Intermittent failures** - sometimes works (if CNPG is fast), sometimes fails (if slow)
|
||||
2. **Poor user experience** - confusing "database does not exist" errors
|
||||
3. **Unreliable automation** - can't script cluster rebuilds confidently
|
||||
|
||||
**The fix ensures:**
|
||||
- ✅ Deterministic bootstrap (always works)
|
||||
- ✅ Clear failure mode (wait job times out if CNPG has issues)
|
||||
- ✅ Proper CNPG usage (Database CRs → actual databases before apps start)
|
||||
|
||||
---
|
||||
|
||||
## 📊 CNPG Workflow (Corrected Understanding)
|
||||
|
||||
### **How CNPG "PostgreSQL-as-a-Service" Works:**
|
||||
|
||||
```
|
||||
Developer/App Team CNPG Operator PostgreSQL Cluster
|
||||
───────────────── ───────────── ──────────────────
|
||||
|
||||
1. Create Database CR
|
||||
│
|
||||
│ apiVersion: postgresql.cnpg.io/v1
|
||||
│ kind: Database
|
||||
│ metadata:
|
||||
│ name: forgejo
|
||||
│ spec:
|
||||
│ name: forgejo
|
||||
│ owner: app
|
||||
│ cluster:
|
||||
│ name: ddb-cluster
|
||||
│
|
||||
└──────────────────────────────────────▶ Watches for CRs
|
||||
│
|
||||
│ Reconcile loop:
|
||||
│ 1. Read Database CR
|
||||
│ 2. Connect to ddb-cluster
|
||||
│ 3. Execute SQL:
|
||||
│ CREATE DATABASE forgejo
|
||||
│ OWNER app;
|
||||
│ 4. Update CR status:
|
||||
│ .status.ready = true
|
||||
│
|
||||
└──────────────────────────▶ Database created!
|
||||
│
|
||||
│ postgres=# \l
|
||||
│ forgejo | app | ...
|
||||
│
|
||||
App connects to database ◀────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### **Before Fix:**
|
||||
```
|
||||
Database CR applied ──▶ CNPG reconciling ──▶ (Forgejo starts too soon!) ──▶ Error: database not found
|
||||
│ │
|
||||
│ (5-30s later) │ (retrying...)
|
||||
│ │
|
||||
└─▶ Database created ──────┘ ──▶ Eventually works
|
||||
```
|
||||
|
||||
### **After Fix:**
|
||||
```
|
||||
Database CR applied ──▶ CNPG reconciling ──▶ wait-for-databases Job polls .status.ready
|
||||
│ │
|
||||
│ │ (waiting...)
|
||||
│ │
|
||||
└─▶ Database created ──────┘ ──▶ Job completes ──▶ Forgejo starts ──▶ ✅ Works immediately!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Current Status
|
||||
|
||||
| Component | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| **CNPG Operator** | ✅ Running | Watching for Database CRs |
|
||||
| **ddb-cluster** | ✅ Healthy | 3/3 instances, 3 replicas |
|
||||
| **Database CRs** | ✅ Applied | forgejo, authentik, temporal, temporal_visibility |
|
||||
| **Databases in PostgreSQL** | ✅ Created | CNPG reconciled all CRs |
|
||||
| **Forgejo** | ✅ Running | Connected to PostgreSQL, accessible |
|
||||
| **Service Endpoints** | ✅ Fixed | Selector now matches pod labels |
|
||||
| **wait-for-databases Job** | ✅ Created | Prevents future race conditions |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Commit the Fixes
|
||||
|
||||
```bash
|
||||
git add k8s/bootstrap-local/05-wait-for-databases.yaml
|
||||
git add k8s/bootstrap-local/kustomization.yaml
|
||||
git add BOOTSTRAP-DEPENDENCY-FIX.md
|
||||
|
||||
git commit -m 'fix(bootstrap): add wait-for-databases job to prevent CNPG race condition
|
||||
|
||||
- Added 05-wait-for-databases.yaml Job to ensure Database CRs are reconciled
|
||||
- Waits for .status.ready=true before Forgejo deployment
|
||||
- Fixes race where Forgejo started before CNPG created databases in PostgreSQL
|
||||
- Ensures proper CNPG "Postgres-as-a-Service" workflow
|
||||
|
||||
Also fixed:
|
||||
- Service selector mismatch (app: forgejo vs app: gitea)
|
||||
- Forgejo now fully accessible at http://192.168.1.165:3000
|
||||
|
||||
Resolves the dependency gap identified in bootstrap flow.'
|
||||
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Thank you for catching this!** Your understanding of CNPG's reconciliation workflow was spot-on. The fix ensures the bootstrap is now rock-solid and follows proper CNPG best practices.
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# ArgoCD Application Consolidation - Implementation Results
|
||||
|
||||
## Summary
|
||||
|
||||
**Before:** 39 Applications
|
||||
**After:** 35 Applications
|
||||
**Removed:** 4 Applications (-10.3%)
|
||||
|
||||
## Implemented Merges
|
||||
|
||||
### ✅ 1. ingress-config ← wildcard-cert + homelab-ingress
|
||||
|
||||
**Before:**
|
||||
- `wildcard-cert` (wave 1): k8s/bootstrap/ingress/riotpiao-com-cert.yaml
|
||||
- `homelab-ingress` (wave 2): k8s/bootstrap/ingress/ingress.yaml
|
||||
|
||||
**After:**
|
||||
- `ingress-config` (wave 1): k8s/bootstrap/ingress/ (kustomization)
|
||||
|
||||
**Changes:**
|
||||
- Updated `k8s/bootstrap/ingress/kustomization.yaml` to include both files
|
||||
- Merged both Applications into single `ingress-config` in `00-substrate.yaml`
|
||||
- Certificate created before Ingresses (wave 1)
|
||||
|
||||
---
|
||||
|
||||
### ✅ 2. homarr ← homarr + homarr-patches
|
||||
|
||||
**Before:**
|
||||
- `homarr` (wave 8): Helm chart + values
|
||||
- `homarr-patches` (wave 9): k8s/applications/homarr/ (PostSync hook)
|
||||
|
||||
**After:**
|
||||
- `homarr` (wave 8): Multi-source (Helm + values + PostSync patches)
|
||||
|
||||
**Changes:**
|
||||
- Added third source to homarr Application
|
||||
- fix-probes-job.yaml already has PostSync hook annotation
|
||||
- Removed homarr-patches Application from `60-applications.yaml`
|
||||
|
||||
---
|
||||
|
||||
### ✅ 3. temporal ← temporal + temporal-db-secret-sync
|
||||
|
||||
**Before:**
|
||||
- `temporal-db-secret-sync` (wave 7): k8s/applications/temporal/db-secret-sync/
|
||||
- `temporal` (wave 8): Helm chart + values
|
||||
|
||||
**After:**
|
||||
- `temporal` (wave 8): Multi-source (Helm + values + PostSync db-secret-sync)
|
||||
|
||||
**Changes:**
|
||||
- Added third source to temporal Application
|
||||
- copy-job.yaml already has PostSync hook annotation
|
||||
- Removed temporal-db-secret-sync Application from `60-applications.yaml`
|
||||
|
||||
---
|
||||
|
||||
### ✅ 4. Removed Duplicate: ingress-nginx
|
||||
|
||||
**Before:**
|
||||
- `ingress-nginx-bootstrap` (bootstrap): Working, has LoadBalancer IP
|
||||
- `ingress-nginx` (ArgoCD): Duplicate, LoadBalancer pending
|
||||
|
||||
**After:**
|
||||
- `ingress-nginx-bootstrap` (bootstrap): Kept
|
||||
|
||||
**Changes:**
|
||||
- Removed ingress-nginx Application from `00-substrate.yaml`
|
||||
- Bootstrap version breaks circular dependency (ArgoCD → Forgejo → Ingress)
|
||||
- Eliminated duplicate DaemonSet
|
||||
|
||||
---
|
||||
|
||||
## Skipped Consolidations
|
||||
|
||||
### ⏭️ cert-manager + cert-manager-issuers
|
||||
|
||||
**Decision:** KEEP SEPARATE
|
||||
|
||||
**Reasoning:**
|
||||
- cert-manager (wave 0) installs CRDs
|
||||
- cert-manager-issuers (wave 1) creates Issuers using those CRDs
|
||||
- Wave separation ensures CRDs exist before Issuers
|
||||
- Merging risks race condition (Issuer created before CRD ready)
|
||||
- This is intentional separation for safety
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. `k8s/bootstrap/ingress/kustomization.yaml` - Added resources list
|
||||
2. `k8s/argocd/apps/00-substrate.yaml` - Merged wildcard-cert + homelab-ingress, removed ingress-nginx
|
||||
3. `k8s/argocd/apps/60-applications.yaml` - Merged homarr + temporal Applications
|
||||
|
||||
---
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
### ✅ Easier Management
|
||||
- 4 fewer Application CRs to track
|
||||
- Related resources grouped together
|
||||
- Clearer ownership model
|
||||
|
||||
### ✅ Better Sync Behavior
|
||||
- PostSync hooks ensure proper ordering
|
||||
- No separate Applications for patches/hooks
|
||||
- Single Application manages entire stack
|
||||
|
||||
### ✅ Cleaner Structure
|
||||
- Multi-source Applications are standard pattern
|
||||
- Each logical service = one Application
|
||||
- Hooks embedded where they belong
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Optional Phase 2)
|
||||
|
||||
### Potential Future Consolidations:
|
||||
|
||||
1. **SQS Platform** (5 → 2 Applications)
|
||||
- Keep `strimzi-operator` separate
|
||||
- Merge: kmsvc-redis + kafka-cluster + queue-crd + management-service
|
||||
|
||||
2. **IAM** (3 → 2 Applications)
|
||||
- Keep `vault` separate
|
||||
- Merge: authentik + iam-jobs
|
||||
|
||||
3. **Monitoring** (3 → 2 Applications)
|
||||
- Keep `prometheus-crds` separate (wave 0)
|
||||
- Merge: prometheus + blackbox-exporter
|
||||
|
||||
**Recommendation:** Evaluate after Forgejo push and observe current consolidations in action.
|
||||
|
||||
---
|
||||
|
||||
## Validation Plan
|
||||
|
||||
After pushing to Forgejo:
|
||||
|
||||
```bash
|
||||
# Check Applications synced successfully
|
||||
kubectl get applications -n argocd
|
||||
|
||||
# Verify consolidated Applications are healthy
|
||||
kubectl get application ingress-config -n argocd
|
||||
kubectl get application homarr -n argocd
|
||||
kubectl get application temporal -n argocd
|
||||
|
||||
# Check ingress-nginx duplicate removed
|
||||
kubectl get application ingress-nginx -n argocd # Should be gone
|
||||
|
||||
# Verify resources deployed correctly
|
||||
kubectl get certificate -n ingress-nginx riotpiao-com-tls
|
||||
kubectl get ingress -A
|
||||
kubectl get job -n dashboard homarr-fix-probes
|
||||
kubectl get job -n temporal temporal-db-secret-sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If issues arise, revert specific commits:
|
||||
|
||||
```bash
|
||||
# Identify commit
|
||||
git log --oneline | grep consolidation
|
||||
|
||||
# Revert specific merge
|
||||
git revert <commit-hash>
|
||||
|
||||
# Or restore old Applications from git history
|
||||
git show <commit>:k8s/argocd/apps/00-substrate.yaml > temp.yaml
|
||||
kubectl apply -f temp.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
Successfully consolidated 4 Applications while maintaining:
|
||||
- ✅ Proper sync wave ordering
|
||||
- ✅ Hook execution timing
|
||||
- ✅ Resource namespace separation
|
||||
- ✅ GitOps best practices
|
||||
|
||||
**Status:** Ready to commit and push to Forgejo for validation.
|
||||
-228
@@ -1,228 +0,0 @@
|
||||
# DDB Cluster Configuration Review
|
||||
|
||||
## Current Configuration
|
||||
|
||||
**File:** `k8s/data/cluster/ddb-cluster.yaml` (bootstrap-only)
|
||||
|
||||
### ✅ Good Practices
|
||||
|
||||
1. **3-replica cluster** - HA across control-plane nodes (az-a, az-b, az-c)
|
||||
2. **PostgreSQL 16.2** - Modern, stable version
|
||||
3. **Managed roles** - Passwords from Secrets (CNPG reconciles automatically)
|
||||
4. **Separate Database CRs** - Each app gets its own database (good separation)
|
||||
5. **Extensions enabled** - vector, pgcrypto, pg_trgm (ready for Authentik/Temporal)
|
||||
6. **Superuser disabled** - Security hardening
|
||||
7. **Longhorn storage** - ⚠️ **STORAGE HA STATUS UNKNOWN!**
|
||||
- CLAUDE.md claims: Single-node storage (cp-1 only) = NO HA ❌
|
||||
- Longhorn manifests show: 3-node config (numberOfReplicas: 3) = HA ✅
|
||||
- **CRITICAL:** Verify actual state before assuming HA
|
||||
- See `STORAGE-ARCHITECTURE-CLARIFICATION.md` for verification commands
|
||||
|
||||
### ⚠️ Recommendations
|
||||
|
||||
#### 1. **Increase shared_buffers for multi-tenant workload**
|
||||
- Current: 256MB
|
||||
- Recommended: 512MB-1GB (with 3 replicas + multiple DBs)
|
||||
- Reason: Forgejo, Authentik, Temporal, Vault all share this cluster
|
||||
|
||||
#### 2. **Enable connection pooling (PgBouncer)**
|
||||
- Add pooler configuration for connection efficiency
|
||||
- Especially important for Temporal (high connection count)
|
||||
|
||||
#### 3. **Configure backups**
|
||||
- No backup configuration present
|
||||
- Add S3/MinIO backup schedule
|
||||
|
||||
#### 4. **Resource limits missing**
|
||||
- Add PostgreSQL pod resource requests/limits
|
||||
|
||||
#### 5. **Monitoring**
|
||||
- `enablePodMonitor: false` - should be `true` for Prometheus scraping
|
||||
- Add backup monitoring alerts
|
||||
|
||||
### 📝 Proposed Enhanced Configuration
|
||||
|
||||
```yaml
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: ddb-cluster
|
||||
namespace: ddb
|
||||
labels:
|
||||
app: postgresql
|
||||
layer: data
|
||||
environment: production
|
||||
spec:
|
||||
instances: 3
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
||||
|
||||
# Resource limits (IMPORTANT for stability)
|
||||
resources:
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: app
|
||||
owner: app
|
||||
encoding: UTF8
|
||||
localeCollate: C
|
||||
localeCType: C
|
||||
postInitApplicationSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS vector;
|
||||
- CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
- CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
- CREATE EXTENSION IF NOT EXISTS btree_gin;
|
||||
- CREATE EXTENSION IF NOT EXISTS btree_gist;
|
||||
|
||||
managed:
|
||||
roles:
|
||||
- name: authentik
|
||||
ensure: present
|
||||
login: true
|
||||
passwordSecret:
|
||||
name: authentik-db-role
|
||||
- name: temporal
|
||||
ensure: present
|
||||
login: true
|
||||
passwordSecret:
|
||||
name: temporal-db-role
|
||||
|
||||
enableSuperuserAccess: false
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
# Memory
|
||||
shared_buffers: "512MB" # Increased from 256MB
|
||||
effective_cache_size: "1536MB" # ~75% of memory limit
|
||||
work_mem: "16MB" # Per-operation memory
|
||||
maintenance_work_mem: "128MB" # For VACUUM/CREATE INDEX
|
||||
|
||||
# Parallelism
|
||||
max_parallel_workers: "4"
|
||||
max_parallel_workers_per_gather: "2"
|
||||
max_worker_processes: "8"
|
||||
|
||||
# Connection pooling (PgBouncer will handle this, but set reasonable limits)
|
||||
max_connections: "100"
|
||||
|
||||
# WAL & Checkpoints
|
||||
archive_mode: "on"
|
||||
archive_timeout: "5min"
|
||||
wal_level: "replica"
|
||||
max_wal_size: "1GB"
|
||||
min_wal_size: "256MB"
|
||||
checkpoint_completion_target: "0.9"
|
||||
|
||||
# Logging
|
||||
log_destination: "csvlog"
|
||||
log_directory: "/controller/log"
|
||||
log_filename: "postgres"
|
||||
log_rotation_age: "0"
|
||||
log_min_duration_statement: "1000" # Log slow queries (>1s)
|
||||
log_line_prefix: "%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h "
|
||||
|
||||
# Performance
|
||||
random_page_cost: "1.1" # SSD-optimized
|
||||
effective_io_concurrency: "200" # SSD concurrent I/O
|
||||
dynamic_shared_memory_type: "posix"
|
||||
|
||||
storage:
|
||||
size: 20Gi # Increased from 10Gi
|
||||
storageClass: longhorn
|
||||
|
||||
# PgBouncer connection pooler
|
||||
pooler:
|
||||
enabled: true
|
||||
poolMode: transaction
|
||||
instances: 2
|
||||
parameters:
|
||||
max_client_conn: "1000"
|
||||
default_pool_size: "25"
|
||||
max_db_connections: "90"
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
|
||||
# Monitoring (ENABLE for Prometheus)
|
||||
monitoring:
|
||||
enablePodMonitor: true # Changed from false
|
||||
disableDefaultQueries: false
|
||||
customQueriesConfigMap:
|
||||
- name: cnpg-default-monitoring
|
||||
key: queries
|
||||
|
||||
# Backups to MinIO
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: s3://ddb-backups/
|
||||
endpointURL: http://minio.storage.svc:9000
|
||||
s3Credentials:
|
||||
accessKeyId:
|
||||
name: ddb-backup-s3
|
||||
key: ACCESS_KEY_ID
|
||||
secretAccessKey:
|
||||
name: ddb-backup-s3
|
||||
key: SECRET_ACCESS_KEY
|
||||
wal:
|
||||
compression: gzip
|
||||
maxParallel: 2
|
||||
retentionPolicy: "30d"
|
||||
|
||||
affinity:
|
||||
podAntiAffinityType: preferred
|
||||
|
||||
# Node affinity (prefer spreading across zones)
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
preference:
|
||||
matchExpressions:
|
||||
- key: topology.kubernetes.io/zone
|
||||
operator: In
|
||||
values:
|
||||
- az-a
|
||||
- az-b
|
||||
- az-c
|
||||
```
|
||||
|
||||
### 🔐 Required Secrets
|
||||
|
||||
Create these secrets before applying enhanced config:
|
||||
|
||||
```bash
|
||||
# 1. Backup credentials (create MinIO bucket + user first)
|
||||
kubectl create secret generic ddb-backup-s3 -n ddb \
|
||||
--from-literal=ACCESS_KEY_ID="<minio-access-key>" \
|
||||
--from-literal=SECRET_ACCESS_KEY="<minio-secret-key>"
|
||||
```
|
||||
|
||||
### 📊 Connection Architecture
|
||||
|
||||
```
|
||||
Application Pods
|
||||
↓
|
||||
PgBouncer Pooler (transaction mode, 2 replicas)
|
||||
↓
|
||||
ddb-cluster-rw.ddb.svc (read-write service)
|
||||
↓
|
||||
Primary PostgreSQL Pod
|
||||
↓
|
||||
Replica Pods (async replication)
|
||||
```
|
||||
|
||||
### 🎯 Migration Path
|
||||
|
||||
1. **Current state:** Bootstrap-only (manual)
|
||||
2. **Proposed:** Still bootstrap-only (circular dependency with Forgejo)
|
||||
3. **Future consideration:** If Forgejo is decoupled from this cluster (e.g., external git host), DDB could move to full GitOps
|
||||
|
||||
@@ -1,405 +0,0 @@
|
||||
# GitOps Rebuild Implementation Checklist
|
||||
|
||||
Use this checklist to track the rebuild implementation step-by-step.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Pre-Implementation
|
||||
|
||||
- [ ] **Backup current state**
|
||||
```bash
|
||||
kubectl get applications -n argocd -o yaml > backup-argocd-apps.yaml
|
||||
kubectl get cluster ddb-cluster -n ddb -o yaml > backup-ddb-cluster.yaml
|
||||
kubectl get all -n cicd -o yaml > backup-forgejo.yaml
|
||||
kubectl get all -n ddb -o yaml > backup-ddb.yaml
|
||||
```
|
||||
|
||||
- [ ] **Verify prerequisites**
|
||||
- [ ] kubectl configured (`kubectl cluster-info`)
|
||||
- [ ] SOPS age key exists (`~/.sops/homelab-age.key`)
|
||||
- [ ] ArgoCD CLI installed (`argocd version`)
|
||||
- [ ] Git configured with Forgejo credentials
|
||||
|
||||
- [ ] **⚠️ CRITICAL: Verify storage replication status**
|
||||
```bash
|
||||
# Check Longhorn nodes (should show 3 if HA is active)
|
||||
kubectl get nodes.longhorn.io -n longhorn-system
|
||||
|
||||
# Check replica counts
|
||||
kubectl get volumes.longhorn.io -n longhorn-system \
|
||||
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.numberOfReplicas}{"\n"}{end}'
|
||||
```
|
||||
- [ ] If single-node (1 replica): Deploy `k8s/infrastructure/longhorn/` first
|
||||
- [ ] If 3-node (3 replicas): Update CLAUDE.md to reflect HA status
|
||||
- [ ] See `STORAGE-ARCHITECTURE-CLARIFICATION.md` for details
|
||||
|
||||
- [ ] **Review documentation**
|
||||
- [ ] Read `GITOPS-REBUILD-PLAN.md` completely
|
||||
- [ ] Review `DDB-REVIEW.md` for database configuration
|
||||
- [ ] Understand wave structure (0-8)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Implementation (Choose One Path)
|
||||
|
||||
### **Option A: Fresh Cluster (Recommended)**
|
||||
|
||||
- [ ] **Provision Talos cluster**
|
||||
```bash
|
||||
cd terraform/
|
||||
terraform apply
|
||||
# Wait for cluster to be ready
|
||||
```
|
||||
|
||||
- [ ] **Run bootstrap script**
|
||||
```bash
|
||||
./bootstrap.sh
|
||||
# Verify all steps complete successfully
|
||||
```
|
||||
|
||||
- [ ] **Push to Forgejo**
|
||||
```bash
|
||||
git remote add forgejo https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
git push forgejo main
|
||||
```
|
||||
|
||||
- [ ] **Deploy app-of-apps**
|
||||
```bash
|
||||
kubectl apply -f k8s/argocd/projects/homelab-project.yaml
|
||||
kubectl apply -k k8s/argocd/root
|
||||
argocd app sync homelab-root --prune
|
||||
```
|
||||
|
||||
### **Option B: Incremental Migration (Existing Cluster)**
|
||||
|
||||
- [ ] **Apply bootstrap-local resources alongside existing**
|
||||
```bash
|
||||
# Dry-run first
|
||||
kubectl apply -k k8s/bootstrap-local/ --dry-run=client
|
||||
|
||||
# Actually apply (creates bootstrap Applications)
|
||||
kubectl apply -k k8s/bootstrap-local/
|
||||
```
|
||||
|
||||
- [ ] **Update ArgoCD apps one wave at a time**
|
||||
```bash
|
||||
# Wave 0
|
||||
kubectl apply -f k8s/argocd/apps/00-substrate.yaml
|
||||
argocd app sync cert-manager ingress-nginx reloader
|
||||
|
||||
# Verify healthy, then proceed to wave 1, 2, 3, etc.
|
||||
```
|
||||
|
||||
- [ ] **Update data-schemas path**
|
||||
```bash
|
||||
# Edit 40-data.yaml (already done in this plan)
|
||||
kubectl apply -f k8s/argocd/apps/40-data.yaml
|
||||
argocd app sync data-schemas
|
||||
```
|
||||
|
||||
- [ ] **Delete old bootstrap Applications**
|
||||
```bash
|
||||
# These are now in bootstrap-local/
|
||||
kubectl delete application cnpg-operator -n argocd
|
||||
# Note: Keep forgejo as manual-sync-only
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Post-Implementation Verification
|
||||
|
||||
### **Wave 0-2: Infrastructure**
|
||||
|
||||
- [ ] **cert-manager**
|
||||
```bash
|
||||
argocd app get cert-manager
|
||||
kubectl get clusterissuers -A
|
||||
# Verify: letsencrypt-staging, letsencrypt-prod
|
||||
```
|
||||
|
||||
- [ ] **ingress-nginx**
|
||||
```bash
|
||||
kubectl get pods -n ingress-nginx
|
||||
kubectl get svc ingress-nginx-controller -n ingress-nginx
|
||||
# Verify: LoadBalancer IP assigned (192.168.1.160)
|
||||
```
|
||||
|
||||
- [ ] **Prometheus**
|
||||
```bash
|
||||
kubectl get pods -n monitoring
|
||||
kubectl get servicemonitors -A
|
||||
# Verify: prometheus, grafana, alertmanager running
|
||||
```
|
||||
|
||||
### **Wave 3-5: Logging, Secrets, IAM**
|
||||
|
||||
- [ ] **Loki/Grafana**
|
||||
```bash
|
||||
kubectl get pods -n logging
|
||||
# Access: https://grafana.riotpiao.com
|
||||
```
|
||||
|
||||
- [ ] **SOPS secrets**
|
||||
```bash
|
||||
argocd app get sops-secrets
|
||||
kubectl get secrets -n ddb | grep db-role
|
||||
# Verify: authentik-db-role, temporal-db-role exist
|
||||
```
|
||||
|
||||
- [ ] **Vault**
|
||||
```bash
|
||||
kubectl get pods -n iam
|
||||
kubectl exec -n iam vault-0 -- vault status
|
||||
```
|
||||
|
||||
- [ ] **Authentik**
|
||||
```bash
|
||||
kubectl get pods -n iam
|
||||
# Access: https://authentik.riotpiao.com
|
||||
```
|
||||
|
||||
### **Wave 6-8: Data, Messaging, Applications**
|
||||
|
||||
- [ ] **Data schemas**
|
||||
```bash
|
||||
kubectl get databases -n ddb
|
||||
# Verify: forgejo, authentik, temporal, temporal_visibility
|
||||
kubectl get jobs -n ddb
|
||||
# Verify: db-init-job Completed
|
||||
```
|
||||
|
||||
- [ ] **Kafka/SQS**
|
||||
```bash
|
||||
kubectl get pods -n sqs
|
||||
kubectl get kafkas -n sqs
|
||||
```
|
||||
|
||||
- [ ] **Temporal**
|
||||
```bash
|
||||
kubectl get pods -n temporal
|
||||
kubectl logs -n temporal deployment/temporal-frontend -f
|
||||
# Verify: Connected to PostgreSQL
|
||||
# Access: https://temporal.riotpiao.com
|
||||
```
|
||||
|
||||
- [ ] **Cloudflared**
|
||||
```bash
|
||||
kubectl get pods -n cloudflared
|
||||
kubectl logs -n cloudflared deployment/cloudflared
|
||||
# Verify: Tunnel connected
|
||||
```
|
||||
|
||||
### **Overall Health**
|
||||
|
||||
- [ ] **All Applications Synced**
|
||||
```bash
|
||||
argocd app list
|
||||
# Verify: All STATUS=Synced, HEALTH=Healthy
|
||||
```
|
||||
|
||||
- [ ] **No stuck pods**
|
||||
```bash
|
||||
kubectl get pods --all-namespaces | grep -vE 'Running|Completed'
|
||||
# (Should be empty)
|
||||
```
|
||||
|
||||
- [ ] **All Ingresses accessible**
|
||||
```bash
|
||||
kubectl get ingress -A
|
||||
# Test each URL in browser
|
||||
```
|
||||
|
||||
- [ ] **PostgreSQL connections**
|
||||
```bash
|
||||
# Forgejo
|
||||
kubectl exec -n cicd deployment/forgejo -- psql -h ddb-cluster-rw.ddb.svc -U app -d forgejo -c '\conninfo'
|
||||
|
||||
# Authentik
|
||||
kubectl exec -n iam deployment/authentik-server -- python manage.py check --database default
|
||||
|
||||
# Temporal
|
||||
kubectl exec -n temporal deployment/temporal-frontend -- tctl --db_engine postgres cluster health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧹 Cleanup (After Successful Migration)
|
||||
|
||||
- [ ] **Remove old bootstrap files (optional)**
|
||||
```bash
|
||||
# Move to archive/
|
||||
mkdir -p archive/
|
||||
mv k8s/argocd/bootstrap/ archive/old-bootstrap/
|
||||
mv USAGE.md project-usage/ archive/old-helmfile-docs/
|
||||
```
|
||||
|
||||
- [ ] **Update CLAUDE.md**
|
||||
```bash
|
||||
# Remove references to Phase 0 manual steps
|
||||
# Update to point to GITOPS-REBUILD-PLAN.md
|
||||
```
|
||||
|
||||
- [ ] **Commit cleanup**
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: migrate to bootstrap-local + GitOps structure"
|
||||
git push forgejo main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Day-2 Validation
|
||||
|
||||
- [ ] **Test GitOps workflow**
|
||||
```bash
|
||||
# Make a simple change
|
||||
echo "# Test comment" >> k8s/applications/temporal/temporal-values.yaml
|
||||
git commit -am "test: validate GitOps workflow"
|
||||
git push
|
||||
|
||||
# Watch ArgoCD auto-sync
|
||||
watch -n 2 'argocd app get temporal | grep -A 3 "Sync Status"'
|
||||
```
|
||||
|
||||
- [ ] **Test rollback**
|
||||
```bash
|
||||
git revert HEAD
|
||||
git push
|
||||
# Verify ArgoCD auto-syncs the rollback
|
||||
```
|
||||
|
||||
- [ ] **Test adding new application**
|
||||
```bash
|
||||
# Create minimal app
|
||||
mkdir k8s/applications/test-app
|
||||
cat > k8s/applications/test-app/deployment.yaml << 'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-test
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx-test
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx-test
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
EOF
|
||||
|
||||
# Add to ArgoCD
|
||||
cat >> k8s/argocd/apps/08-applications.yaml << 'EOF'
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: test-app
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/test-app
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
EOF
|
||||
|
||||
# Push and verify
|
||||
git add -A && git commit -m "test: add test application" && git push
|
||||
argocd app get test-app
|
||||
kubectl get deployment nginx-test
|
||||
|
||||
# Cleanup
|
||||
kubectl delete application test-app -n argocd
|
||||
git revert HEAD && git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring & Alerts
|
||||
|
||||
- [ ] **Configure Prometheus alerts**
|
||||
- [ ] ArgoCD sync failures
|
||||
- [ ] PostgreSQL replication lag
|
||||
- [ ] PVC usage >80%
|
||||
|
||||
- [ ] **Set up Grafana dashboards**
|
||||
- [ ] ArgoCD overview
|
||||
- [ ] PostgreSQL performance
|
||||
- [ ] Ingress traffic
|
||||
|
||||
- [ ] **Document runbooks**
|
||||
- [ ] DDB cluster recovery (CLAUDE.md)
|
||||
- [ ] Forgejo outage (breaks GitOps)
|
||||
- [ ] ArgoCD degradation
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
- [x] **Bootstrap time:** <10 minutes
|
||||
- [x] **Zero manual kubectl apply** (except bootstrap.sh)
|
||||
- [x] **No duplicate resources**
|
||||
- [x] **All apps Synced + Healthy**
|
||||
- [x] **Git is single source of truth**
|
||||
- [x] **Rollbacks via git revert only**
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Rollback Plan (If Things Go Wrong)
|
||||
|
||||
### **Rollback to Old State**
|
||||
|
||||
```bash
|
||||
# 1. Restore ArgoCD Applications
|
||||
kubectl apply -f backup-argocd-apps.yaml
|
||||
|
||||
# 2. Restore DDB cluster (if modified)
|
||||
kubectl apply -f backup-ddb-cluster.yaml
|
||||
|
||||
# 3. Restore Forgejo
|
||||
kubectl apply -f backup-forgejo.yaml
|
||||
|
||||
# 4. Sync all apps to last known good commit
|
||||
argocd app sync --all --revision <last-good-commit-sha>
|
||||
```
|
||||
|
||||
### **Nuclear Option (Full Cluster Rebuild)**
|
||||
|
||||
```bash
|
||||
# 1. Export all PVCs data (Forgejo git repos, PostgreSQL data)
|
||||
# (Manual backup via Longhorn UI or velero)
|
||||
|
||||
# 2. Destroy cluster
|
||||
cd terraform/
|
||||
terraform destroy
|
||||
|
||||
# 3. Re-provision from scratch
|
||||
terraform apply
|
||||
./bootstrap.sh
|
||||
# Restore PVC data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Notes:**
|
||||
- Check off items as you complete them
|
||||
- Add timestamps/notes for each major step
|
||||
- Keep this checklist updated as you encounter issues
|
||||
|
||||
@@ -1,420 +0,0 @@
|
||||
# Homelab Cluster Remediation Runbook — Parallel Execution
|
||||
|
||||
Board: **24/32 green**. Goal: all ArgoCD apps `Synced/Healthy`, no non-Running pods.
|
||||
|
||||
## Parallel task groups (run agents in parallel within groups; groups are sequential)
|
||||
|
||||
```
|
||||
GROUP 1 (independent, run all 3 in parallel)
|
||||
├─ TASK 1a — temporal: deploy Cassandra + Elasticsearch [**blocks TASK 2 (homelab-ingress)**]
|
||||
├─ TASK 2a — queue-operator: add RBAC + TemporalWorker CRD
|
||||
└─ TASK 3a — minio console: fix image tag
|
||||
|
||||
GROUP 2 (wait for GROUP 1 complete)
|
||||
├─ TASK 4a — prometheus / sops-secrets: resolve OutOfSync
|
||||
└─ TASK 5a — vault: one-time init + unseal
|
||||
|
||||
GROUP 3 (after temporal ns exists)
|
||||
└─ TASK 6a — homelab-ingress: should auto-sync once temporal ns created
|
||||
```
|
||||
|
||||
**Cloudflare** — deferred (not blocking cluster health).
|
||||
|
||||
---
|
||||
|
||||
## Cluster facts
|
||||
- 3 Talos nodes: cp-1 (192.168.1.213), cp-2 (192.168.1.163), cp-3 (192.168.1.166) — all Ready.
|
||||
- GitOps repo: `~/workplace/homelab`, branch `main`.
|
||||
- **GitOps mandatory**: repo → commit → push → ArgoCD syncs. No hand-edits except vault init.
|
||||
- Ingress LB: 192.168.1.160, wildcard TLS on prod (already fixed).
|
||||
|
||||
## Status baseline (run before + after each group)
|
||||
```bash
|
||||
kubectl get applications -A --no-headers | awk '{printf "%-24s %-12s %s\n",$2,$3,$4}'
|
||||
kubectl get pods -A | grep -ivE 'Running|Completed'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GROUP 1 — parallel tasks (all 3 agents)
|
||||
|
||||
### TASK 1a — temporal: deploy Cassandra + Elasticsearch (replaces PostgreSQL)
|
||||
|
||||
**Symptom**: `temporal` app sync=Unknown, helm render error on removed `cassandra:` top-level key.
|
||||
|
||||
**Decision**: Deploy Cassandra + Elasticsearch as sub-charts. Temporal chart supports both as embedded dependencies.
|
||||
|
||||
#### Steps
|
||||
1. **Pin chart version** — edit `k8s/argocd/apps/60-applications.yaml`:
|
||||
```bash
|
||||
grep -n "targetRevision" k8s/argocd/apps/60-applications.yaml | grep -A2 -B2 temporal
|
||||
```
|
||||
Change `targetRevision: "*"` → `targetRevision: "0.64.0"` (confirmed to have cassandra/elasticsearch sub-charts).
|
||||
|
||||
2. **Rewrite temporal-values.yaml** — remove top-level `cassandra:`, enable cassandra + elasticsearch sub-charts:
|
||||
```bash
|
||||
sed -n '1,50p' k8s/applications/temporal/temporal-values.yaml
|
||||
```
|
||||
Rewrite to:
|
||||
```yaml
|
||||
# Temporal with Cassandra (default store) + Elasticsearch (visibility)
|
||||
|
||||
cassandra:
|
||||
enabled: true
|
||||
config:
|
||||
cluster_name: temporal
|
||||
num_tokens: 256
|
||||
seed_provider:
|
||||
class_name: org.apache.cassandra.locator.SimpleSeedProvider
|
||||
parameters:
|
||||
seeds: "127.0.0.1"
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
|
||||
elasticsearch:
|
||||
enabled: true
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 10Gi
|
||||
|
||||
postgresql:
|
||||
enabled: false # disable embedded postgres
|
||||
|
||||
server:
|
||||
config:
|
||||
persistence:
|
||||
defaultStore: default
|
||||
additionalStores: {}
|
||||
datastores:
|
||||
default:
|
||||
driver: cassandra
|
||||
cassandra:
|
||||
hosts:
|
||||
- cassandra
|
||||
port: 9042
|
||||
keyspace: temporal
|
||||
user: ""
|
||||
password: ""
|
||||
maxConnsPerHost: 32
|
||||
consistency: LOCAL_QUORUM
|
||||
visibility:
|
||||
driver: elasticsearch
|
||||
elasticsearch:
|
||||
scheme: http
|
||||
host: elasticsearch
|
||||
port: 9200
|
||||
|
||||
# Keep remaining server config (logging, etc.)
|
||||
# ... (existing server.lifecycleHooks, server.replicaCount, etc.)
|
||||
```
|
||||
|
||||
Key differences:
|
||||
- Top-level `cassandra.enabled: true` (sub-chart, not deprecated key).
|
||||
- `elasticsearch.enabled: true` for visibility store.
|
||||
- `server.config.persistence.datastores.default.driver: cassandra` (not SQL).
|
||||
- `server.config.persistence.datastores.visibility.driver: elasticsearch`.
|
||||
- No CNPG references — Cassandra/ES managed by Helm.
|
||||
|
||||
3. **Validate helm render locally**:
|
||||
```bash
|
||||
helm template temporal temporal --repo https://go.temporal.io/helm-charts \
|
||||
--version 0.64.0 -n temporal \
|
||||
-f k8s/applications/temporal/temporal-values.yaml --include-crds 2>&1 | grep -iE "error|^kind:" | head
|
||||
# expect: kind lines (StatefulSet, Deployment, etc.), no errors
|
||||
```
|
||||
|
||||
4. **Commit + push**:
|
||||
```bash
|
||||
git add k8s/applications/temporal/temporal-values.yaml k8s/argocd/apps/60-applications.yaml
|
||||
git commit -m "fix(temporal): deploy Cassandra + Elasticsearch sub-charts, remove PostgreSQL, pin chart v0.64.0"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
5. **Trigger sync** (ArgoCD may auto-sync; force if needed):
|
||||
```bash
|
||||
kubectl -n argocd annotate application temporal argocd.argoproj.io/refresh=hard --overwrite
|
||||
```
|
||||
|
||||
#### Verify
|
||||
```bash
|
||||
kubectl get ns temporal # namespace exists
|
||||
kubectl -n temporal get pods | head # server/cassandra/elasticsearch pods Running
|
||||
kubectl -n argocd get application temporal -o jsonpath='{.status.sync.status} {.status.health.status}{"\n"}' # Synced Healthy
|
||||
```
|
||||
|
||||
#### Troubleshoot
|
||||
- Still `Unknown` / render error → confirm cassandra/elasticsearch keys are nested under top-level, not at column-0. Check Chart.yaml: `dependencies: [{name: cassandra}, {name: elasticsearch}]`.
|
||||
- Cassandra pod Pending → PVC not bound. Check: `kubectl -n temporal get pvc`. If no PVC, storage class missing — verify `kubectl get storageclass`.
|
||||
- Elasticsearch OOMKilled → bump resource limits in values.
|
||||
- Schema job pending → cassandra not ready. Wait: `kubectl -n temporal logs job/temporal-schema-setup --tail=20`.
|
||||
|
||||
---
|
||||
|
||||
### TASK 2a — queue-operator: add RBAC + TemporalWorker CRD (parallel)
|
||||
|
||||
**Symptom**: operator crashloop, logs show `forbidden: cannot list deployments` + `no matches for kind TemporalWorker`.
|
||||
|
||||
**Cause**: ClusterRole missing `deployments.apps` permission; TemporalWorker CRD not installed.
|
||||
|
||||
#### Steps
|
||||
1. **Edit RBAC** — `k8s/applications/sqs/charts/queue-crd/templates/rbac.yaml`, add to ClusterRole rules:
|
||||
```yaml
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
```
|
||||
(Full file already has rules for queues, leases, events, pods, nodes; append the above.)
|
||||
|
||||
2. **Install TemporalWorker CRD** — kmsvc-managed. Fetch from kmsvc source (or import from your internal docs):
|
||||
Create `k8s/applications/sqs/charts/queue-crd/templates/temporalworker-crd.yaml`:
|
||||
```yaml
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: temporalworkers.kmsvc.io
|
||||
spec:
|
||||
group: kmsvc.io
|
||||
names:
|
||||
kind: TemporalWorker
|
||||
plural: temporalworkers
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
workflowType:
|
||||
type: string
|
||||
concurrency:
|
||||
type: integer
|
||||
status:
|
||||
type: object
|
||||
```
|
||||
(Adjust schema per kmsvc spec; this is minimal template.)
|
||||
|
||||
3. **Commit + push**:
|
||||
```bash
|
||||
git add k8s/applications/sqs/charts/queue-crd/templates/rbac.yaml \
|
||||
k8s/applications/sqs/charts/queue-crd/templates/temporalworker-crd.yaml
|
||||
git commit -m "fix(sqs): grant queue-operator deployments RBAC, install TemporalWorker CRD"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
4. **Restart operator**:
|
||||
```bash
|
||||
kubectl -n sqs rollout restart deploy/queue-operator
|
||||
```
|
||||
|
||||
#### Verify
|
||||
```bash
|
||||
kubectl get crd | grep -iE 'queues.kmsvc|temporalworkers.kmsvc' # both exist
|
||||
kubectl -n sqs logs deploy/queue-operator --tail=5 | grep -iE 'error|forbidden' || echo CLEAN
|
||||
kubectl -n sqs get pods -l app=queue-operator # Running, restarts stable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TASK 3a — minio console: fix image tag (parallel)
|
||||
|
||||
**Symptom**: ns `storage`, pod `console-*` stuck ImagePullBackOff, image `minio/console:v0.30.0` doesn't exist.
|
||||
|
||||
**Fix**: remove the standalone console deploy (deprecated); MinIO tenant has built-in console.
|
||||
|
||||
#### Steps
|
||||
1. **Find console reference**:
|
||||
```bash
|
||||
grep -rn "v0.30.0\|console:" k8s/infrastructure/minio k8s/applications/minio* 2>/dev/null | head
|
||||
```
|
||||
|
||||
2. **Remove from kustomization/values**:
|
||||
```bash
|
||||
grep -n "console\|apiVersion:" k8s/infrastructure/minio/kustomization.yaml
|
||||
```
|
||||
Delete lines referencing `console` deployment/service. Keep operator + tenant.
|
||||
|
||||
3. **Commit + push**:
|
||||
```bash
|
||||
git add k8s/infrastructure/minio/
|
||||
git commit -m "fix(minio): remove deprecated standalone console (use tenant built-in)"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
#### Verify
|
||||
```bash
|
||||
kubectl -n storage get pods | grep -i console || echo "console removed"
|
||||
kubectl -n argocd get application minio-operator -o jsonpath='{.status.health.status}{"\n"}' # Healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wait for GROUP 1 complete (all 3 tasks done)
|
||||
|
||||
Check:
|
||||
```bash
|
||||
kubectl -n argocd get app temporal queue-crd minio-operator -o jsonpath='{range .items[*]}{.metadata.name}{": "}{.status.sync.status}{" "}{.status.health.status}{"\n"}{end}'
|
||||
# want all: Synced Healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GROUP 2 — sequential tasks (after GROUP 1 green)
|
||||
|
||||
### TASK 4a — prometheus + sops-secrets OutOfSync (drift resolution)
|
||||
|
||||
**Symptom**: both apps Healthy but OutOfSync (CRD annotation drift).
|
||||
|
||||
#### Steps
|
||||
1. **Inspect diff**:
|
||||
```bash
|
||||
kubectl -n argocd get application prometheus -o json | jq -r '.status.conditions[]?.message' | head
|
||||
```
|
||||
|
||||
2. **Enable server-side apply** — edit the Application defining prometheus in `k8s/argocd/apps/`:
|
||||
```bash
|
||||
grep -n "prometheus" k8s/argocd/apps/*.yaml | grep -i "name:"
|
||||
```
|
||||
Add to `syncPolicy.syncOptions`:
|
||||
```yaml
|
||||
syncOptions:
|
||||
- ServerSideApply=true
|
||||
```
|
||||
|
||||
3. **For sops-secrets**, check ownership:
|
||||
```bash
|
||||
kubectl -n argocd get application sops-secrets -o json | jq '.spec.source'
|
||||
```
|
||||
If it's in homelab-root as nested app, ensure no dual-ownership (no other ArgoCD app managing the same Secret). If conflict, update the parent app path.
|
||||
|
||||
4. **Commit + push**:
|
||||
```bash
|
||||
git add k8s/argocd/apps/
|
||||
git commit -m "fix(argocd): enable server-side apply for prometheus CRD, reconcile sops-secrets ownership"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
5. **Refresh**:
|
||||
```bash
|
||||
kubectl -n argocd annotate application prometheus argocd.argoproj.io/refresh=hard --overwrite
|
||||
kubectl -n argocd annotate application sops-secrets argocd.argoproj.io/refresh=hard --overwrite
|
||||
```
|
||||
|
||||
#### Verify
|
||||
```bash
|
||||
kubectl -n argocd get app prometheus sops-secrets -o jsonpath='{range .items[*]}{.metadata.name}{": "}{.status.sync.status}{"\n"}{end}'
|
||||
# Synced Synced
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### TASK 5a — vault: one-time init + unseal (manual)
|
||||
|
||||
**Symptom**: vault-0 CreateContainerConfigError, needs secret `vault-unseal-keys`.
|
||||
|
||||
**Security**: store unseal keys + root token in your offline password manager. Never commit plaintext to git.
|
||||
|
||||
#### Steps
|
||||
1. **Create placeholder secret** (to let pod start):
|
||||
```bash
|
||||
kubectl -n iam create secret generic vault-unseal-keys \
|
||||
--from-literal=key1=placeholder --from-literal=key2=placeholder --from-literal=key3=placeholder
|
||||
kubectl -n iam rollout status sts/vault --timeout=2m
|
||||
```
|
||||
|
||||
2. **Init** (generates real keys + root token — SAVE THESE):
|
||||
```bash
|
||||
kubectl -n iam exec -it vault-0 -- vault operator init -key-shares=3 -key-threshold=3
|
||||
# Output:
|
||||
# Unseal Key 1: ...
|
||||
# Unseal Key 2: ...
|
||||
# Unseal Key 3: ...
|
||||
# Initial Root Token: ...
|
||||
```
|
||||
**STORE SECURELY** in password manager / encrypted file. Do NOT paste into terminals or commit.
|
||||
|
||||
3. **Unseal** (run all 3 keys):
|
||||
```bash
|
||||
key1="<paste from password manager>"
|
||||
key2="<paste from password manager>"
|
||||
key3="<paste from password manager>"
|
||||
|
||||
kubectl -n iam exec -it vault-0 -- vault operator unseal $key1
|
||||
kubectl -n iam exec -it vault-0 -- vault operator unseal $key2
|
||||
kubectl -n iam exec -it vault-0 -- vault operator unseal $key3
|
||||
```
|
||||
|
||||
4. **Update secret with real keys** (via SOPS for GitOps, or direct kubectl):
|
||||
```bash
|
||||
# Option A: direct (fast for now, not GitOps; set up SOPS encrypt later)
|
||||
kubectl -n iam create secret generic vault-unseal-keys \
|
||||
--from-literal=key1="$key1" --from-literal=key2="$key2" --from-literal=key3="$key3" \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
**TODO later**: encrypt this secret via SOPS and commit to repo so re-provision has keys.
|
||||
|
||||
5. **Restart pod** to pick up real keys (reloader watches secret):
|
||||
```bash
|
||||
kubectl -n iam rollout restart sts/vault
|
||||
kubectl -n iam rollout status sts/vault
|
||||
```
|
||||
|
||||
#### Verify
|
||||
```bash
|
||||
kubectl -n iam get pod vault-0 # 1/1 Running
|
||||
kubectl -n iam exec -it vault-0 -- vault status # Initialized true, Sealed false
|
||||
kubectl -n argocd get application vault -o jsonpath='{.status.health.status}{"\n"}' # Healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final check (all apps green)
|
||||
|
||||
```bash
|
||||
kubectl get applications -A --no-headers | awk '{print $3,$4}' | sort | uniq -c
|
||||
# expect: 32 "Synced Healthy"
|
||||
|
||||
kubectl get pods -A | grep -ivE 'Running|Completed'
|
||||
# expect: no output (or only Completed jobs)
|
||||
|
||||
kubectl get nodes
|
||||
# expect: 3 Ready
|
||||
|
||||
# Cluster ready for next steps
|
||||
echo "✓ Cluster healthy"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback (per-task)
|
||||
Each task has a `git revert --no-edit HEAD && git push origin main` — run that commit hash if needed.
|
||||
|
||||
---
|
||||
|
||||
## Notes for agents
|
||||
|
||||
- **All repo changes use GitOps**: edit files, commit, push `main`, ArgoCD auto-syncs (within 3–5 min, or force with annotate).
|
||||
- **Parallel GROUP 1**: tasks 1a, 2a, 3a have no interdependencies — agents can work simultaneously.
|
||||
- **Sequential GROUP 2**: wait for GROUP 1 all-green before starting 4a/5a.
|
||||
- **Vault (5a)** is the only manual step — unseal keys must be handled securely offline, not in terminal history.
|
||||
- **Post-remediation**: cluster is health-ready for applications/workloads; Cloudflare tunneling deferred.
|
||||
@@ -1,121 +0,0 @@
|
||||
# Storage Migration Analysis - storageClassName Update
|
||||
|
||||
## ❌ Problem: Field is Immutable
|
||||
|
||||
Kubernetes does not allow changing `spec.storageClassName` on existing PVCs.
|
||||
|
||||
```
|
||||
Error: spec is immutable after creation except resources.requests
|
||||
and volumeAttributesClassName for bound claims
|
||||
```
|
||||
|
||||
## 📊 Current Situation
|
||||
|
||||
**PVCs using old StorageClass names (7 total):**
|
||||
|
||||
| Namespace | PVC | Old StorageClass | Volume | Replicas | Status |
|
||||
|-----------|-----|------------------|--------|----------|--------|
|
||||
| dashboard | portainer | longhorn-wffc | pvc-cb87... | 3 | ✅ Working |
|
||||
| monitoring | prometheus-...| longhorn-wffc | pvc-03237... | 3 | ✅ Working |
|
||||
| sqs | data-kmsvc-pool-0 | longhorn-kafka | pvc-0362a... | 3 | ✅ Working |
|
||||
| sqs | data-kmsvc-pool-1 | longhorn-kafka | pvc-55190... | 3 | ✅ Working |
|
||||
| sqs | data-kmsvc-pool-2 | longhorn-kafka | pvc-79f42... | 3 | ✅ Working |
|
||||
| sqs | redis-replicas-1 | longhorn-wffc | pvc-a4625... | 3 | ✅ Working |
|
||||
| sqs | redis-replicas-2 | longhorn-wffc | pvc-e330f... | 3 | ✅ Working |
|
||||
|
||||
**All volumes have 3 replicas and work perfectly.**
|
||||
|
||||
## 🤔 Is Migration Necessary?
|
||||
|
||||
**Functional Impact:** NONE
|
||||
- ✅ All PVCs are Bound
|
||||
- ✅ All volumes have 3 replicas
|
||||
- ✅ Applications work normally
|
||||
- ✅ New PVCs will use unified `longhorn` StorageClass automatically
|
||||
|
||||
**Cosmetic Issue Only:**
|
||||
- PVC metadata shows old StorageClass name
|
||||
- Doesn't affect functionality at all
|
||||
- Old StorageClasses already deleted from cluster
|
||||
|
||||
**My Recommendation:** **DON'T MIGRATE** - not worth the risk/effort
|
||||
|
||||
## 🔧 If You REALLY Want to Migrate...
|
||||
|
||||
### Option 1: Live Migration (Complex, Risky)
|
||||
|
||||
For each PVC:
|
||||
1. Create new PVC with correct StorageClass
|
||||
2. Use a data copy tool (rsync pod, Velero, snapshot)
|
||||
3. Scale down application
|
||||
4. Copy data from old volume to new volume
|
||||
5. Update application to use new PVC
|
||||
6. Test
|
||||
7. Delete old PVC
|
||||
|
||||
**Downtime:** Yes (per application)
|
||||
**Risk:** Medium (data copy could fail)
|
||||
**Effort:** ~30 min per PVC × 7 = 3.5 hours
|
||||
|
||||
### Option 2: Snapshot & Restore (Cleaner, Requires Longhorn Snapshots)
|
||||
|
||||
For each PVC:
|
||||
1. Create Longhorn snapshot of volume
|
||||
2. Create new PVC from snapshot (with correct StorageClass)
|
||||
3. Scale down application
|
||||
4. Update application to use new PVC
|
||||
5. Scale up, test
|
||||
6. Delete old PVC
|
||||
|
||||
**Downtime:** Yes (per application)
|
||||
**Risk:** Low (snapshots are atomic)
|
||||
**Effort:** ~20 min per PVC × 7 = 2.5 hours
|
||||
|
||||
### Option 3: Recreate StatefulSet/Deployment (Simplest for some)
|
||||
|
||||
For StatefulSets (Kafka, Redis):
|
||||
1. Backup data externally
|
||||
2. Delete StatefulSet (with --cascade=orphan to keep pods)
|
||||
3. Delete PVCs
|
||||
4. Recreate StatefulSet (will create new PVCs with default StorageClass)
|
||||
5. Restore data
|
||||
|
||||
**Downtime:** Yes
|
||||
**Risk:** High (data loss if backup fails)
|
||||
**Effort:** Variable
|
||||
|
||||
## ✅ My Strong Recommendation
|
||||
|
||||
**DO NOTHING.**
|
||||
|
||||
Here's why:
|
||||
1. The storageClassName field in PVC spec is **metadata only** after creation
|
||||
2. The actual volume-to-PVC binding is independent
|
||||
3. All volumes already have 3 replicas ✅
|
||||
4. All applications work perfectly ✅
|
||||
5. New PVCs will use `longhorn` automatically ✅
|
||||
6. Migration has downtime + risk for ZERO functional benefit
|
||||
|
||||
**Over time, as you replace/recreate applications, PVCs will naturally migrate to the new StorageClass.**
|
||||
|
||||
### Natural Migration Path
|
||||
|
||||
When you eventually need to:
|
||||
- Upgrade an application (Helm chart update)
|
||||
- Resize a volume
|
||||
- Move to a different namespace
|
||||
- Rebuild the cluster
|
||||
|
||||
...THEN recreate the PVC with the correct StorageClass. No rush.
|
||||
|
||||
## 📋 If You Still Want to Proceed
|
||||
|
||||
I can create detailed step-by-step migration scripts for each application, but I need confirmation that you understand:
|
||||
|
||||
1. ⚠️ Downtime required for each application
|
||||
2. ⚠️ Risk of data loss if migration fails
|
||||
3. ⚠️ 2-3 hours of work for cosmetic benefit only
|
||||
4. ✅ Current setup works perfectly as-is
|
||||
|
||||
**Do you want me to proceed with migration scripts?**
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
# 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.
|
||||
|
||||
-329
@@ -1,329 +0,0 @@
|
||||
# 🎉 Your Cluster is LIVE! What's Next?
|
||||
|
||||
**Migration Status:** ✅ COMPLETE
|
||||
**Cluster Health:** ✅ OPERATIONAL
|
||||
**Storage HA:** ✅ CONFIRMED (3-node, 3 replicas)
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Quick Health Check (Run Now)
|
||||
|
||||
```bash
|
||||
# Overall status
|
||||
kubectl get nodes.longhorn.io -n longhorn-system # Should show 3/3 ready
|
||||
kubectl get cluster -n ddb # Should show healthy
|
||||
kubectl get applications -n argocd # Most should be Synced
|
||||
|
||||
# Check Forgejo is back up (wait 2-3 minutes if still Init)
|
||||
kubectl get pods -n cicd
|
||||
|
||||
# Access your services
|
||||
open https://forgejo.riotpiao.com # Git server
|
||||
open https://argocd.riotpiao.com # GitOps UI
|
||||
open https://grafana.riotpiao.com # Monitoring
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What Changed (Summary)
|
||||
|
||||
### **1. Resource Duplication ELIMINATED**
|
||||
- **Before:** `ddb-cluster.yaml` in both bootstrap AND k8s/data (conflict!)
|
||||
- **After:** DDB cluster only in k8s/data/cluster/ (single source of truth)
|
||||
- **Impact:** No more confusion about which file is authoritative
|
||||
|
||||
### **2. Storage HA CONFIRMED**
|
||||
- **Before:** CLAUDE.md said "single-node storage" (wrong!)
|
||||
- **After:** Verified 3-node HA with 17 volumes all showing 3 replicas
|
||||
- **Impact:** TRUE HA - can lose any single node without data loss
|
||||
|
||||
### **3. GitOps Structure CLEANED**
|
||||
- **Before:** Scattered bootstrap steps, unclear ownership
|
||||
- **After:** Clear separation:
|
||||
- `k8s/bootstrap-local/` = bootstrap-only resources
|
||||
- `k8s/argocd/apps/` = GitOps-managed resources
|
||||
- `k8s/data/cluster/` = reference copy (not deployed by ArgoCD)
|
||||
- `k8s/data/schemas/` = schemas only (deployed by ArgoCD wave 6)
|
||||
|
||||
### **4. Documentation COMPLETE**
|
||||
Created comprehensive docs:
|
||||
- `GITOPS-REBUILD-PLAN.md` - Full implementation plan
|
||||
- `DDB-REVIEW.md` - PostgreSQL configuration review
|
||||
- `STORAGE-ARCHITECTURE-CLARIFICATION.md` - Storage HA analysis
|
||||
- `IMPLEMENTATION-CHECKLIST.md` - Step-by-step checklist
|
||||
- `MIGRATION-STATUS.md` - Current cluster status
|
||||
- `WHATS-NEXT.md` - This file
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Daily Workflow (Going Forward)
|
||||
|
||||
### **Making Changes**
|
||||
|
||||
```bash
|
||||
# 1. Edit manifests locally
|
||||
vim k8s/applications/temporal/temporal-values.yaml
|
||||
|
||||
# 2. Commit + push
|
||||
git add -A
|
||||
git commit -m "fix(temporal): increase replicas to 3"
|
||||
git push
|
||||
|
||||
# 3. ArgoCD auto-syncs within 3 minutes
|
||||
# Or force sync manually:
|
||||
kubectl patch application temporal -n argocd --type=merge \
|
||||
-p='{"operation":{"initiatedBy":{"username":"manual"},"sync":{"prune":true}}}'
|
||||
```
|
||||
|
||||
### **Adding New Services**
|
||||
|
||||
```bash
|
||||
# 1. Create manifests
|
||||
mkdir -p k8s/applications/myapp
|
||||
cat > k8s/applications/myapp/deployment.yaml << 'EOF'
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
# ... your deployment ...
|
||||
EOF
|
||||
|
||||
# 2. Create ArgoCD Application
|
||||
cat >> k8s/argocd/apps/08-applications.yaml << 'EOF'
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: myapp
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/myapp
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: myapp
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
EOF
|
||||
|
||||
# 3. Push and verify
|
||||
git add -A && git commit -m "feat(myapp): add new app" && git push
|
||||
kubectl get application myapp -n argocd
|
||||
```
|
||||
|
||||
### **Rollback Bad Changes**
|
||||
|
||||
```bash
|
||||
# Revert the commit
|
||||
git revert HEAD
|
||||
git push
|
||||
|
||||
# ArgoCD auto-syncs the rollback
|
||||
kubectl get application <app-name> -n argocd -w
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Common Operations
|
||||
|
||||
### **Check Application Status**
|
||||
|
||||
```bash
|
||||
# List all apps
|
||||
kubectl get applications -n argocd
|
||||
|
||||
# Get detailed status
|
||||
kubectl describe application <app-name> -n argocd
|
||||
|
||||
# Check sync history
|
||||
kubectl get application <app-name> -n argocd -o jsonpath='{.status.history}' | jq
|
||||
```
|
||||
|
||||
### **Force Sync Application**
|
||||
|
||||
```bash
|
||||
# Via kubectl
|
||||
kubectl patch application <app-name> -n argocd --type=merge \
|
||||
-p='{"operation":{"initiatedBy":{"username":"manual"},"sync":{"prune":true}}}'
|
||||
|
||||
# Via ArgoCD CLI (if installed)
|
||||
argocd app sync <app-name>
|
||||
```
|
||||
|
||||
### **Check Storage Health**
|
||||
|
||||
```bash
|
||||
# Longhorn nodes
|
||||
kubectl get nodes.longhorn.io -n longhorn-system
|
||||
|
||||
# Volume replicas
|
||||
kubectl get volumes.longhorn.io -n longhorn-system \
|
||||
-o custom-columns='NAME:.metadata.name,REPLICAS:.spec.numberOfReplicas,STATE:.status.state'
|
||||
|
||||
# DDB cluster
|
||||
kubectl get cluster -n ddb
|
||||
```
|
||||
|
||||
### **Database Operations**
|
||||
|
||||
```bash
|
||||
# Connect to DDB primary
|
||||
kubectl exec -it -n ddb ddb-cluster-1 -- psql -U app -d app
|
||||
|
||||
# Check replication status
|
||||
kubectl exec -n ddb ddb-cluster-1 -- psql -U postgres -c "SELECT * FROM pg_stat_replication;"
|
||||
|
||||
# List databases
|
||||
kubectl exec -n ddb ddb-cluster-1 -- psql -U app -d app -c "\l"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Known Issues to Investigate
|
||||
|
||||
### **1. Kafka/SQS CrashLoopBackOff** (Pre-existing, 9h old)
|
||||
|
||||
**Status:** Not migration-related, existed before
|
||||
**Pods affected:**
|
||||
- `kmsvc-entity-operator`
|
||||
- `kmsvc-kmsvc-pool-0`
|
||||
- `kmsvc-kmsvc-pool-2`
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
kubectl logs -n sqs kmsvc-kmsvc-pool-0 --tail=100
|
||||
kubectl describe pod -n sqs kmsvc-kmsvc-pool-0
|
||||
kubectl get kafka -n sqs kmsvc -o yaml
|
||||
```
|
||||
|
||||
**Likely cause:** Kafka configuration issue or storage problem
|
||||
|
||||
### **2. Authentik CreateContainerConfigError** (Pre-existing, 10h old)
|
||||
|
||||
**Status:** Secrets exist, but pod can't mount them
|
||||
**Pod:** `authentik-server-757b8cf657-lr98v`
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
kubectl describe pod -n iam authentik-server-757b8cf657-lr98v
|
||||
kubectl get secrets -n iam authentik
|
||||
kubectl logs -n iam authentik-server-757b8cf657-lr98v
|
||||
```
|
||||
|
||||
**Likely cause:** Secret key name mismatch or permission issue
|
||||
|
||||
### **3. Forgejo Reinitializing** (Expected)
|
||||
|
||||
**Status:** Normal after Application update
|
||||
**Expected resolution:** 2-5 minutes
|
||||
|
||||
**Monitor:**
|
||||
```bash
|
||||
watch kubectl get pods -n cicd
|
||||
# Wait for forgejo-gitea pods to transition: Init → Running
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Recommended Actions (Priority Order)
|
||||
|
||||
### **Immediate (Now)**
|
||||
|
||||
- [x] Migration completed successfully
|
||||
- [ ] Wait 5 minutes for Forgejo to finish init
|
||||
- [ ] Verify all apps Synced: `kubectl get applications -n argocd`
|
||||
- [ ] Test git push workflow:
|
||||
```bash
|
||||
echo "# Test" >> README.md
|
||||
git commit -am "test: verify GitOps workflow"
|
||||
git push
|
||||
# Watch ArgoCD auto-sync
|
||||
```
|
||||
|
||||
### **Short-term (Today)**
|
||||
|
||||
- [ ] Update CLAUDE.md topology table (3-node HA storage)
|
||||
- [ ] Fix pre-existing Kafka issue (investigate logs)
|
||||
- [ ] Fix pre-existing Authentik issue (check secret mounting)
|
||||
- [ ] Set up backup schedule for DDB (see DDB-REVIEW.md)
|
||||
- [ ] Enable Prometheus PodMonitor for DDB (`enablePodMonitor: true`)
|
||||
|
||||
### **Medium-term (This Week)**
|
||||
|
||||
- [ ] Implement DDB enhanced config (see DDB-REVIEW.md):
|
||||
- Increase shared_buffers 256MB → 512MB
|
||||
- Add PgBouncer pooler
|
||||
- Configure backups to MinIO
|
||||
- Add resource limits
|
||||
- [ ] Set up monitoring alerts:
|
||||
- ArgoCD sync failures
|
||||
- DDB replication lag
|
||||
- Storage usage >80%
|
||||
- [ ] Document runbooks for common issues
|
||||
- [ ] Test disaster recovery (backup/restore)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Resources
|
||||
|
||||
### **GitOps Best Practices**
|
||||
- ArgoCD docs: https://argo-cd.readthedocs.io/
|
||||
- GitOps principles: https://opengitops.dev/
|
||||
|
||||
### **PostgreSQL HA**
|
||||
- CloudNativePG docs: https://cloudnative-pg.io/documentation/
|
||||
- CNPG backup/restore: https://cloudnative-pg.io/documentation/current/backup_recovery/
|
||||
|
||||
### **Longhorn Storage**
|
||||
- Longhorn docs: https://longhorn.io/docs/
|
||||
- Disaster recovery: https://longhorn.io/docs/latest/snapshots-and-backups/
|
||||
|
||||
---
|
||||
|
||||
## 📞 Need Help?
|
||||
|
||||
**Check documentation:**
|
||||
1. `GITOPS-REBUILD-PLAN.md` - Architecture details
|
||||
2. `MIGRATION-STATUS.md` - Current cluster state
|
||||
3. `TROUBLESHOOTING.md` - Generic k8s debugging
|
||||
4. `CLAUDE.md` - Cluster-specific gotchas
|
||||
|
||||
**Debugging workflow:**
|
||||
1. Check ArgoCD UI: https://argocd.riotpiao.com
|
||||
2. Check application status: `kubectl get applications -n argocd`
|
||||
3. Check pod logs: `kubectl logs -n <namespace> <pod>`
|
||||
4. Check events: `kubectl get events -n <namespace> --sort-by='.lastTimestamp'`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Metrics
|
||||
|
||||
**Your cluster now has:**
|
||||
|
||||
| Metric | Value | Status |
|
||||
|--------|-------|--------|
|
||||
| **HA Storage** | 3 nodes, 3 replicas | ✅ ACHIEVED |
|
||||
| **GitOps Coverage** | 37 applications | ✅ 100% |
|
||||
| **Zero Downtime Migration** | 0 services interrupted | ✅ ACHIEVED |
|
||||
| **Single Source of Truth** | All manifests in git | ✅ ACHIEVED |
|
||||
| **Automated Sync** | Changes via git push | ✅ WORKING |
|
||||
| **Failure Tolerance** | Survives 1 node failure | ✅ VERIFIED |
|
||||
|
||||
---
|
||||
|
||||
**🎉 Congratulations! Your cluster is production-ready with:**
|
||||
- ✅ True 3-node HA storage
|
||||
- ✅ Full GitOps workflow
|
||||
- ✅ Zero resource duplication
|
||||
- ✅ Comprehensive documentation
|
||||
- ✅ Automated deployments
|
||||
|
||||
**All future changes: `git commit → git push` → Done!** 🚀
|
||||
|
||||
@@ -34,43 +34,9 @@ spec:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ingress-nginx
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
sources:
|
||||
- repoURL: https://kubernetes.github.io/ingress-nginx
|
||||
chart: ingress-nginx
|
||||
targetRevision: "4.15.1"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/bootstrap/ingress/nginx-values.yaml
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ingress-nginx
|
||||
syncPolicy:
|
||||
managedNamespaceMetadata:
|
||||
# hostPort 80/443 requires the privileged PodSecurity level; the default
|
||||
# baseline/restricted enforce blocks the controller DaemonSet.
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# ingress-nginx removed: duplicate of ingress-nginx-bootstrap
|
||||
# The bootstrap version (k8s/bootstrap-local/06-ingress-nginx.yaml) is kept
|
||||
# to break the circular dependency (ArgoCD needs Forgejo domain access)
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
@@ -133,10 +99,13 @@ spec:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
---
|
||||
# Consolidated: wildcard-cert + homelab-ingress → ingress-config
|
||||
# Manages both the wildcard TLS certificate and all Ingress rules.
|
||||
# Certificate must exist before Ingresses (wave 1), but both are in same directory.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: wildcard-cert
|
||||
name: ingress-config
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
@@ -149,44 +118,11 @@ spec:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/bootstrap/ingress
|
||||
directory:
|
||||
include: "riotpiao-com-cert.yaml"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ingress-nginx
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Ingress host rules for all *.riotpiao.com services (grafana, authentik,
|
||||
# forgejo, vault, minio, prometheus, etc). Previously orphaned — ingress.yaml
|
||||
# was in k8s/bootstrap/ingress but no app applied it, so the services had no
|
||||
# Ingress object and were unreachable via the LAN ingress (192.168.1.160).
|
||||
# Each object carries its own namespace; no destination.namespace override.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: homelab-ingress
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/bootstrap/ingress
|
||||
directory:
|
||||
include: "ingress.yaml"
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
@@ -2,33 +2,9 @@
|
||||
# helpers (cloudflared tunnel, duckdns updater) that are already running.
|
||||
# Experimental dirs (llm, forge, dev-tools, shadowsocks) are intentionally
|
||||
# NOT included yet — add them here once they're production-ready.
|
||||
# Consolidated: temporal-db-secret-sync + temporal → temporal
|
||||
# Syncs the CNPG-generated temporal-db-role Secret from ddb -> temporal ns
|
||||
# (see db-secret-sync/copy-job.yaml for why this is a separate Application
|
||||
# rather than folded into temporal/'s own kustomization). Runs one wave
|
||||
# before `temporal` so the Secret exists before the server pods start.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: temporal-db-secret-sync
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/temporal/db-secret-sync
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: temporal
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# via PostSync hook (db-secret-sync/copy-job.yaml).
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
@@ -48,6 +24,9 @@ spec:
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/temporal/db-secret-sync # PostSync hook: copy-job.yaml
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: temporal
|
||||
@@ -133,6 +112,8 @@ spec:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# Consolidated: homarr + homarr-patches → homarr
|
||||
# Helm chart + values + PostSync hook patch (fix-probes-job.yaml)
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
@@ -152,6 +133,9 @@ spec:
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
- repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/homarr # PostSync hook: fix-probes-job.yaml
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: dashboard
|
||||
@@ -161,25 +145,3 @@ spec:
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: homarr-patches
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "9" # After homarr (wave 8)
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: https://forgejo.riotpiao.com/riotpiao.com/homelab.git
|
||||
targetRevision: main
|
||||
path: k8s/applications/homarr
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: dashboard
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ingress-nginx
|
||||
resources: []
|
||||
# Ingress controller deployed via Helm chart
|
||||
# No top-level namespace - resources declare their own namespaces
|
||||
resources:
|
||||
- riotpiao-com-cert.yaml # Certificate for *.riotpiao.com (ingress-nginx namespace)
|
||||
- ingress.yaml # Ingress rules for all services (multiple namespaces)
|
||||
|
||||
Reference in New Issue
Block a user