PostgreSQL 1,289 has two kinds of legitimate client: the API and the schema Jobs (PostgreSQL StatefulSet and Migration Job), whose Pods carry an automatic batch.kubernetes.io/job-name label. One policy admits both and refuses the rest:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: postgres, labels: { app: booknest } }
spec:
podSelector: { matchLabels: { tier: db } }
policyTypes: [Ingress]
ingress:
- from:
- podSelector: { matchLabels: { tier: api } }
- podSelector:
matchExpressions: [{ key: batch.kubernetes.io/job-name, operator: Exists }]
ports: [{ port: 5432 }]kubectl apply -f k8s/network-policies.yaml
kubectl run dbcheck --image=localhost:33500/postgres:18 --restart=Never -- \
pg_isready -h postgres -t 3 >/dev/null 2>&1
kubectl create job dbcheck --image=localhost:33500/postgres:18 -- pg_isready -h postgres -t 3 \
2>/dev/null
kubectl wait --for=condition=Complete job/dbcheck >/dev/null
kubectl wait --for=jsonpath='{.status.phase}'=Failed pod/dbcheck >/dev/null
kubectl logs pod/dbcheck; kubectl logs job/dbcheck
kubectl delete pod/dbcheck job/dbcheck >/dev/null
git add k8s/network-policies.yaml
git commit -qm "Let only the API and Jobs reach PostgreSQL"Output
networkpolicy.networking.k8s.io/postgres created job.batch/dbcheck created postgres:5432 - no response postgres:5432 - accepting connections
The plain Pod got no response, the Job was accepted, and the API Pods stayed ready. The two podSelector entries under from are alternatives (API or Job), unlike the combined entry in Policy Rules. Any Job may connect, a deliberate trade, since only administrators and the pipeline (Pipeline RBAC) create Jobs here.