Reorganize the brownfield repository, remove retired and generated artifacts, harden ignore rules, and record the GitOps/IaC redesign.
145 lines
7.3 KiB
Markdown
145 lines
7.3 KiB
Markdown
# Shared PostgreSQL Migration Runbook
|
|
|
|
## Target
|
|
|
|
- CloudNativePG cluster: `shared-postgresql`
|
|
- Namespace: `shared-db`
|
|
- Compatibility service: `shared-postgresql.shared-db.svc.cluster.local:5432`
|
|
- CNPG rw service: `shared-postgresql-rw.shared-db.svc.cluster.local:5432`
|
|
- Tailscale service: `shared-postgresql-tailscale.shared-db.svc.cluster.local:5432`
|
|
|
|
## Apply order
|
|
|
|
1. Create the CNPG superuser secret
|
|
2. Apply `shared-postgresql/cloudnativepg-cluster.yaml`
|
|
3. Wait for the cluster to become ready
|
|
4. Restore the databases and validate against the CNPG `rw` Service
|
|
5. Remove the legacy Helm release so the old Service can be removed cleanly
|
|
6. Apply `shared-postgresql/shared-postgresql-service.yaml`
|
|
7. Validate app connectivity through the compatibility and Tailscale Services
|
|
|
|
## Storage preflight
|
|
|
|
```bash
|
|
kubectl get storageclass localpv-zfs-ceph
|
|
kubectl get pods -n openebs
|
|
```
|
|
|
|
Make sure the OpenEBS ZFS storage class exists and the OpenEBS components are healthy before starting the cutover.
|
|
|
|
## Reconcile source passwords
|
|
|
|
The following passwords are the current source of truth for the migration:
|
|
|
|
- `SOURCE_POSTGRES_PASSWORD` for the source dump and CNPG superuser secret
|
|
- `OLD_GITEA_PASSWORD` and `OLD_CASDOOR_PASSWORD` for the restored app logins
|
|
|
|
## Create the CNPG secret
|
|
|
|
```bash
|
|
kubectl create secret generic shared-postgresql-superuser-secret \
|
|
-n shared-db \
|
|
--type=kubernetes.io/basic-auth \
|
|
--from-literal=username=postgres \
|
|
--from-literal=password="$SOURCE_POSTGRES_PASSWORD" \
|
|
--dry-run=client -o yaml | kubectl apply -f -
|
|
```
|
|
|
|
## Dump from the old instance
|
|
|
|
Pause writes from dependent apps first.
|
|
|
|
```bash
|
|
kubectl get pod -n shared-db -l app.kubernetes.io/component=primary,app.kubernetes.io/instance=shared-postgresql,app.kubernetes.io/name=postgresql --show-labels
|
|
SOURCE_POD="$(kubectl get pod -n shared-db -l app.kubernetes.io/component=primary,app.kubernetes.io/instance=shared-postgresql,app.kubernetes.io/name=postgresql -o jsonpath='{.items[0].metadata.name}')"
|
|
kubectl exec -n shared-db "$SOURCE_POD" -- sh -lc "PGPASSWORD='$SOURCE_POSTGRES_PASSWORD' pg_dumpall -h 127.0.0.1 -U postgres --globals-only" > globals.sql
|
|
kubectl exec -n shared-db "$SOURCE_POD" -- sh -lc "PGPASSWORD='$SOURCE_POSTGRES_PASSWORD' pg_dump -h 127.0.0.1 -U postgres -Fc -d e5renew" > e5renew.dump
|
|
kubectl exec -n shared-db "$SOURCE_POD" -- sh -lc "PGPASSWORD='$SOURCE_POSTGRES_PASSWORD' pg_dump -h 127.0.0.1 -U postgres -Fc -d gitea" > gitea.dump
|
|
kubectl exec -n shared-db "$SOURCE_POD" -- sh -lc "PGPASSWORD='$SOURCE_POSTGRES_PASSWORD' pg_dump -h 127.0.0.1 -U postgres -Fc -d casdoor" > casdoor.dump
|
|
```
|
|
|
|
## Restore into CNPG
|
|
|
|
`globals.sql` restores the roles and passwords from the source cluster. The init SQL only creates the application databases and grants.
|
|
|
|
```bash
|
|
cat globals.sql > globals.with-postgres-reset.sql
|
|
printf "ALTER ROLE postgres PASSWORD :'source_postgres_password';\n" >> globals.with-postgres-reset.sql
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -v source_postgres_password="$SOURCE_POSTGRES_PASSWORD" -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d postgres -f globals.with-postgres-reset.sql
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d postgres -f shared-postgresql/shared-postgresql-init.sql
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d e5renew e5renew.dump
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d gitea gitea.dump
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" pg_restore -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d casdoor casdoor.dump
|
|
```
|
|
|
|
## Pre-cutover validation
|
|
|
|
```bash
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d e5renew -c 'select 1;'
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U postgres -d e5renew -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
PGPASSWORD="$OLD_GITEA_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U gitea -d gitea -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
PGPASSWORD="$OLD_CASDOOR_PASSWORD" psql -h shared-postgresql-rw.shared-db.svc.cluster.local -U casdoor -d casdoor -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
```
|
|
|
|
## Post-cutover validation
|
|
|
|
```bash
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql.shared-db.svc.cluster.local -U postgres -d e5renew -c 'select 1;'
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql.shared-db.svc.cluster.local -U postgres -d e5renew -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
PGPASSWORD="$OLD_GITEA_PASSWORD" psql -h shared-postgresql.shared-db.svc.cluster.local -U gitea -d gitea -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
PGPASSWORD="$OLD_CASDOOR_PASSWORD" psql -h shared-postgresql.shared-db.svc.cluster.local -U casdoor -d casdoor -c 'create table if not exists migration_check(id int); insert into migration_check values (1); delete from migration_check; drop table migration_check;'
|
|
PGPASSWORD="$SOURCE_POSTGRES_PASSWORD" psql -h shared-postgresql-tailscale.shared-db.svc.cluster.local -U postgres -d e5renew -c 'select 1;'
|
|
```
|
|
|
|
## Rollback
|
|
|
|
- Keep the old Helm-based PostgreSQL deployment until validation passes.
|
|
- If restore fails, delete the CNPG cluster.
|
|
- Delete the CNPG compatibility Service with `kubectl delete -f shared-postgresql/shared-postgresql-service.yaml` so the `shared-postgresql` hostname is free for the legacy release again.
|
|
- Reapply the legacy Helm release.
|
|
- After the old Helm release is back, regenerate the Tailscale Service from the live pod labels and apply it:
|
|
|
|
```bash
|
|
python - <<'PY' | kubectl apply -f -
|
|
import json
|
|
import subprocess
|
|
import yaml
|
|
|
|
pod = subprocess.check_output([
|
|
'kubectl', 'get', 'pod', '-n', 'shared-db',
|
|
'-l', 'app.kubernetes.io/component=primary,app.kubernetes.io/instance=shared-postgresql,app.kubernetes.io/name=postgresql',
|
|
'-o', 'json'
|
|
], text=True)
|
|
labels = json.loads(pod)['items'][0]['metadata']['labels']
|
|
selector = {
|
|
'app.kubernetes.io/component': labels['app.kubernetes.io/component'],
|
|
'app.kubernetes.io/instance': labels['app.kubernetes.io/instance'],
|
|
'app.kubernetes.io/name': labels['app.kubernetes.io/name'],
|
|
}
|
|
service = {
|
|
'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': selector,
|
|
},
|
|
}
|
|
print(yaml.safe_dump(service, sort_keys=False))
|
|
PY
|
|
```
|
|
|
|
- Do not delete the old PVC until the new cluster is verified.
|