Files
poimen-memory/tasks/M6.4-nginx-stream-routing.md
T

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 homelabforgejo-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

  1. Confirm how nginx-ingress is deployed in this cluster (k8s/infra/ ingress/) and whether it already exposes a tcp-services ConfigMap or stream {} snippet mechanism — ingress-nginx (the community controller) supports TCP passthrough via a tcp-services ConfigMap mapping <external-port>: <namespace>/<service>:<port>; confirm this is the controller in use before assuming that config shape.
  2. 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).
  3. Add the tcp-services (or equivalent) entry routing that external port to agent-manager-db-rw.agent-manager.svc.cluster.local:5432 (CNPG's read-write Service name convention — confirm against the actual Service name M6.1's Cluster generates).
  4. 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).
  5. Commit, push to both remotes, verify which repoURL the owning ArgoCD Application watches (same caveat as M6.1) before assuming a push landed, let ArgoCD sync.
  6. Test connectivity from the Mac client: psql postgresql://<user>@<homelab-ingress-host>:<external-port>/ agent_manager (credentials from M6.5).

Acceptance

  • psql (or pgx) from outside the cluster reaches agent-manager-db through the ingress controller's external IP/port.
  • agent-manager-db's own Service remains ClusterIP-only — no LoadBalancer/NodePort added to it directly (that would defeat the point of routing through nginx).
  • TLS/auth on the connection is Postgres's own (sslmode, password auth) — nginx stream {} 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 testverify/m6.4.sh diffed against expected/m6.4.txt:

  1. a1_external_connectspsql postgresql://app@<ingress-host>:<port>/agent_manager -c 'select 1' from the Mac client succeeds.
  2. a2_db_service_still_clusteripkubectl get svc -n agent-manager agent-manager-db-rw -o jsonpath='{.spec.type}' is ClusterIP.
  3. a3_argocd_synced — the ingress-owning app is Synced/Healthy after the config change.
  4. 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 exec into a pod and psql the 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 yaml check before assuming the port is free).
  • stream {} / tcp-services config 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 manual kubectl apply to the ingress controller's config.

Background: M6.1 · k8s/infra/ingress/ (nginx-ingress deployment, controller type to confirm)