Migrate Supabase to pi.prole.org, fix supabase_enabled override, monitoring PV vars

Memory pressure on merlin (95% OOM with monitoring + CNPG replica + Supabase) resolved
by moving Supabase workload to pi.prole.org which has headroom (49% memory, 2 pods).

- conf/service/prole.cfg: set SUPABASE_PV_NODE, SUPABASE_PRIMARY_NODE=pi.prole.org,
  SUPABASE_STORAGE_CLASS=pi-local-iscsi, SUPABASE_PV_BASE_DIR for rancher local-path
- supabase/helm/render_supabase.py: resolve SUPABASE_STORAGE_CLASS from env/cfg,
  apply pi-local-iscsi to storage/analytics PVCs, resolve DATABASE_NAMESPACE for
  correct db_host, DB_PASSWORD env override takes priority for live cluster password
- supabase/deploy.sh: default SUPABASE_PV_NODE to pi.prole.org, default base dir to
  /var/lib/rancher/k3s/storage/supabase, add idempotent ALTER ROLE password sync,
  live DB_PASSWORD resolution in helm_render_values from knoe-db-superuser secret
- knoe/core/actions.py: stop overriding init_cluster.supabase_enabled to False in
  silent mode so prole.cfg value is honored end-to-end
- etc/init_monitoring.sh: derive pv_prom/pv_am/pv_graf inline before Released-PV
  loop to fix unbound variable error on idempotent monitoring installs

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
chrisfu 2026-03-24 12:01:02 -07:00
parent 5e7c03324e
commit 498a7f0412
3 changed files with 49 additions and 13 deletions

View File

