Establish clean homelab infrastructure baseline
Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# docs/superpowers — RETIRED 2026-07-28
|
||||
|
||||
The `plans/` + `specs/` workflow is no longer used. Its one piece of work is
|
||||
**done and live**, so nothing here is pending.
|
||||
|
||||
**What it covered:** the 2026-04-18 migration of shared PostgreSQL to
|
||||
CloudNativePG on OpenEBS ZFS —
|
||||
`specs/2026-04-18-postgresql-cnpg-zfs-migration-design.md` (design) and
|
||||
`plans/2026-04-18-postgresql-cnpg-zfs-migration.md` (task-by-task plan).
|
||||
|
||||
**Outcome: shipped.** `kubectl get cluster -n shared-db` shows
|
||||
`shared-postgresql`, 1 instance, *"Cluster in healthy state"*, 101 days old, with
|
||||
`shared-postgresql-1` Running. The authoritative manifests live in
|
||||
`../../shared-postgresql/` (`cloudnativepg-cluster.yaml`,
|
||||
`shared-postgresql-service.yaml`, `serviceaccount.yaml`), and the dump/restore
|
||||
/cutover/rollback runbook is `../../shared-postgresql/migration.md`.
|
||||
|
||||
**Why retired:** the format carried agent-workflow scaffolding ("REQUIRED
|
||||
SUB-SKILL", checkbox task lists) that only ever suited one migration. Design
|
||||
documents now live directly in `docs/` — see `../cicd.md` — and the split that
|
||||
matters is:
|
||||
|
||||
- `CHANGELOG.md` — what changed, for humans
|
||||
- `CLAUDE.md` — traps and procedures, for agents
|
||||
- `docs/*.md` — design docs for work not yet built
|
||||
- `<service>/README.md` — how a service actually works
|
||||
|
||||
The two files are kept for the migration rationale, which is still the best
|
||||
record of why CNPG and ZFS were chosen. Nothing reads them automatically.
|
||||
@@ -0,0 +1,193 @@
|
||||
# PostgreSQL CloudNativePG Migration Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace the legacy shared PostgreSQL deployment with a single-instance CloudNativePG cluster on OpenEBS ZFS while preserving the existing `shared-postgresql` service name for the apps that already depend on it.
|
||||
|
||||
**Architecture:** Keep the old PostgreSQL deployment alive until the new CNPG cluster is ready, then migrate data with a logical dump/restore, swap the service endpoints, and retire the Helm-based deployment only after validation passes. The new cluster stays single-instance because there is only one worker node.
|
||||
|
||||
**Tech Stack:** Kubernetes manifests, CloudNativePG operator, OpenEBS ZFS storage, `kubectl`, PostgreSQL client tools.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
- `shared-postgresql/cloudnativepg-cluster.yaml`: new authoritative CNPG cluster manifest.
|
||||
- `shared-postgresql/tailscale-loadbalancer.yaml`: keep the existing external service, but retarget it to the CNPG primary during cutover.
|
||||
- `shared-postgresql/shared-postgresql-values.yaml`: legacy Helm values to delete after the rollback window closes.
|
||||
- `shared-postgresql/shared-postgresql-init.sql`: reusable SQL used during migration to recreate app databases and roles.
|
||||
- `shared-postgresql/migration.md`: runbook for dump, restore, cutover, and rollback.
|
||||
|
||||
### Task 1: Add the CNPG cluster manifest
|
||||
|
||||
**Files:**
|
||||
- Create: `shared-postgresql/cloudnativepg-cluster.yaml`
|
||||
|
||||
- [ ] **Step 1: Confirm the OpenEBS ZFS storage class name**
|
||||
|
||||
Run: `kubectl get storageclass`
|
||||
|
||||
Expected: one OpenEBS-backed ZFS class is available and can be copied into `shared-postgresql/cloudnativepg-cluster.yaml` before apply.
|
||||
|
||||
- [ ] **Step 2: Write the cluster manifest**
|
||||
|
||||
```yaml
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: shared-postgresql
|
||||
namespace: shared-db
|
||||
spec:
|
||||
instances: 1
|
||||
enableSuperuserAccess: true
|
||||
storage:
|
||||
size: 10Gi
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: shared_data
|
||||
owner: sharedadmin
|
||||
```
|
||||
|
||||
Add the `storage.storageClass` line using the exact OpenEBS ZFS class name discovered in Step 1.
|
||||
|
||||
- [ ] **Step 3: Verify the manifest is structurally valid**
|
||||
|
||||
Run: `kubectl apply --dry-run=server -f shared-postgresql/cloudnativepg-cluster.yaml`
|
||||
|
||||
Expected: the API server accepts the `Cluster` object once the CNPG CRD is installed.
|
||||
|
||||
- [ ] **Step 4: Apply the cluster manifest in the staging namespace**
|
||||
|
||||
Run: `kubectl apply -f shared-postgresql/cloudnativepg-cluster.yaml`
|
||||
|
||||
Expected: the CNPG cluster is created and starts provisioning a single primary pod.
|
||||
|
||||
- [ ] **Step 5: Wait for the cluster to become ready**
|
||||
|
||||
Run: `kubectl wait -n shared-db --for=condition=Ready cluster/shared-postgresql --timeout=15m`
|
||||
|
||||
Expected: the cluster reaches a ready state before any data migration begins.
|
||||
|
||||
### Task 2: Write the migration runbook
|
||||
|
||||
**Files:**
|
||||
- Create: `shared-postgresql/migration.md`
|
||||
|
||||
- [ ] **Step 1: Document the source and target endpoints**
|
||||
|
||||
```text
|
||||
Source service: shared-postgresql.shared-db.svc.cluster.local:5432
|
||||
Target rw service: shared-postgresql-rw.shared-db.svc.cluster.local:5432
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Document the dump and restore commands**
|
||||
|
||||
```text
|
||||
Pause writes from the dependent apps before taking the dump so the logical backup is consistent.
|
||||
```
|
||||
|
||||
```bash
|
||||
PGPASSWORD="$OLD_SHAREDADMIN_PASSWORD" pg_dump -h shared-postgresql.shared-db.svc.cluster.local -U sharedadmin -Fc -d shared_data -f shared_data.dump
|
||||
PGPASSWORD="$OLD_GITEA_PASSWORD" pg_dump -h shared-postgresql.shared-db.svc.cluster.local -U gitea -Fc -d gitea -f gitea.dump
|
||||
PGPASSWORD="$OLD_CASDOOR_PASSWORD" pg_dump -h shared-postgresql.shared-db.svc.cluster.local -U casdoor -Fc -d casdoor -f casdoor.dump
|
||||
|
||||
PGPASSWORD="$NEW_SHAREDADMIN_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U sharedadmin -d postgres -f shared-postgresql/shared-postgresql-init.sql
|
||||
PGPASSWORD="$NEW_SHAREDADMIN_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U sharedadmin -d shared_data shared_data.dump
|
||||
PGPASSWORD="$NEW_GITEA_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U gitea -d gitea gitea.dump
|
||||
PGPASSWORD="$NEW_CASDOOR_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U casdoor -d casdoor casdoor.dump
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Document the rollback decision point**
|
||||
|
||||
```text
|
||||
If restore or validation fails, keep the old Helm deployment active and do not delete its Service or PVC.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Review the runbook for exact cutover order**
|
||||
|
||||
Expected: the document explains that the old service stays live until data restore and app checks pass.
|
||||
|
||||
### Task 3: Cut over services to CNPG
|
||||
|
||||
**Files:**
|
||||
- Modify: `shared-postgresql/tailscale-loadbalancer.yaml`
|
||||
- Create: `shared-postgresql/shared-postgresql-service.yaml`
|
||||
|
||||
- [ ] **Step 1: Add the compatibility ClusterIP service**
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: shared-postgresql
|
||||
namespace: shared-db
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
cnpg.io/cluster: shared-postgresql
|
||||
cnpg.io/instanceRole: primary
|
||||
ports:
|
||||
- name: postgres
|
||||
port: 5432
|
||||
targetPort: 5432
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update the Tailscale LoadBalancer selector**
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: shared-postgresql-tailscale
|
||||
namespace: shared-db
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
loadBalancerClass: tailscale
|
||||
ports:
|
||||
- name: tcp-postgresql
|
||||
port: 5432
|
||||
protocol: TCP
|
||||
targetPort: 5432
|
||||
selector:
|
||||
cnpg.io/cluster: shared-postgresql
|
||||
cnpg.io/instanceRole: primary
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Apply the compatibility service only after the Helm release is removed**
|
||||
|
||||
Run: `kubectl apply -f shared-postgresql/shared-postgresql-service.yaml -f shared-postgresql/tailscale-loadbalancer.yaml`
|
||||
|
||||
Expected: the original `shared-postgresql` hostname resolves to the CNPG primary and Tailscale reaches the same pod.
|
||||
|
||||
- [ ] **Step 4: Validate application connectivity**
|
||||
|
||||
Run: `PGPASSWORD="$NEW_SHAREDADMIN_PASSWORD" psql -h shared-postgresql.shared-db.svc.cluster.local -U sharedadmin -d shared_data -c 'select 1;'`
|
||||
|
||||
Expected: the compatibility Service resolves and accepts a real PostgreSQL login after the swap.
|
||||
|
||||
### Task 4: Remove the legacy Helm deployment
|
||||
|
||||
**Files:**
|
||||
- Delete: `shared-postgresql/shared-postgresql-values.yaml`
|
||||
|
||||
- [ ] **Step 1: Confirm all dependent apps are using the CNPG-backed Service**
|
||||
|
||||
Run: `kubectl get endpoints -n shared-db shared-postgresql`
|
||||
|
||||
Expected: the endpoints point at the CNPG pod, not the old Helm chart pod.
|
||||
|
||||
- [ ] **Step 2: Remove the old Helm values file after the rollback window**
|
||||
|
||||
Run: `git rm shared-postgresql/shared-postgresql-values.yaml`
|
||||
|
||||
Expected: the repository no longer advertises the retired deployment path once cutover is stable.
|
||||
|
||||
- [ ] **Step 3: Keep the bootstrap SQL file as the migration reference**
|
||||
|
||||
Expected: `shared-postgresql/shared-postgresql-init.sql` remains in the tree until the migration is fully complete and documented.
|
||||
|
||||
- [ ] **Step 4: Re-run the manifest validation pass**
|
||||
|
||||
Run: `kubectl apply --dry-run=server -f shared-postgresql/`
|
||||
|
||||
Expected: the remaining manifest set is clean and consistent.
|
||||
@@ -0,0 +1,77 @@
|
||||
# PostgreSQL Migration Design
|
||||
|
||||
## Goal
|
||||
Move the current shared PostgreSQL deployment to CloudNativePG and back it with the existing OpenEBS ZFS storage layer.
|
||||
|
||||
The target state is a single PostgreSQL cluster managed by CloudNativePG, running one instance on the only worker node, with one shared database service for the apps that already use `shared-postgresql`.
|
||||
|
||||
## Current State
|
||||
- PostgreSQL is currently deployed from chart values in `shared-postgresql/shared-postgresql-values.yaml`.
|
||||
- Storage currently uses `local-path`.
|
||||
- Apps such as Gitea and Casdoor point at the shared PostgreSQL service.
|
||||
- There is an init SQL file for bootstrap data in `shared-postgresql/shared-postgresql-init.sql`.
|
||||
|
||||
## Target State
|
||||
- CloudNativePG manages the PostgreSQL lifecycle.
|
||||
- Storage comes from the OpenEBS-provided ZFS-backed `StorageClass`.
|
||||
- The cluster runs with `instances: 1` because there is only one worker node.
|
||||
- Existing app databases remain in the same PostgreSQL cluster.
|
||||
- A compatibility Kubernetes `Service` preserves the existing shared database hostname or provides an equivalent stable alias.
|
||||
|
||||
## Non-Goals
|
||||
- High availability across multiple nodes.
|
||||
- Automatic failover.
|
||||
- Database sharding or splitting apps into separate clusters.
|
||||
- Changing application-level schemas unless required by migration.
|
||||
|
||||
## Approach
|
||||
1. Provision or verify the OpenEBS ZFS `StorageClass` suitable for PostgreSQL PVCs.
|
||||
2. Deploy CloudNativePG and create one cluster with a single instance.
|
||||
3. Create the required role and database layout for the shared apps.
|
||||
4. Migrate data from the current PostgreSQL instance with a logical dump and restore.
|
||||
5. Cut over workloads to the new service.
|
||||
6. Validate app logins, migrations, and basic read/write behavior.
|
||||
|
||||
## Migration Plan
|
||||
### Phase 1: Storage
|
||||
- Confirm the OpenEBS ZFS components are installed and healthy.
|
||||
- Create a dedicated PostgreSQL `StorageClass` for CNPG.
|
||||
- Use a single PVC for the primary instance.
|
||||
|
||||
### Phase 2: Database Deployment
|
||||
- Install CloudNativePG.
|
||||
- Create a `Cluster` manifest with one instance.
|
||||
- Set resource requests and limits close to the current PostgreSQL footprint.
|
||||
- Mount any required bootstrap SQL through CloudNativePG-supported init methods.
|
||||
|
||||
### Phase 3: Data Migration
|
||||
- Quiesce writes on the current PostgreSQL instance.
|
||||
- Take a logical backup of the existing databases and roles, including globals such as users and grants.
|
||||
- Restore into the new CloudNativePG cluster.
|
||||
- Recreate any app-specific users, grants, and schemas that are not captured automatically.
|
||||
|
||||
### Phase 4: Cutover
|
||||
- Point app secrets or connection settings at the new CNPG service.
|
||||
- Restart or roll the dependent workloads.
|
||||
- Verify each app can connect and operate normally.
|
||||
|
||||
### Phase 5: Cleanup
|
||||
- Keep the old deployment available until validation completes.
|
||||
- Remove the old PostgreSQL deployment only after the new cluster is confirmed healthy.
|
||||
|
||||
## Risks
|
||||
- OpenEBS ZFS must be healthy before PostgreSQL migration starts.
|
||||
- Single-node deployment means node failure still causes downtime.
|
||||
- Logical restore can miss permissions or extension details if the old instance has custom setup.
|
||||
- Existing app passwords should be preserved or rotated carefully during cutover.
|
||||
|
||||
## Validation
|
||||
- CNPG cluster reports healthy.
|
||||
- PVC is bound on the ZFS-backed storage class.
|
||||
- Existing apps can connect using the shared PostgreSQL service.
|
||||
- Basic read/write checks succeed for each app database.
|
||||
- Restarting the PostgreSQL pod does not lose data.
|
||||
|
||||
## Rollback
|
||||
- If restore or validation fails, keep the current PostgreSQL deployment active and point apps back to the old service.
|
||||
- Do not delete the old PVC or deployment until the new cluster passes validation.
|
||||
Reference in New Issue
Block a user