grpc-ecosystem / grpc-spring

Spring Boot starter module for gRPC framework.
https://grpc-ecosystem.github.io/grpc-spring/
Apache License 2.0
3.52k stars 826 forks source link

Failed to create channel: user-service. Invalid DNS name: userservice:5000 #871

Closed henryfung3a27 closed 1 year ago

henryfung3a27 commented 1 year ago

The context

I have a python gRPC server service running on docker. I can call the methods from the host with a python client. Now I want to have another SprintBoot client service to call the methods. Both the Python server and SpringBoot client run on docker.

I have the following application.properties:

grpc.client.user-service.address=dns:///userservice:5000
grpc.client.user-service.enable-keep-alive=true
grpc.client.user-service.keep-alive-without-calls=true
grpc.client.user-service.negotiation-type=plaintext

And the following Service:

package com.example.demo2.service;

import org.springframework.stereotype.Service;
import com.ex.grpc.UserManagerGrpc.UserManagerBlockingStub;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.client.inject.GrpcClient;

@Service
@Slf4j
public class GrpcService {

    @GrpcClient("user-service")
    private UserManagerBlockingStub stub;

    public void testStub() {
        try {
            // calling toString() for NPE test
            log.info("stub = {}", stub.toString());
        } catch (Exception e) {
            log.error("stub error", e);
        }
    }

}

The question

I get the following error on startup.

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'grpcService': Error creating bean with name 'grpcService' defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/example/demo2/service/GrpcService.class]: Failed to create channel: user-service
       at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:712) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:692) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:133) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:481) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1408) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[spring-context-6.0.6.jar!/:6.0.6]
       at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.6.jar!/:6.0.6]
       at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[spring-boot-3.0.4.jar!/:3.0.4]
       at com.example.demo2.Demo2Application.main(Demo2Application.java:10) ~[classes!/:2.1.0]
       at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
       at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
       at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
       at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
       at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) ~[app.jar:2.1.0]
       at org.springframework.boot.loader.Launcher.launch(Launcher.java:95) ~[app.jar:2.1.0]
       at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) ~[app.jar:2.1.0]
       at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:65) ~[app.jar:2.1.0]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'grpcService' defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/example/demo2/service/GrpcService.class]: Failed to create channel: user-service
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:606) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1405) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1325) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:709) ~[spring-beans-6.0.6.jar!/:6.0.6]
       ... 28 common frames omitted
Caused by: java.lang.IllegalStateException: Failed to create channel: user-service
       at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:218) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processFields(GrpcClientBeanPostProcessor.java:148) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.postProcessBeforeInitialization(GrpcClientBeanPostProcessor.java:125) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:420) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1754) ~[spring-beans-6.0.6.jar!/:6.0.6]
       at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.0.6.jar!/:6.0.6]
       ... 37 common frames omitted
Caused by: java.lang.IllegalArgumentException: Invalid DNS name: userservice:5000
       at com.google.common.base.Preconditions.checkArgument(Preconditions.java:220) ~[guava-31.1-android.jar!/:na]
       at io.grpc.internal.DnsNameResolver.<init>(DnsNameResolver.java:171) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.DnsNameResolverProvider.newNameResolver(DnsNameResolverProvider.java:62) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.DnsNameResolverProvider.newNameResolver(DnsNameResolverProvider.java:45) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.NameResolverRegistry$NameResolverFactory.newNameResolver(NameResolverRegistry.java:168) ~[grpc-api-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:749) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:781) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.ManagedChannelImpl.<init>(ManagedChannelImpl.java:661) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:631) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at io.grpc.internal.AbstractManagedChannelImplBuilder.build(AbstractManagedChannelImplBuilder.java:297) ~[grpc-core-1.51.0.jar!/:1.51.0]
       at net.devh.boot.grpc.client.channelfactory.AbstractChannelFactory.newManagedChannel(AbstractChannelFactory.java:140) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[na:na]
       at net.devh.boot.grpc.client.channelfactory.AbstractChannelFactory.createChannel(AbstractChannelFactory.java:108) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at net.devh.boot.grpc.client.channelfactory.InProcessOrAlternativeChannelFactory.createChannel(InProcessOrAlternativeChannelFactory.java:86) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:213) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
       ... 42 common frames omitted

The application's environment

Which versions do you use?

Additional information

I have read https://github.com/yidongnan/grpc-spring-boot-starter/pull/775#issuecomment-1329023335 to set the properties.

I have read https://github.com/grpc/grpc/blob/master/doc/naming.md as well and I have been tweaking the address property and tried below

dns:///userservice:5000
static://userservice:5000
userservice:5000
ipv4:userservice:5000

But they all give different errors.

henryfung3a27 commented 1 year ago