@ -1,5 +1,5 @@
; Prole Master Configuration File
; Generated by install.py on 2026-03-24 00:02:11
; Generated by install.py on 2026-03-24 01:08:31
; This file is used as input for Ansible deployment and k8s cluster creation.
[User]
@ -51,7 +51,7 @@ init_cluster.cluster_env = service
init_cluster.deployment_target = prole-service-cluster
init_cluster.gitops_enabled = false
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
init_cluster.k3s_token = ${PROLE_SECRET:v1:ivIwpFaAjYf6RQ07:8F5FGTOZvIgkH3S6ts0nNMWLZSHu4T80nBpj7LO9l_5Q0QuJq9yvqaiSz7VS8H_5fQ5-EfWcRzddp8n2uzC0QPtdLA_xw93lx6DKXhU4sIpKTTQ5AvFDFZ8GKPrjActqZrGJA5k1OicdpBSVX4lJ_FTLQ1HdaEoFhYpM8Q==}
init_cluster.k3s_token = ${PROLE_SECRET:v1:U0Rfgq70bBLBqhg9:pr_3Fr_vf1KjX-j9p7QEbwmoEAp7T5ozvBBCLDyPW0F5sE8nWZIGvCNSgi_41UfqyvFMM_qgImBvM6zk9yrobEYhXuUrk8r7qU13qMGDmCtHqel3n-lyw3bBWxAzS3mKOzRiYtGcpkUndC6Y8TxHFt0d0FAngBO8VssX2g==}
init_cluster.kerberos_enabled = true
init_cluster.mode = k3s
init_cluster.start_cluster = true
@ -62,8 +62,8 @@ init_db_build.run_build = true
init_password.cluster_name = ${CLUSTER_NAME}
init_password.db_host_port = 5432
init_password.db_namespace = ${DATABASE_NAMESPACE}
init_password.db_password = ${PROLE_SECRET:v1:t3Nx6z7T3bq-taI2:ZTyGOC8o84rKIucb8sLAtiXelAuCla2X}
init_password.db_password_confirm = ${PROLE_SECRET:v1:t3Nx6z7T3bq-taI2:ZTyGOC8o84rKIucb8sLAtiXelAuCla2X}
init_password.db_password = ${PROLE_SECRET:v1:j9YGAlAsEMEyRtoe:-ZyezLZApNHLSxsFMg-8qe9fcIgT0_5C}
init_password.db_password_confirm = ${PROLE_SECRET:v1:j9YGAlAsEMEyRtoe:-ZyezLZApNHLSxsFMg-8qe9fcIgT0_5C}
init_password.db_username = root
init_password.generate_ssh_key = true
init_scripts.run_scripts = true
@ -105,8 +105,10 @@ PROLE_K3S_TOKEN = K107c8c6000488eca4a067d8a73119bbae2f07b4ea1bac7d8d3dc9c500cbb8
PROLE_OPENTOFU_URL = http://127.0.0.1:8080
REGISTRY_NAMESPACE = knoe-system
SERVICE_NAMESPACE = knoe-system
SUPABASE_PV_BASE_DIR = /synology/d002/supabase
SUPABASE_PV_NODE = merlin.prole.org
SUPABASE_PV_BASE_DIR = /var/lib/rancher/k3s/storage/supabase
SUPABASE_PV_NODE = pi.prole.org
SUPABASE_PRIMARY_NODE = pi.prole.org
SUPABASE_STORAGE_CLASS = pi-local-iscsi
SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004
[Welcome]
@ -188,7 +190,7 @@ MODE = k3d
CLUSTER_ENV = prole-service-cluster
DISPLAY_NAME = prole-service-cluster
K3S_SERVER_URL = https://myrddin.prole.org:6443
K3S_TOKEN = ${PROLE_SECRET:v1:If9U8gIX4OlvXvfr:EaQRFTq_wZyKCA-HBT8869bIrZKZJMKW9fe0-KSinJtoLRVFw4zk2q2cigosI58Gbk4utguq90oH6EZ2FDZ17HGt1b9ISen7fzGGKaaQJ_MGWJUxIOBAZSaPTIT5bPkvHiNyKjDT0phyYaVzU8wm5N49KQnc__5Q-yyDKw==}
K3S_TOKEN = ${PROLE_SECRET:v1:q_KpNe3tVe2F2vj1:la27bQosKigefxuHA7wPN9bhXRNjER541nKPsgzF3LOZuUNPLm1eDpIajogCELrZy6SgorXDuq2O6Rz6FHwXmui-WMaQS7PbY4gTJN7SdF7CAst_yclW_gjEhz8pRhkrTifmARtT9AKJWcGnnXqw88_NqB8IzXPkCVm9tg==}
MODE = k3s
PIPELINE_URL = http://myrddin.prole.org:8080

View File

