Repository navigation
Description
Severity: P2 (Envoy Deployment drift not corrected during K8s API flap)
Component: Network Plane — cmd/atenet/internal/router/controller.go
Audit ID: NET-5
Summary
The atenet router's reconcile() function calls readyTemplates() first and returns
early if it fails. readyTemplates() performs a K8s API List across all namespaces —
an expensive, frequently-failing call during API server stress. The XDS snapshot and
the Envoy Deployment reconciliation (envoyRunner.reconcile()) do not depend on
template data, but both are gated behind this check. A Kubernetes API flap prevents
Envoy Deployment self-healing: if the Deployment is manually scaled to 0 during the
flap, it stays at 0 until the API recovers.
Root Cause
File: cmd/atenet/internal/router/controller.go lines 88–99
func (c *Controller) reconcile(ctx context.Context) error {
templates, err := c.atStore.readyTemplates(ctx)
if err != nil {
c.logger.Error("Failed to get ActorTemplates", "err", err)
return err // ← short-circuit: UpdateSnapshot and envoyRunner.reconcile NEVER RUN
}
if err := c.xdsServer.UpdateSnapshot(ctx, templates); err != nil { ... }
if err := c.envoyRunner.reconcile(ctx); err != nil { ... }
return nil
}The XDS snapshot update (UpdateSnapshot) uses only templates for route rules, but
an empty template list is a valid (safe) input — it just means no routes are advertised.
envoyRunner.reconcile() manages the Envoy Deployment and Service and does not use
templates at all.
Steps to Reproduce
- Simulate a Kubernetes API server flap:
# On kind: temporarily stop the API server container docker pause <kind-control-plane-container> sleep 30 # API unreachable for 30s docker unpause <kind-control-plane-container>
- During the flap, manually scale the Envoy Deployment to 0:
# Use raw kubectl before docker pause, queue the command kubectl scale deployment envoy -n ate-system --replicas=0 - After the API recovers, observe:
kubectl get deployment envoy -n ate-system # READY: 0/0 ← not recovered to 1/1 - Check controller logs —
reconcile()was returning early on every cycle during
the flap and never calledenvoyRunner.reconcile()after the API recovered. The
next successful reconcile (which includes readyTemplates) does fix it, but there's
a window where Envoy is down and all actor requests fail.
Expected Behavior
UpdateSnapshot and envoyRunner.reconcile() should not be gated on readyTemplates.
An empty template list is a safe input. A K8s API flap should never prevent Envoy
Deployment self-healing.
Suggested Fix
Split reconcile into two independent paths:
func (c *Controller) reconcile(ctx context.Context) error {
// Always runs — does not depend on templates
if err := c.envoyRunner.reconcile(ctx); err != nil {
c.logger.Error("Failed to reconcile Envoy", "err", err)
}
// Best-effort — failure does not block Envoy reconciliation
templates, err := c.atStore.readyTemplates(ctx)
if err != nil {
c.logger.Warn("Failed to get ActorTemplates — XDS snapshot not updated", "err", err)
return err
}
return c.xdsServer.UpdateSnapshot(ctx, templates)
}
Activity
[-]atenet controller reconcile short-circuits on Kubernetes API error — Envoy Deployment cannot self-heal[/-][+][P2] atenet controller reconcile short-circuits on Kubernetes API error — Envoy Deployment cannot self-heal[/+]EItanya commentedon Jul 30, 2026
I have a slightly different question. Why is envoy lifecycle owned by substrate at all and not just a part of the deployment artifacts, like in the helm chart or other. That seems like just another thing that can go wrong without providing a ton of value.
mikecook commentedon Oct 27, 2026
This no longer applies on
main. The code it describes was removed in two steps:envoyrunner.goand theenvoyRunner.reconcile()call. Envoy is now theenvoycontainer in theatenet-routerDeployment inmanifests/ate-install/atenet-router.yaml, so the Deployment controller keeps it running regardless of the router's API access. That also answers Eitan Yarmush (@EItanya)'s question above: the Envoy lifecycle is no longer owned by Substrate code.controller.go,atstore.go, andreadyTemplatesalong with the ActorTemplate CRD, so the early-return reconcile loop is gone.Proposing to close this.