Skip to content

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_listener on :8080 and ingress_https_listener on :8443
  • A route table (substrate_routes) that runs every request through ext_proc.
  • A static ate-cluster pointing at ExtProc (:50051).
  • A dynamic_forward_proxy_cluster that follows whatever :authority the 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:

  1. Extracts the actor ID from :authority.
  2. Calls ateapi.ResumeActor(actorId) (dedup’d via singleflight).
  3. Receives the Actor object back with ateom_pod_ip.
  4. Returns a HeaderMutation telling the proxy to overwrite :authority with <pod_ip>:80. (The :80 is currently hardcoded - see the TODO(bowei) in extproc.go about 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:

  1. Watches the K8s API for relevant objects.
  2. Generates a CoreDNS Corefile that maps *.actors.resources.substrate.ate.devatenet-router ClusterIP.
  3. 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.