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
<actor_name>.<atespace>.actors.resources.substrate.ate.dev resolves to the
router.
What it does
flowchart LR
Client([Client]) -- "DNS:<br/>name.atespace.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"
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(the HTTPS listener is added only when a TLS port is configured).- A route table (
substrate_routes) whose single catch-all route forwards to thedynamic_forward_proxy_cluster; the HTTP connection manager runs every request through theext_procfilter first. - A static
ate-clusterpointing at ExtProc (default127.0.0.1:50051, HTTP/2). - A
dynamic_forward_proxy_clusterthat follows whatever:authoritythe proxy has after ExtProc rewrites it.
When an OTLP collector address is configured, the snapshot also carries an
otel_collector_cluster and the connection manager gets an OpenTelemetry
tracing provider so Envoy emits ingress spans.
The router’s controller loop runs every 5 seconds and re-snapshots after polling for ready templates (a periodic poll, not an informer-driven watch).
ExtProc server (:50051)
This is the brain of the routing. The L7 proxy hands every request’s headers off to ExtProc; ExtProc:
- Parses the
:authority/Hostheader into an(atespace, actor_name)pair from<actor_name>.<atespace>.actors.resources.substrate.ate.dev(port stripped). An unparseable host is answered with a 404. - Asks ateapi to resume that actor, referencing it by
ObjectRef{atespace, name}. Concurrent requests for the same actor collapse into a single resume. - Receives the
Actorback and readsateom_pod_ip. - Returns a
HeaderMutationtelling the proxy to overwrite:authoritywith<pod_ip>:80. (The:80is currently hardcoded; supporting other actor ports is a known follow-up.)
After that, the proxy’s dynamic forward proxy carries the request to the
worker. The server is instrumented with OTel: it extracts any inbound
traceparent from the request headers and starts a span so its work links to
the Envoy ingress span.
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. (Reconciliation of the proxy Deployment is skipped in standalone / file-backed modes.)
The DNS subsystem
Independent from the router - it’s a separate Cobra subcommand
(atenet dns) typically run as its own Deployment. The DNS controller
reconciles on an interval and:
- Looks up the
atenet-routeranddnsService ClusterIPs in theate-systemnamespace. - Generates a CoreDNS Corefile whose
templateplugin answers any<actor_name>.<atespace>.actors.resources.substrate.ate.devname (both labels are DNS-1123) with theatenet-routerClusterIP. - Writes the Corefile to the shared volume and signals CoreDNS to reload
(
SIGUSR1), and points GKE’skube-dnsConfigMapstubDomainsat thednsservice for the actor suffix.
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.
The request-coalescing optimization
If 50 simultaneous requests hit a cold actor, ExtProc would naïvely fire
50 ResumeActor calls at ateapi. Instead, concurrent requests keyed by
<atespace>/<actor_name> collapse: only the first goes through, the
rest piggyback on its result. The in-flight resume also runs under a detached
background timeout so a caller disconnecting doesn’t abort the resume for the
others, and Aborted responses (a concurrent resume) are retried with backoff.
Logging hygiene
The router’s status dashboard records recent requests, but it redacts the query string off recorded paths (it may carry credentials) before storing them. Secrets are never logged verbatim.
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.
- Atespace - why the DNS name carries an atespace.