5.6 KiB
M6.4 — nginx TCP routing to agent-manager-db
| Field | Value |
|---|---|
| Phase | M6 — agent-manager migration |
| Size | S — <1 day |
| Status | ⬜ Not started |
| Flags | homelab |
| Spec | inlined below |
| Blocks | M6.1 |
Goal
A path from the Mac client running agent-manager to the cluster-internal,
ClusterIP-only agent-manager-db — through the shared ingress controller,
matching this homelab's existing pattern, not a raw LAN IP or a tunnel.
Facts (inlined — no spec read needed)
Decided, not open: dedicated ingress routing through nginx, not
kubectl port-forward/SSH tunnel and not a MetalLB LoadBalancer IP.
Matches the pattern already used elsewhere in this homelab of routing
through the shared ingress controller rather than exposing raw
per-service LAN IPs.
Postgres is not HTTP. The standard nginx-ingress Ingress resource is
HTTP(S)-oriented (host/path routing, TLS termination via SNI on 443).
Postgres speaks its own binary wire protocol on 5432. The ingress
controller needs stream {} block config (TCP/UDP passthrough) or a
dedicated TCP mode Service/ConfigMap entry — whichever the specific
nginx-ingress deployment in this cluster supports (check
k8s/infra/ingress/ for how it's deployed and whether tcp-services
ConfigMap wiring already exists for anything else, since this may be the
first TCP passthrough case in the cluster).
No existing precedent in this homelab — forgejo-db, authentik-db,
temporal-db are all consumed only by pods inside the same cluster over
their ClusterIP Service, never from outside. agent-manager-db is the
first case of an external (Mac) client needing to reach a CNPG cluster,
which is why this task exists as dedicated work rather than "just add a
Service."
Steps
- Confirm how nginx-ingress is deployed in this cluster (
k8s/infra/ ingress/) and whether it already exposes atcp-servicesConfigMap orstream {}snippet mechanism — ingress-nginx (the community controller) supports TCP passthrough via atcp-servicesConfigMap mapping<external-port>: <namespace>/<service>:<port>; confirm this is the controller in use before assuming that config shape. - Pick an external port for Postgres traffic (5432 is already the in-cluster default; an external port distinct from any other exposed service avoids collision — check what's already claimed).
- Add the
tcp-services(or equivalent) entry routing that external port toagent-manager-db-rw.agent-manager.svc.cluster.local:5432(CNPG's read-write Service name convention — confirm against the actual Service name M6.1'sClustergenerates). - Expose that port on the ingress controller's
Service/LoadBalancer(this is the one LAN-facing port for this whole feature — the DB itself stays ClusterIP-only, only the ingress controller's existing external IP gains a new port). - Commit, push to both remotes, verify which
repoURLthe owning ArgoCDApplicationwatches (same caveat as M6.1) before assuming a push landed, let ArgoCD sync. - Test connectivity from the Mac client:
psql postgresql://<user>@<homelab-ingress-host>:<external-port>/ agent_manager(credentials from M6.5).
Acceptance
psql(orpgx) from outside the cluster reachesagent-manager-dbthrough the ingress controller's external IP/port.agent-manager-db's own Service remains ClusterIP-only — noLoadBalancer/NodePortadded to it directly (that would defeat the point of routing through nginx).- TLS/auth on the connection is Postgres's own (
sslmode, password auth) — nginxstream {}passthrough does not terminate or inspect the Postgres protocol, so it adds no auth of its own. Confirm this is acceptable given the homelab's network boundary (LAN-only ingress exposure, not public internet) before treating it as done.
Verify
Harness: psql from the Mac client (outside the cluster network),
plus kubectl checks on the ingress controller's config.
Integration test — verify/m6.4.sh diffed against
expected/m6.4.txt:
a1_external_connects—psql postgresql://app@<ingress-host>:<port>/agent_manager -c 'select 1'from the Mac client succeeds.a2_db_service_still_clusterip—kubectl get svc -n agent-manager agent-manager-db-rw -o jsonpath='{.spec.type}'isClusterIP.a3_argocd_synced— the ingress-owning app isSynced/Healthyafter the config change.a4_wrong_port_refused— connecting to a random unmapped port on the same ingress host fails (proves the mapping is port-specific, not an accidental catch-all passthrough).
Command: bash verify/m6.4.sh | diff - expected/m6.4.txt
False pass:
- Testing connectivity from inside the cluster (e.g.
kubectl execinto a pod andpsqlthe ClusterIP directly). That was already true before this task and proves nothing about the ingress path — assertion 1 must run from the actual Mac client, outside the cluster network.
Traps
- Reusing port 5432 externally on the ingress controller's existing
external IP if anything else is already listening there (unlikely for
Postgres specifically, but worth a
kubectl get svc -n <ingress-ns> <ingress-svc> -o yamlcheck before assuming the port is free). stream {}/tcp-servicesconfig living outside the GitOps-tracked kustomization because it's a ConfigMap edit that "felt like a quick manual fix." Same hard rule as everything else: commit + push + ArgoCD sync, no manualkubectl applyto the ingress controller's config.
Background: M6.1 · k8s/infra/ingress/ (nginx-ingress deployment, controller type to confirm)