Files
homelab/k8s/argocd/bootstrap.archived/BOOTSTRAP.md
T
Story Crater Bot f656338a15 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-08-18 15:08:03 -07:00

5.5 KiB
Raw Blame History

Cluster bootstrap & CD — two scopes

The GitOps repo is hosted on the cluster's own Forgejo, so ArgoCD cannot pull it until Forgejo exists. There are two distinct workflows — pick the one that matches your situation.

Scope 1 — from scratch     cold cluster, nothing running yet   → full bootstrap
Scope 2 — iterating CD     cluster + Forgejo + ArgoCD are up   → just git push

Scope 1 — Cluster from scratch (cold bootstrap)

Strict ordering, because each layer depends on the previous one existing.

Talos + Cilium CNI          Terraform (nodes)                     — cluster reachable
Substrate                   cert-manager, ingress-nginx, reloader — Terraform helm_releases
                            Longhorn, ArgoCD                      — imperative install
Phase 0 (git-independent)   CNPG + ddb-cluster + redis + Forgejo  — manual, below
Seed                        git push repo into Forgejo
Phase 1                     app-of-apps root                      — ArgoCD deploys the rest

Run every command from a local checkout with KUBECONFIG pointed at the cluster.

Phase 0 — bootstrap Forgejo and its data plane (git-independent)

# 1. CNPG operator (public Helm) + its CRDs.
kubectl apply -f k8s/argocd/bootstrap/cnpg-operator.yaml
argocd app sync cnpg-operator
# CNPG ships CRDs in the chart's crds/ folder, which ArgoCD's helm-template does
# NOT install. If the ddb-cluster apply below fails with "no matches for kind
# Cluster", install them once:
#   kubectl apply --server-side -f \
#     https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.1.yaml

# 2. Namespaces first. cicd MUST be labelled privileged (DinD/runner needs it);
#    the Forgejo app deliberately does NOT create it.
kubectl create namespace ddb  --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace cicd --dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace cicd pod-security.kubernetes.io/enforce=privileged --overwrite

# 3. Postgres cluster + Forgejo database + Redis (raw manifests, local checkout).
kubectl apply -f k8s/data/ddb-cluster.yaml
kubectl apply -f k8s/data/forgejo-database.yaml
kubectl apply -f k8s/security/ci-cd/forgejo-redis.yaml

# 4. Copy the CNPG-generated app password into cicd (Secrets are ns-scoped).
kubectl get secret ddb-cluster-app -n ddb -o yaml \
  | sed 's/namespace: ddb/namespace: cicd/' \
  | kubectl apply -n cicd -f -

# 5. Forgejo (public Helm, values inlined — git-independent).
kubectl apply -f k8s/argocd/bootstrap/forgejo.yaml
argocd app sync forgejo

Seed + Phase 1

# 6. Push this repo to the freshly-created Forgejo so Phase 1 has a source.
git remote add forgejo https://forgejo.riotpiao.com/riotpiao.com/homelab.git
git push forgejo main

# 7. (Optional) Cloudflared tunnel secret — if the SOPS CMP plugin is NOT yet
#    wired up: create the cloudflared-token Secret manually before wave 8 syncs.
#    Skip this step if the CMP plugin is already live and handling SOPS decryption.
kubectl create namespace cloudflared --dry-run=client -o yaml | kubectl apply -f -
sops -d k8s/applications/cloudflared/cloudflared-secrets.enc.yaml \
  | yq '.cloudflared.tunnelToken' \
  | kubectl create secret generic cloudflared-token -n cloudflared \
      --from-literal=token=- --dry-run=client -o yaml | kubectl apply -f -
# Once the SOPS CMP plugin is deployed (Phase 1 wave 0), migrate this Secret
# into the plugin's script output and remove this manual step.

# 8. Deploy everything else from git — one sync brings up the whole cluster.
kubectl apply -f k8s/argocd/projects/homelab-project.yaml   # AppProject
kubectl apply -k k8s/argocd/root                            # app-of-apps root
argocd app sync homelab-root

homelab-root renders every Application under k8s/argocd/apps/ and syncs them in sync-wave order (networking → storage/observability → logging → security → data → messaging → applications).


Scope 2 — Iterating CD (day-2, cluster already up)

The app-of-apps and its children already exist and reconcile from Forgejo. To change anything, you do NOT re-run the bootstrap — you just push:

# edit manifests under k8s/**
git add -A && git commit -m "..."
git push                      # to Forgejo main (origin)
# ArgoCD auto-syncs (child apps have syncPolicy.automated); to force it:
argocd app sync homelab-root
  • Adding a new service = add an Application manifest under k8s/argocd/apps/ and its manifests under the path it references, then push. The app-of-apps picks it up on next sync.
  • Changing a Helm value = edit the values file the Application points at ($values/...), push.
  • Forgejo itself is Phase-0 / bootstrap (manual sync only) — a bad CI commit must not be able to break the system CI depends on.

Notes / prerequisites

  • Secrets: several values files (loki S3, grafana admin, authentik, vault) expect secrets that helmfile used to inject via --set. Under ArgoCD these come from the *.enc.yaml SOPS files via the ArgoCD SOPS plugin — confirm the plugin is configured before syncing waves 23, or those charts render with empty secrets.
  • Substrate (cert-manager, ingress-nginx, reloader) is installed by Terraform as bootstrap Helm releases, not ArgoCD. Cilium CNI, Longhorn, and ArgoCD itself are cluster-bootstrap installs.
  • Single storage node: only talos-cp-1 runs workloads/storage, so stateful services are single-instance (ddb-cluster = 1). See the repo USAGE.md and root CLAUDE.md topology section.