grpc/grpc-go · error

extproc: failed to create channel to the external processor…

Error message

extproc: failed to create channel to the external processor server %q: %v

What it means

Returned by getOrCreateExtProcChannel (ext_proc.go:312) when iextproc.CreateExtProcChannel fails to dial the external processor server for the given TargetURI. The interceptor cannot be built, so BuildClientInterceptor returns an error and the RPC fails before reaching the dataplane unless failure_mode_allow semantics apply at a higher layer.

Solutions

  1. From the client pod, verify reachability: grpcurl or nc to the ext_proc server target URI/port.
  2. Check the ext_proc server is deployed and its Service resolves (kubectl get endpoints, nslookup).
  3. Confirm channel credentials and CA in the grpc_service match what the ext_proc server presents.
  4. Correct the TargetURI in the xDS grpc_service (use dns:/// or xds:/// scheme as intended) and republish.
  5. If dial failures are transient, ensure the client retries/resends the RPC so a fresh interceptor build redials.

Example fix

// before: unreachable target in xDS config
grpc_service { target_uri: "ext-proc:50000" } // wrong port / no DNS

// after: correct, resolvable target
grpc_service { target_uri: "dns:///ext-proc.extproc-system.svc.cluster.local:443" }
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: dial the ext_proc target before relying on it, then close.
func preflightExtProcTarget(ctx context.Context, target string) error {
    ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()
    cc, err := grpc.DialContext(ctx, target, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        return fmt.Errorf("ext_proc target %q unreachable: %w", target, err)
    }
    cc.Close()
    return nil
}

Try / catch

// In the gRPC client: a failed BuildClientInterceptor surfaces as an RPC error.
// Retry the RPC so the channel redials; pair with failure_mode_allow for resilience.
for attempt := 0; attempt < 3; attempt++ {
    err := conn.Invoke(ctx, method, req, resp)
    if err == nil { break }
    st, _ := status.FromError(err)
    if st.Code() != codes.Internal || !strings.Contains(st.Message(), "failed to create channel") {
        break
    }
    time.Sleep(backoff(attempt))
}

Prevention

When it happens

Trigger: Triggered at client-interceptor build time when dialing server.TargetURI fails: DNS lookup error, TCP/TLS handshake failure, no route to host, bad authority, unsupported channel credentials, or the ext_proc server is down.

Common situations: Ext_proc deployment not running or scaled to zero, wrong service DNS name in the xDS config, mTLS/CA mismatch between client and ext_proc server, network policy/firewall blocking the port, or a target URI scheme the resolver does not support.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/79a73044db67cfe4. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/httpfilter/extproc/ext_proc.go:312

// one if it doesn't exist.
func (cf *clientFilter) getOrCreateExtProcChannel(server xdsresource.GRPCServiceConfig) (*grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient], error) {
	// Create the grpcServiceKey.
	key := grpcServiceKey{
		targetURI:          server.TargetURI,
		channelCredentials: server.ChannelCredentials,
		callCredentials:    server.CallCredentials,
	}

	// If the channel for the key is present in the map and its refcount is
	// greater than 0, increment the refcount and return the channel.
	if rc := cf.getProcChannel(key); rc != nil {
		return rc, nil
	}

	// Create the external processor channel without holding the lock.
	cc, cancel, err := iextproc.CreateExtProcChannel(server)
	if err != nil {
		return nil, fmt.Errorf("extproc: failed to create channel to the external processor server %q: %v", server.TargetURI, err)
	}

	client := v3procservicegrpc.NewExternalProcessorClient(cc)
	// Create a new refcounted client. The onZero cleanup function will remove the
	// client from the map and close the underlying channel.
	var rc *grpcsync.RefCounted[v3procservicegrpc.ExternalProcessorClient]
	rc = grpcsync.NewRefCounted(&client, func() {
		cf.removeProcChannel(key, rc)
		cancel()
	})

	// Double-check if another goroutine created and stored a channel for this
	// key while we were unlocked.
	if existing := cf.storeProcChannel(key, rc); existing != rc {
		rc.Decrement()
		return existing, nil
	}
	return rc, nil

View on GitHub (pinned to 0c51461d27)