Skip to content

Services and networking

How a webhook sender reaches the radar, how the radar reaches Postgres, and how to test from your laptop.

Overview

Pod IPs change every time a pod is replaced, so nothing in Kubernetes talks to a pod IP directly. Instead, traffic goes to a Service, a stable name and virtual IP that forwards to whichever pods currently match it. For the radar there are two connections to get right: Postgres to radar (the radar opens a connection to your database) and deploy sender to radar (ArgoCD, Flux, a CI job or an OpenTelemetry Collector posts events to the radar). This page covers both, plus kubectl port-forward for testing and NetworkPolicy for locking it down.

Services

A Service has a selector (which pods it targets, by label), ports and a type:

Type Reachable from Use
ClusterIP (default) inside the cluster only everything the radar exposes, by default
NodePort every node's IP on a high port quick tests on bare clusters
LoadBalancer outside, through a cloud load balancer exposing the webhook beyond the cluster
kubectl get service -n <ns>
kubectl describe service <fullname>-manager -n <ns>     # check the Endpoints line
kubectl get endpoints <fullname>-manager -n <ns>        # which pod IPs are behind it

Empty endpoints means the selector matches nothing

If Endpoints is <none>, the Service exists but sends traffic nowhere: either no pod matches its selector (a label mismatch) or no matching pod is Ready. This is the first thing to check when "the webhook times out".

What the radar exposes

In manager mode, the Service <fullname>-manager has three ports:

Port name Default What it serves
webhook 8080 deploy events: one route per DeploySource, at /webhook/<namespace>/<name>
pg-metrics 9090 Prometheus metrics derived from pg_stat_statements
ctrl-metrics 8443 controller-runtime's own reconcile and queue metrics

The health probes (8081) are not on the Service: the kubelet probes the pod directly. In operator mode, the Service <fullname> exposes webhook and metrics on service.webhookPort and service.metricsPort. The chart's default service.type is ClusterIP.

DNS: how names resolve

Every Service gets a DNS name inside the cluster:

<service>.<namespace>.svc.cluster.local

Within the same namespace the short name <service> is enough; from another namespace use <service>.<namespace>. The full form always works.

This is how you tell things where the radar is:

# a deploy sender in any namespace posting to the radar
http://<fullname>-manager.<ns>.svc.cluster.local:8080/webhook/<ns>/<deploysource-name>

# the radar's DSN pointing at a Postgres Service
postgres://pgrr_monitor:...@orders-rw.<ns>.svc.cluster.local:5432/orders

For CloudNativePG, the read-write endpoint is the Service named <cluster>-rw (and -ro, -r for replicas). Point the DSN at -rw for the primary, which is where pg_stat_statements lives that you want to watch.

In pg-regression-radar

A DeploySource creates a route in the manager; its path is reported in status.webhookPath: kubectl get deploysource <name> -n <ns> -o jsonpath='{.status.webhookPath}'. A DeploySource with sourceType: kubernetes has no webhook path, because the radar watches the workload itself and nothing needs to call it. Setting up senders is covered in Deploy Sources & Webhooks.

Testing from your laptop: port-forward

A ClusterIP Service is not reachable from your machine. kubectl port-forward opens a temporary tunnel through the API server:

# in one terminal: forward local port 8080 to the manager Service
kubectl port-forward -n <ns> service/<fullname>-manager 8080:8080

# in another: post a test deploy event
curl -X POST http://localhost:8080/webhook/<ns>/<deploysource-name> \
  -H 'Content-Type: application/json' \
  -d '{"app":"orders-api","namespace":"<ns>","revision":"abc123","timestamp":"2026-11-11T12:00:00Z"}'

Port-forward lasts only while the command runs, binds to localhost by default, and is for debugging, not for production access. The same works for metrics:

kubectl port-forward -n <ns> service/<fullname>-manager 9090:9090
curl -s localhost:9090/metrics | head

That payload matches a DeploySource with sourceType: generic; other source types expect their own payload shape (see Deploy Sources & Webhooks). If the request is refused with 401, the radar has a webhook secret configured: send it as -H 'X-Webhook-Token: <value>'. See Config and Secrets.

Reaching the radar from outside the cluster

Only expose the webhook endpoint when a sender outside the cluster needs it, and always set a webhook secret first. Two common routes:

  • An Ingress (or Gateway) in front of the Service, with TLS. The Ingress controller is cluster-specific; the radar needs nothing special from it other than forwarding the path to port 8080.
  • A LoadBalancer Service (--set service.type=LoadBalancer), which is simpler but exposes the port directly.

Either way the endpoint must be authenticated, because an open endpoint lets anyone inject deploy events and skew detection. The chart prints a warning on install when ingester.webhookSecret is empty.

NetworkPolicy

By default, any pod in a cluster can talk to any other pod. A NetworkPolicy restricts that: it selects pods by label and lists which ingress (incoming) and egress (outgoing) connections are allowed. Once any policy selects a pod for a direction, everything not listed for that direction is denied.

Two things you must know first:

  • Policies only work if your cluster's network plugin enforces them. On a cluster whose CNI does not, the objects are accepted and silently ignored.
  • The model is "default deny plus explicit allows". An empty policy that selects the radar's pods and lists no rules blocks all traffic to and from them. That is the right starting point, and it is also why a freshly applied policy "breaks" things until the allows are in.

The chart can render both parts for you (off by default):

helm upgrade --install radar deploy/helm/deploylens \
  --set mode=manager \
  --set networkPolicy.enabled=true \
  --set networkPolicy.egress.postgresCIDRs[0]=10.10.0.0/16 \
  --set networkPolicy.egress.apiServerCIDRs[0]=10.20.0.0/16

What the radar needs to be allowed, by direction:

Direction To / from Port Needed for
ingress your deploy senders, Prometheus 8080, 9090 webhooks and metrics
egress Postgres 5432 scraping pg_stat_statements
egress the Kubernetes API server (manager mode only) 443 / 6443 reconciling objects, leader election
egress cluster DNS 53 UDP/TCP resolving every name above
egress your alert destination 443 Slack, PagerDuty, relay

The most frequent mistake is forgetting DNS and the API server egress. A manager that cannot resolve names or reach the API server fails in ways that look unrelated to the network. The full options, and how to adjust the DNS namespace, are in Hardening (Helm).

kubectl get networkpolicy -n <ns>
kubectl describe networkpolicy <fullname>-manager-allow -n <ns>

See also