atenet
atenet is one binary that exposes two Cobra subcommands: atenet router
and atenet dns. Each subcommand is run as its own Deployment in
practice. The router does the per-request actor lookup via an L7 proxy + ExtProc;
the DNS controller programs CoreDNS so
<actorId>.actors.resources.substrate.ate.dev resolves to the router.
What it does
flowchart LR
Client([Client]) -- "DNS:<br/>actorId.actors.resources.substrate.ate.dev" --> DNS[atenet dns Deployment]
Client -- "HTTP" --> ROUTER[atenet router Deployment]
subgraph ROUTER_internals["Router subsystem"]
direction TB
XDS["xDS server :18000"]
EXT["ExtProc server :50051"]
ENVR["Proxy runner<br/>(K8s Deployment mgmt)"]
end
subgraph DNS_internals["DNS subsystem"]
DNSCTL[CoreDNS Corefile controller]
end
ROUTER --> XDS
ROUTER --> EXT
ROUTER --> ENVR
ENVR -- bootstrap --> ENVOY[(L7 proxy pods)]
XDS -- snapshot --> ENVOY
EXT -- ResumeActor --> API[ateapi]
click API "/components/ateapi/" "ateapi"
cmd/atenet/internal/app/router/router.go:84-94 ·
cmd/atenet/internal/app/dns/cmd.go:34-39
The Router subsystem
Three pieces sharing one process:
xDS server (:18000)
Serves the L7 proxy’s dynamic configuration. The snapshot it publishes contains:
ingress_http_listeneron:8080andingress_https_listeneron:8443- A route table (
substrate_routes) that runs every request through ext_proc. - A static
ate-clusterpointing at ExtProc (:50051). - A
dynamic_forward_proxy_clusterthat follows whatever:authoritythe proxy has after ExtProc rewrites it.
The router’s controller loop runs every 5 seconds and re-snapshots from
atStore.readyTemplates(...) (a periodic poll, not an informer-driven
watch).
cmd/atenet/internal/app/router/xds.go:42-249 ·
cmd/atenet/internal/app/router/controller.go:73-110
ExtProc server (:50051)
This is the brain of the routing. The L7 proxy hands every request’s headers off to ExtProc; ExtProc:
- Extracts the actor ID from
:authority. - Calls
ateapi.ResumeActor(actorId)(dedup’d via singleflight). - Receives the Actor object back with
ateom_pod_ip. - Returns a
HeaderMutationtelling the proxy to overwrite:authoritywith<pod_ip>:80. (The:80is currently hardcoded - see theTODO(bowei)inextproc.goabout supporting other ports.)
After that, the proxy’s dynamic forward proxy carries the request to the worker.
cmd/atenet/internal/app/router/extproc.go:125-175 ·
cmd/atenet/internal/app/router/extproc_out.go:36-44
Proxy runner
atenet manages a K8s Deployment of L7 proxy pods as part of its own reconciliation - it’s not a sidecar pattern, the proxy pods are a separate fleet that this process keeps healthy. Bootstrap config points the proxies at the xDS server above.
cmd/atenet/internal/app/router/envoyrunner.go:31-222
The DNS subsystem
Independent from the router - it’s a separate Cobra subcommand
(atenet dns) typically run as its own Deployment. The DNS controller:
- Watches the K8s API for relevant objects.
- Generates a CoreDNS Corefile that maps
*.actors.resources.substrate.ate.dev→atenet-routerClusterIP. - Updates a ConfigMap or sends a reload to CoreDNS.
The trick: DNS answers point at the router, not at any worker. Worker IPs change too often for DNS to be a good source of truth.
internal/dns/dns.go:71-88 ·
internal/dns/corefile.go:42-59
The singleflight optimization
If 50 simultaneous requests hit a cold actor, ExtProc would naïvely fire
50 ResumeActor calls at ateapi. A singleflight.Group keyed by actor ID
collapses them: only the first goes through, the rest piggyback on its
result.
cmd/atenet/internal/app/router/resumer.go:29-93
Why ship router + DNS in one binary?
They share Go packages (CRD types, K8s clients, logging) and are versioned together, but each is its own Cobra subcommand with its own Deployment. Splitting them into separate binaries would be a packaging change, not an architectural one.
Related
- Request path flow - the warm-path mechanics.
- Resume actor flow - the cold-path mechanics through atenet.
- ateapi - atenet’s only gRPC dependency.