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
- Set lookupService to the URI of the Route Lookup Server, e.g. 'rls://rls.example.com:443' or 'dns:///rls.example.com:443'.
- If the value is templated, ensure the template variable is populated before the config is applied (fail the deploy if it is empty).
- 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
- Fail the deploy if the RLS host template variable is unset.
- Treat lookupService as a required field in config schema validation.
- Log the effective lookupService value at startup for quick diagnosis.
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
- rls: bad control channel service config
- rls: bad RouteLookupConfig proto %+v
- rls: cache_size_bytes must be set to a non-zero value: %+v
- rls: childPolicyConfigTargetFieldName field is not set in…
- rls: invalid childPolicy: entry
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)