Repository navigation
Topology routing: per-Pod Service membership, default backends, private Knative, and every Internet entrypoint - #2045
Conversation
Pods group by app label, so one group can hold stable and canary Pods that
different Services select. The traffic view unioned the group's Services
and drew every one to every Pod, and summary mode gave each Service the
health of every Pod in the group. Membership is now tracked per Pod: Pod
edges, summary counts and group expansion use each Pod's own Services, and
a Service selecting only part of a group labels its edge ("4 of 7 pods").
A backend with serversTransport replaced the route → Service edge with a route → transport → Service chain of configuration edges. The Service lost the route from its Routes, focused topology stopped at the transport, two routes sharing a transport looked joined through it, and the traffic view (which keeps route → Service) disagreed with the resources view. The route now always routes to its backend, and the transport is configuration of each route that uses it.
Topology read only rule paths: an Ingress whose only backend is spec.defaultBackend had no edge in either view (though its AI context listed the Service), and an Ingress with several paths to one Service emitted the same edge ID once per path. Topology and resource context now share one backend extraction that includes the default backend and lists each Service once.
The traffic view connected the Internet to every Knative Service. A Service labelled networking.knative.dev/visibility=cluster-local (or made private through its Route, which publishes a <name>.<ns>.svc URL) is only reachable inside the cluster; it now has no Internet edge.
Grouping replaced the Internet node with one per group, but rebuilt edges only to Ingresses and Gateways: Istio Gateways, Knative Services, Traefik routes and Contour HTTPProxies lost their Internet connection. Regrouping now moves each original Internet edge, whatever its entrypoint kind, and leaves an ungrouped entrypoint on the original node.
The missing-Service check read only rule paths, so an Ingress whose default backend names a missing Service passed (and one Service missing from several paths was reported once per path). The Ingress drawer warned "Traffic will not be routed" for an Ingress with only a default backend and never showed it. Both now use the default backend: the check through the shared backend extraction, the drawer as a Default Backend row.
The transport → route configuration edge was resolved after the backend Service lookup and skipped with it.
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
PR Summary by QodoCorrect topology routes, Pod membership, and Internet entrypoints
AI Description
Diagram
High-Level Assessment
Files changed (15)
|
Code Review by Qodo
1. Default backends get the wrong edge type
|
…al URL An Ingress whose default backend is a resource (not a Service) lost its routing warning without gaining a row; it now shows the resource. A Knative URL counts as cluster-local only at <name>.<ns>.svc.cluster.local, so a public domain that starts with "svc." keeps its Internet edge; the visibility label covers other cluster domains.
…s do A Knative Service is private when its published URL is its cluster-local address, on any cluster domain, and a DomainMapping publishes even a private one. An HTTP route's ServersTransport resolves in its backend's namespace, a TCP route's in the route's, and a TraefikService backend has none.
|
Qodo review of 9e677a7:
|
For finding 1, the concern is specifically the rule’s required semantic type: Ingress-to-Service exposure relationships should use The finding remains open because the recommended fix targets both default and rule-path Ingress edges, plus the relationship projection—not only the newly added default-backend edges. You can update the rule’s scope or disable it here: https://app.qodo.ai/rules/3036572?state=active The resource-backend finding is already resolved, as noted. |
A selector such as role: "" selects only Pods that carry the role label with an empty value. Radar compared labels[k] with the value, so a missing key read as "" and matched, and the Service was drawn to Pods it doesn't select. Require the key in both selector helpers, as Kubernetes does.
A cluster-local Knative Service is now drawn without an Internet edge, but nothing on the node said why, so it looked disconnected by mistake. A private Service published through a DomainMapping showed its cluster-local URL next to an Internet edge. Mark cluster-local Services with a badge, and show a mapped Service's custom domain, with every domain and the internal URL in its tooltip. When DomainMappings can't be read, the badge is withheld, since the Service may be mapped.
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
Radar's Related Resources section, and the MCP resource context given to AI tools, put many relationships under labels that were false for them: - An IngressRoute listed its backend Service under **Pods**. - A VirtualService listed its Istio Gateway under **Services**. - A Service listed the HTTPRoutes in front of it under **Gateways**. - A Traefik Middleware showed the route that uses it as its **Configuration**, the reverse of the truth. - A ServiceMonitor appeared under **Used By** on everything it scrapes. These come from an audit of every place Radar builds topology edges. This PR makes each group's label true. It builds on #2045 (merged), which owns the Traefik routing edges this PR also touches. ## What changed **Configuration edges point from configuration to consumer.** A `configures` edge means "source configures target", and the projection reads it that way. These producers drew it from consumer to configuration, so their labels came out swapped: - Karpenter NodePool → NodeClass. - Traefik routes → Middleware, TLSOption, TLSStore, ServersTransport and Certificate. - Middleware chains. - ServersTransport, TLSOption and TLSStore → Secrets. - Contour HTTPProxy → TLS Secret and Certificate. They're now reversed, so a route lists its middleware under Configuration and the middleware lists the route under Used By. **A Knative front door exposes its Knative Service.** Knative's Route creates a core Service named after the Knative Service: the alias for the ingress that callers address. Radar drew it as the Knative Service's backend, matched by name alone. It's now drawn as the Service that exposes the Knative Service, and only when the Route of that name owns it. The Knative Service lists it under Services, and it lists the Knative Service under Routes To. **New groups for relationships that had none.** Each matches on exact API group. All are additive fields on `Relationships`, with drawer labels: | Field | Label | What it holds | |---|---|---| | `backends` | Routes To | what a Service, router, Istio Gateway, Knative Route, Broker or event source routes to (Pods stays for real Pods) | | `routedFrom` | Routed From | upstream routers that aren't core Services or recognized routes (Istio Gateway, Broker, event source) | | `monitors` / `monitorTargets` | Monitored By / Monitors | Prometheus Operator ServiceMonitor and PodMonitor, both directions | | `protects` | Selects | what a disruption budget or network policy selects (previously not shown at all) | | `healthChecks` | Health Checks | a CAPI Cluster's MachineHealthChecks | | `stagedPolicies` | Staged Policies | Calico staged policies, kept apart from enforcing Network Policies | Workload history attaches staged policies alongside NetworkPolicies, so a staged policy's changes stay on the workload's timeline. A Knative Service's chip reads `ksvc/` rather than `svc/`, so it isn't mistaken for the core Services listed beside it. **Existing groups tightened:** - **Services:** only core Services. - **Gateways:** only Gateway API Gateways. HTTP, GRPC, TCP, TLS and UDP routes go to **Routes**. - **Routers:** no longer list themselves or their sibling routes as their own upstream. **MCP resource context.** `exposes` merged both directions, so an Ingress "exposed" its Service and the Service "exposed" the Ingress. Directional `exposedBy` and `backends` fields are added, along with `monitors`, `monitorTargets`, `protects`, `healthChecks` and `stagedPolicies`. `exposes` stays as their union for compatibility. Each field applies the caller's read permissions, and `docs/mcp.md` describes them. Applications and the workload view's serving path keep counting the entries that moved. ## Result on live clusters I ran this PR and `main` against real Traefik 3.7, Contour 1.33, Knative 1.23 and Istio installs, and compared Related Resources for the same objects: | Resource | `main` | This PR | |---|---|---| | IngressRoute `edge-web` | **Pods:** Service `whoami` · **Used By:** its Middleware | **Routes To:** Service `whoami` · **Configuration:** ServersTransport, Middleware | | Middleware `strip-api` | **Configuration:** the route using it | **Used By:** the route | | HTTPProxy `shop` | **Pods:** Service, child HTTPProxy · **Used By:** its TLS Secret | **Routes To:** Service, child HTTPProxy · **Configuration:** its TLS Secret | | Istio VirtualService `productpage` | **Pods:** Service · **Services:** Istio Gateway | **Routes To:** Service · **Routed From:** Istio Gateway | | Knative Route `hello-public` | **Pods:** Revision | **Routes To:** Revision | Applications output was identical on both builds for every namespace checked. | IngressRoute drawer, `main` | IngressRoute drawer, this PR | |---|---| |  |  | ## Testing - Builder-level projection tests for each item, plus drawer and workload-view render tests and resource-context tests. Each fails with its fix reverted. - `pkg` and `internal` Go tests, `make tsc`, and the shared UI and web suites pass. ## Notes - **Version skew:** a newer UI against an older Radar renders the old fields as before. An older UI against this Radar loses only the entries that moved into the new groups. - **Scope:** the Traefik TLS-Secret and traffic-view configuration producers don't exist in the builder, so they weren't invented here. - **Follow-up:** a semantic relation field on edges, which would replace classification by Kind and group, is tracked in RAD-695.
The traffic view drew routes that don't exist and dropped some that do:
These come from an audit of every place Radar builds topology edges; this PR fixes the routing and membership findings.
What changed
Pod membership is per Pod, not per group. Pods group by app label, so one group can hold stable and canary Pods that different Services select. The traffic view took the union of the group's Services and drew every one to every Pod.
PodGroup.PodServiceIDs).ownerIdsthe resources view uses for mixed-owner groups.Selectors require their keys. A selector value of
""matches only Pods that carry the label with an empty value, as in Kubernetes. Radar read a missing key as"", so a Service selectingrole: ""was drawn to Pods without the label. This applies to every selector match in topology.Every Internet entrypoint stays connected. Regrouping the Internet node now moves each original Internet edge, whatever the entrypoint kind, and leaves an ungrouped entrypoint on the original node. The logic moved into a pure, tested helper,
internet-groups.ts.Ingress default backends. Several readers ignored
spec.defaultBackend. One extraction (configrefs.IngressBackendServices: the default backend plus every rule path, each Service once) now feeds:The Ingress drawer shows a Default Backend row, for a Service or a resource backend, and no longer warns for an Ingress that routes everything to its default backend.
Private Knative Services. A Knative Service is private when it has the label
networking.knative.dev/visibility=cluster-local, or when its published URL is its cluster-local address (status.urlhost equalsstatus.address.urlhost). Knative builds a private Route's URL from the same cluster-local template as its address, on any cluster domain, so this also covers a label set on the Route. A DomainMapping that targets the Service or its Route publishes it again. Private Services get no Internet edge (private services, DomainMapping).The node says why:
Traefik ServersTransport configures a route; it doesn't replace its backend. A backend with
serversTransportused to become route → transport → Service, so the Service lost the route from its Routes, focused topology stopped at the transport, and two routes sharing a transport looked joined. Now:serversTransport, so it gets no transport edge.Result on a live cluster
A kind cluster with 7
shopPods (5 stable, 2 canary) behind three Services, 3cartPods behind stable and canary Services, Ingresses with a default backend, three repeated paths and missing Services, Traefik routes sharing a transport, and public, private and domain-mapped Knative Services. Same cluster,mainvs this PR:ownerIds.gonethree times toghost-defaultandgoneonce each.On a real Knative 1.23 install (EKS), the traffic view looks like this:
Testing
pkgandinternalGo tests,make tsc, the shared UI and web unit suites.Notes
ns-name@kubernetescrd) and transports inside TraefikService weighted or mirroring lists aren't resolved;