homelab/build/config/ansible/roles/k3s_postgres/templates/postgres-cluster.yaml.j2
Russell Seymour 1250c9cef6
Some checks are pending
deploy / deploy (push) Waiting to run
Initial checkin of code for managing homelab
2026-08-24 20:52:36 +01:00

104 lines
4.9 KiB
Django/Jinja

# Managed by Ansible (roles/k3s_postgres) — do not edit on the node.
#
# Plain manifests, not a HelmChart — same pattern as
# roles/k3s_metallb/templates/metallb-config.yaml.j2: the Cluster CR below
# references a CRD (postgresql.cnpg.io) that only exists once the
# cloudnative-pg HelmChart (cnpg-operator.yaml, same manifests directory) has
# actually installed the operator, so it's applied here rather than gated
# behind a "wait for CRDs" step — k3s's deploy controller retries a manifest
# referencing not-yet-existing CRDs until they show up, instead of failing
# once and giving up. templates/postgres-superuser-secret.yaml.j2 relies on
# the Namespace created here the same way, for the same reason.
apiVersion: v1
kind: Namespace
metadata:
name: {{ k3s_postgres_namespace }}
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: shared-postgres
namespace: {{ k3s_postgres_namespace }}
spec:
# 1 primary + 1 replica (src/shared/postgres/ansible/kubernetes/vars.yml).
# CNPG's own default pod anti-affinity (preferred, topology key hostname)
# spreads them across distinct nodes; the nodeAffinity below narrows which
# nodes those can be.
instances: {{ pg_config.POSTGRES_INSTANCES }}
imageName: {{ k3s_postgres_image }}
# Password-based superuser login, from the Secret Ansible renders
# alongside this file. Off by default in CNPG — without this, the operator
# ignores the secret's content and sets the postgres user's password to
# NULL, disabling remote login as it.
#
# The name must stay clear of `<cluster>-superuser` (i.e.
# `shared-postgres-superuser`), which is what CNPG calls the secret it
# generates for itself when this stanza is absent. Claiming that name for
# our own object stops password changes reaching the database — see
# postgres-superuser-secret.yaml.j2 for the full symptom.
enableSuperuserAccess: true
superuserSecret:
name: shared-postgres-superuser-vault
# A LoadBalancer Service for the primary, on top of the ClusterIP -rw/-ro/-r
# Services CNPG creates for every Cluster. Declared here as a CNPG *managed
# service* rather than as a Service manifest of our own: selectorType: rw
# means the operator maintains the selector, so the LB follows a failover to
# the other instance the same way the built-in -rw Service does. A
# hand-written Service would need its selector re-pointed by hand after
# every promotion.
#
# This is what makes the cluster reachable from the Ansible controller at a
# fixed host:port, which is the thing per-app database provisioning needs —
# the community.postgresql tasks compose_stack/lxc_app use can't talk to a
# ClusterIP, and `kubectl port-forward` isn't a stable address. It does put
# the superuser on the LAN: acceptable here for the same reason the Unraid
# instance's published port is, and the LAN is the boundary either way.
managed:
services:
additional:
- selectorType: rw
serviceTemplate:
metadata:
name: shared-postgres-lb
{% if k3s_postgres_loadbalancer_ip %}
# Pinned rather than left to MetalLB's next-free pick, because
# this address ends up in config elsewhere (inventory, an app's
# vars.yml) and shouldn't move when the Service is recreated.
annotations:
metallb.universe.tf/loadBalancerIPs: "{{ k3s_postgres_loadbalancer_ip }}"
{% endif %}
spec:
type: LoadBalancer
# No ports: — the operator fills in Postgres's own (5432) from
# the same template it builds the -rw Service from; naming them
# here would only risk disagreeing with it.
# POSTGRES_PORT (common/vars.yml) is 5432, the same port CNPG always
# listens on inside the pod/Service — nothing to override here.
storage:
size: {{ pg_config.POSTGRES_STORAGE_SIZE }}
storageClass: {{ pg_config.POSTGRES_STORAGE_CLASS }}
resources:
{{ k3s_postgres_resources | to_nice_yaml(indent=2) | indent(4, first=true) }}
# Workers only — k3s-ctrl-01 stays free of app pods, same boundary the
# cluster already keeps for monitoring/MetalLB.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: DoesNotExist
# No bootstrap: stanza — CNPG's default initdb bootstrap creates a
# `postgres` superuser (above) plus a default `app` database owned by an
# auto-generated `app` role/secret. That default app database is unused
# today: no app is deployed onto k3s yet, and real per-app database
# provisioning here is a deferred design problem (see CLAUDE.md → "Key
# decisions") — the community.postgresql approach compose_stack/lxc_app
# use needs a controller-reachable host:port, which this cluster's
# in-cluster -rw Service isn't without further work.