grpc/grpc-go · error

rls: empty lookup_service in route lookup config %+v

Error message

rls: empty lookup_service in route lookup config %+v

What it means

Emitted from parseRLSProto (config.go:196) when RouteLookupConfig.lookup_service is the empty string. The lookup_service is the address of the Route Lookup Server and is required; without it the RLS balancer has nowhere to send RouteLookup RPCs.

Solutions

  1. Set lookupService to the URI of the Route Lookup Server, e.g. 'rls://rls.example.com:443' or 'dns:///rls.example.com:443'.
  2. If the value is templated, ensure the template variable is populated before the config is applied (fail the deploy if it is empty).
  3. Verify the xDS RouteLookupConfig resource on the control plane has lookup_service populated and that the resource version is active.

Example fix

// before
"routeLookupConfig": { "lookupService": "", "grpcKeybuilders": [ ... ] }

// after
"routeLookupConfig": { "lookupService": "dns:///rls.example.com:443", "grpcKeybuilders": [ ... ] }
Defensive patterns

Strategy: validation

Validate before calling

func validateLookupService(s string) error {
    if strings.TrimSpace(s) == "" {
        return errors.New("routeLookupConfig.lookupService must be non-empty")
    }
    return nil
}

Type guard

func hasLookupService(s string) bool { return strings.TrimSpace(s) != "" }

Prevention

When it happens

Trigger: The routeLookupConfig object omits lookupService/lookup_service, or sets it to an empty string. The check runs after key-builder validation and before URI parsing.

Common situations: An xDS RLS config where the lookup service hostname template was never filled in; a templated config whose ${RLS_HOST} variable expanded to empty; misconfiguration during initial rollout of RLS.

Related errors


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

Appendix: source

Thrown at balancer/rls/config.go:196

		return nil, err
	}
	lbCfg.childPolicyName = name
	lbCfg.childPolicyConfig = config
	lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName
	return lbCfg, nil
}

func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {
	// Validations specified on the `grpc_keybuilders` field are performed here.
	kbMap, err := keys.MakeBuilderMap(rlsProto)
	if err != nil {
		return nil, err
	}

	// `lookup_service` field must be set and must parse as a target URI.
	lookupService := rlsProto.GetLookupService()
	if lookupService == "" {
		return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
	}
	parsedTarget, err := url.Parse(lookupService)
	if err != nil {
		// url.Parse() fails if scheme is missing. Retry with default scheme.
		parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
		if err != nil {
			return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
		}
	}
	if parsedTarget.Scheme == "" {
		parsedTarget.Scheme = resolver.GetDefaultScheme()
	}
	if resolver.Get(parsedTarget.Scheme) == nil {
		return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
	}

	lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
	if err != nil {

View on GitHub (pinned to 0c51461d27)