Extra context: these services are defined in the same docker-compose.yml and they are in the same subnet. They can ping each other with ping <container_name>.


After a bit more tweak, I found that using IP address instead of the container name in the address property works.

$ docker inspect my-network gives the IP addresses.

grpc.client.user-service.address=dns:///172.20.0.5:5000     // <----
grpc.client.user-service.enable-keep-alive=true
grpc.client.user-service.keep-alive-without-calls=true
grpc.client.user-service.negotiation-type=plaintext

However, shouldn't it resolve the name itself? I used the same manner to connect to databases which are running on docker as well and they worked well.

spring.data.mongodb.host=mongodb
spring.data.mongodb.port=27017

I think setting the grpc address with the IP address is not ideal because they change. Is there any solution to hep resolving the name to their IP address?

henryfung3a27 commented 1 year ago

When I set

grpc.client.user-service.address=static://userservice:5000

I get

Caused by: java.lang.IllegalArgumentException: hostname can't be null
    at java.base/java.net.InetSocketAddress.checkHost(InetSocketAddress.java:158) ~[na:na]
    at java.base/java.net.InetSocketAddress.<init>(InetSocketAddress.java:225) ~[na:na]
    at net.devh.boot.grpc.client.nameresolver.StaticNameResolverProvider.of(StaticNameResolverProvider.java:76) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    at net.devh.boot.grpc.client.nameresolver.StaticNameResolverProvider.newNameResolver(StaticNameResolverProvider.java:53) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    at io.grpc.NameResolverRegistry$NameResolverFactory.newNameResolver(NameResolverRegistry.java:168) ~[grpc-api-1.51.0.jar!/:1.51.0]
    at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:749) ~[grpc-core-1.51.0.jar!/:1.51.0]
    at io.grpc.internal.ManagedChannelImpl.getNameResolver(ManagedChannelImpl.java:781) ~[grpc-core-1.51.0.jar!/:1.51.0]
    at io.grpc.internal.ManagedChannelImpl.<init>(ManagedChannelImpl.java:661) ~[grpc-core-1.51.0.jar!/:1.51.0]
    at io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:631) ~[grpc-core-1.51.0.jar!/:1.51.0]
    at io.grpc.internal.AbstractManagedChannelImplBuilder.build(AbstractManagedChannelImplBuilder.java:297) ~[grpc-core-1.51.0.jar!/:1.51.0]
    at net.devh.boot.grpc.client.channelfactory.AbstractChannelFactory.newManagedChannel(AbstractChannelFactory.java:140) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[na:na]
    at net.devh.boot.grpc.client.channelfactory.AbstractChannelFactory.createChannel(AbstractChannelFactory.java:108) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    at net.devh.boot.grpc.client.channelfactory.InProcessOrAlternativeChannelFactory.createChannel(InProcessOrAlternativeChannelFactory.java:86) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:213) ~[grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar!/:2.14.0.RELEASE]
    ... 42 common frames omitted
ST-DDT commented 1 year ago

That is strange. userservice:5000 looks totally fine to me. Please debug passing the actual service address to

https://github.com/yidongnan/grpc-spring-boot-starter/blob/8b74fe7510770dc39cd22af1f8e3c4f146dc9de6/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java#L51-L56

(e.g. in a unit-test) and check whether and why host would be null. I'm unable to reproduce it in my setup.

As for the dns resolver not working: Please ask that over at grpc-java and link it here, so I know what went wrong there for the future.

henryfung3a27 commented 1 year ago

Hmmm, very interesting outcome I have found.

https://github.com/yidongnan/grpc-spring-boot-starter/blob/8b74fe7510770dc39cd22af1f8e3c4f146dc9de6/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java#L65-L82

At line 71

final URI uri = URI.create("//" + host);

Where host is the plain text of the input.

host uri.getHost() uri.getPort()
userservice:5000 userservice 5000
user-service:5000 user-service 5000
user_service:5000 null -1

image

Looks like underscore _ is not allowed in the URI string. It cannot be parsed and thus the host and port are null.

The reason my example used userservice is because the original name is something else related to my company work and I chose not to disclose it. (facepalm) What a mistake.


Seems like underscore is acceptable according to rfc3986 (January 2005). But not in Java(?)

2.3.  Unreserved Characters

   Characters that are allowed in a URI but do not have a reserved
   purpose are called unreserved.  These include uppercase and lowercase
   letters, decimal digits, hyphen, period, underscore, and tilde.

      unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
henryfung3a27 commented 1 year ago

In this case, I think there is no issue relating to this repo. I am closing this issue. Thanks a lot.

ST-DDT commented 1 year ago

I created a PR to document this limitation in the docs: #911