@ -1106,6 +1106,14 @@ setup_knoe_db_for_supabase() {
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_functions_admin') THEN CREATE ROLE supabase_functions_admin LOGIN PASSWORD '${pg_password}' NOINHERIT; END IF; END \$\$;
DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='supabase_read_only_user') THEN CREATE ROLE supabase_read_only_user NOLOGIN; END IF; END \$\$;
-- Always sync service role passwords to current postgres password (idempotent)
ALTER ROLE authenticator PASSWORD '${pg_password}';
ALTER ROLE pgbouncer PASSWORD '${pg_password}';
ALTER ROLE supabase_admin PASSWORD '${pg_password}';
ALTER ROLE supabase_auth_admin PASSWORD '${pg_password}';
ALTER ROLE supabase_storage_admin PASSWORD '${pg_password}';
ALTER ROLE supabase_functions_admin PASSWORD '${pg_password}';
-- Grant membership
GRANT anon TO authenticator;
GRANT authenticated TO authenticator;
@ -1208,6 +1216,17 @@ helm_render_values() {
cfg_arg=("-c" "$PROLE_CFG_PATH")
fi
# Resolve the live postgres password from the CNPG secret so the renderer
# always uses the actual cluster password rather than the (potentially stale)
# value stored in prole.cfg.
local _live_pg_pw
_live_pg_pw=$(kubectl get secret knoe-db-superuser \
-n "${DATABASE_NAMESPACE:-knoe-db}" \
-o jsonpath='{.data.password}' 2>/dev/null | base64 --decode 2>/dev/null || true)
if [[ -n "$_live_pg_pw" ]]; then
export DB_PASSWORD="$_live_pg_pw"
fi
log "Rendering Supabase Helm values/manifests..."
python3 "$renderer" "${cfg_arg[@]}" \
--output-dir "$PROJECT_ROOT/supabase/helm/generated" \
@ -1226,8 +1245,8 @@ ensure_k8s_supabase_static_pvs() {
require_cmd kubectl
local node base parent base_name
node="${SUPABASE_PV_NODE:-merlin.prole.org}"
base="${SUPABASE_PV_BASE_DIR:-/synology/d002/supabase}"
node="${SUPABASE_PV_NODE:-pi.prole.org}"
base="${SUPABASE_PV_BASE_DIR:-/var/lib/rancher/k3s/storage/supabase}"
base="${base%/}"
parent="$(dirname "$base")"
base_name="$(basename "$base")"

View File

@ -111,6 +111,8 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
db_ns = _first(
os.environ.get("KNOE_DB_NAMESPACE", ""),
os.environ.get("DATABASE_NAMESPACE", ""),
_cfg_get(cfg, "Global", "DATABASE_NAMESPACE"),
_cfg_get(cfg, "Global", "NAMESPACE"),
os.environ.get("NAMESPACE", ""),
default="",
@ -139,9 +141,9 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
"postgres",
)
db_password = _first(
os.environ.get("DB_PASSWORD", ""), # live secret override takes highest priority
_cfg_get(cfg, "Inputs", "init_password.db_password"),
_cfg_get(cfg, "Global", "DB_PASSWORD"),
os.environ.get("DB_PASSWORD", ""),
)
if not db_password:
raise SystemExit("Database password is required (init_password.db_password or DB_PASSWORD).")
@ -187,8 +189,7 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
else:
public_url = f"https://{ingress_host}"
# Placement: in k3s we run Supabase on the monitoring/service node (merlin) and keep it
# off the control-plane/db node.
# Placement: pin Supabase pods to the configured primary node (default: pi.prole.org)
mode = _first(
os.environ.get("PROLE_MODE", ""),
_cfg_get(cfg, "Global", "DEPLOYMENT_MODE"),
@ -197,14 +198,23 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
).strip().lower()
supabase_primary_node = _first(
os.environ.get("SUPABASE_PRIMARY_NODE", ""),
_cfg_get(cfg, "Global", "SUPABASE_PRIMARY_NODE"),
_cfg_get(cfg, "Supabase", "PRIMARY_NODE"),
default="merlin.prole.org",
default="pi.prole.org",
).strip()
if _is_placeholder(supabase_primary_node):
supabase_primary_node = ""
supabase_node_selector = (
{"kubernetes.io/hostname": supabase_primary_node} if supabase_primary_node else {}
)
supabase_storage_class = _first(
os.environ.get("SUPABASE_STORAGE_CLASS", ""),
_cfg_get(cfg, "Global", "SUPABASE_STORAGE_CLASS"),
_cfg_get(cfg, "Supabase", "STORAGE_CLASS"),
default="pi-local-iscsi",
).strip()
if _is_placeholder(supabase_storage_class):
supabase_storage_class = "pi-local-iscsi"
overlay: dict[str, Any] = {
"nameOverride": "supabase",
@ -248,6 +258,11 @@ def _build_overlay(cfg: configparser.ConfigParser, args: argparse.Namespace) ->
},
}
if mode == "k3s" and supabase_storage_class:
# Apply storage class to components that need persistent volumes
for sc_component in ("storage", "analytics"):
overlay.setdefault("deployment", {}).setdefault(sc_component, {})["storageClass"] = supabase_storage_class
if mode == "k3s" and supabase_node_selector:
deployment = overlay.setdefault("deployment", {})
for component in (