tailscale/tailscale · error

failed to get EndpointSlice: %w

Error message

failed to get EndpointSlice: %w

What it means

Thrown by the egress EndpointSlice reconciler when controller-runtime's Get of the EndpointSlice fails with an error other than NotFound (NotFound is handled one line above and returns cleanly). Any error reaching this line is infrastructure-level: API server connectivity problems, RBAC denying discovery.k8s.io EndpointSlices get, throttling, or a canceled context.

Source

Thrown at cmd/k8s-operator/egress-eps.go:50

}

// Reconcile reconciles an EndpointSlice for a tailnet service. It updates the EndpointSlice with the endpoints of
// those ProxyGroup Pods that are ready to route traffic to the tailnet service.
// It compares tailnet service state stored in egress proxy state Secrets by containerboot with the desired
// configuration stored in proxy-cfg ConfigMap to determine if the endpoint is ready.
func (er *egressEpsReconciler) Reconcile(ctx context.Context, req reconcile.Request) (res reconcile.Result, err error) {
	lg := er.logger.With("Service", req.NamespacedName)
	lg.Debugf("starting reconcile")
	defer lg.Debugf("reconcile finished")

	eps := new(discoveryv1.EndpointSlice)
	err = er.Get(ctx, req.NamespacedName, eps)
	if apierrors.IsNotFound(err) {
		lg.Debugf("EndpointSlice not found")
		return reconcile.Result{}, nil
	}
	if err != nil {
		return reconcile.Result{}, fmt.Errorf("failed to get EndpointSlice: %w", err)
	}
	if !eps.DeletionTimestamp.IsZero() {
		lg.Debugf("EnpointSlice is being deleted")
		return res, nil
	}

	// Get the user-created ExternalName Service and use its status conditions to determine whether cluster
	// resources are set up for this tailnet service.
	svc := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      eps.Labels[LabelParentName],
			Namespace: eps.Labels[LabelParentNamespace],
		},
	}
	err = er.Get(ctx, client.ObjectKeyFromObject(svc), svc)
	if apierrors.IsNotFound(err) {
		lg.Infof("ExternalName Service %s/%s not found, perhaps it was deleted", svc.Namespace, svc.Name)
		return res, nil

View on GitHub (pinned to cfe32b8be6)

Solutions

  1. Verify the operator ClusterRole includes verbs=[get,list,watch] on discovery.k8s.io/endpointslices
  2. Check apiserver health and client-side rate limiting (client-side QPS/burst settings)
  3. Do nothing special for single occurrences — returning the error makes controller-runtime requeue with exponential backoff
  4. If persistent, inspect operator logs for the wrapped error text and fix that root cause
Defensive patterns

Strategy: try-catch

Type guard

// Distinguish handled not-found from real infrastructure errors
func isInfraErr(err error) bool {
	return err != nil && !apierrors.IsNotFound(err)
}

Try / catch

// Go: return the error; controller-runtime requeues with exponential backoff
err = er.Get(ctx, req.NamespacedName, eps)
if apierrors.IsNotFound(err) {
	return reconcile.Result{}, nil
}
if err != nil {
	return reconcile.Result{}, fmt.Errorf("failed to get EndpointSlice: %w", err)
}

Prevention

When it happens

Trigger: The operator's service account lacks get on endpointslices (RBAC regression); API server temporarily unreachable or overloaded (429/503); watch/cache resync races surfacing as transient read failures; request context canceled during reconciliation.

Common situations: Deploying a new operator version without the updated ClusterRole; apiserver disruption causing reconcile storms; large clusters where list/watch pressure produces throttled reads.

Related errors


AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15). Data as JSON: /api/errors/3ab384ec0ad5c6fe. Report an issue: GitHub.