spring boot 2 + feign + eureka client wont resolve service-name to URL

12.1k Views Asked by At

I am trying spring-boot (2.0.5) with spring-cloud (Finchley.SR1) and trying to setup communication between two services using Eureka as discovery server and Feign/Ribbon as clients. The setup is quite straightforward (although a bit muddied by the various things and other answers I tried):

Eureka's application.yml

spring:
  application:
    name: eureka-service
server:
  port: 8761
eureka:
  instance:
    hostname: localhost
    preferIpAddress: true
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka

second service's bootstrap.yml

spring:
  application:
    name: secondservice
eureka:
  instance:
    hostname: ${spring.application.name}
    preferIpAddress: true
    instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
    statusPageUrlPath: ${server.servlet.context-path}/actuator/info
    healthCheckUrlPath: ${server.servlet.context-path}/actuator/health
    leaseRenewalIntervalInSeconds: 15
    leaseExpirationDurationInSeconds: 45
    metadata-map:
      server.servlet.context-path: ${server.servlet.context-path}
  client:
    enabled: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka

my test/template service's bootstrap.yml

spring:
  application:
    name: templateservice
eureka:
  instance:
    hostname: ${spring.application.name}
    preferIpAddress: true
    instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
    statusPageUrlPath: ${server.servlet.context-path}/actuator/info
    healthCheckUrlPath: ${server.servlet.context-path}/actuator/health
    leaseRenewalIntervalInSeconds: 15
    leaseExpirationDurationInSeconds: 45
    metadata-map:
      server.servlet.context-path: ${server.servlet.context-path}
  client:
    enabled: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka
logging:
  level:
    com...MessageServiceClient: DEBUG

My Feign client

@FeignClient(name = "secondservice", configuration = FeignConfig.class)
public interface MessageServiceClient {
    @RequestMapping(method = RequestMethod.GET, value = "/dummy")
    public String getMessage();
}

My Service class:

@Autowired MessageServiceClient messageServiceClient;
@Autowired private LoadBalancerClient loadBalancer;
public String getDummyMessage() {
    ServiceInstance instance = loadBalancer.choose("secondservice");
    URI secondServiceUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));

    System.out.println(secondServiceUri); // logs http://192.168.0.205:8090, check log below

    return messageServiceClient.getMessage(); // throws 404??
}

In FeignConfig, only thing being done is setting log level to FULL. The log looks like this:

2018-10-08 11:14:59.511  INFO [templateservice,,,] 16801 --- [onPool-worker-2] s.c.a.AnnotationConfigApplicationContext : Refreshing SpringClientFactory-secondservice: startup date [Mon Oct 08 11:14:59 IST 2018]; parent: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@451f35ad
2018-10-08 11:14:59.683  INFO [templateservice,,,] 16801 --- [onPool-worker-2] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2018-10-08 11:15:00.042  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: secondservice.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-10-08 11:15:00.095  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-secondservice
2018-10-08 11:15:00.146  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.netflix.loadbalancer.BaseLoadBalancer  : Client: secondservice instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=secondservice,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2018-10-08 11:15:00.189  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2018-10-08 11:15:00.287  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.netflix.config.ChainedDynamicProperty  : Flipping property: secondservice.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-10-08 11:15:00.291  INFO [templateservice,,,] 16801 --- [onPool-worker-2] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client secondservice initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=secondservice,current list of Servers=[192.168.0.205:8090, 192.168.0.205:8090],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:2;   Active connections count: 0;    Circuit breaker tripped count: 0;   Active connections per server: 0.0;]
},Server stats: [[Server:192.168.0.205:8090;    Zone:defaultZone;   Total Requests:0;   Successive connection failure:0;    Total blackout seconds:0;   Last connection made:Thu Jan 01 05:30:00 IST 1970;  First connection made: Thu Jan 01 05:30:00 IST 1970;    Active Connections:0;   total failure count in last (1000) msecs:0; average resp time:0.0;  90 percentile resp time:0.0;    95 percentile resp time:0.0;    min resp time:0.0;  max resp time:0.0;  stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@3625959e
http://192.168.0.205:8090
2018-10-08 11:15:01.215  INFO [templateservice,,,] 16801 --- [erListUpdater-0] c.netflix.config.ChainedDynamicProperty  : Flipping property: secondservice.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2018-10-08 11:15:07.951 DEBUG [templateservice,,,] 16801 --- [onPool-worker-2] c.e.m.t.clients.MessageServiceClient     : [MessageServiceClient#getMessage] ---> GET http://secondservice/dummy HTTP/1.1
2018-10-08 11:15:12.527 DEBUG [templateservice,,,] 16801 --- [onPool-worker-2] c.e.m.t.clients.MessageServiceClient     : [MessageServiceClient#getMessage] <--- HTTP/1.1 404 (4575ms)
2018-10-08 11:15:12.559 ERROR [templateservice,7004692c56b2e643,7004692c56b2e643,false] 16801 --- [nio-8080-exec-4] o.s.c.s.i.web.ExceptionLoggingFilter     : Uncaught exception thrown

The issue is, this throws a 404, of course cause the url it is trying to hit is http://secondservice/dummy, and there is no such thing. It might help noting, that if I set url on the FeignClient, it works, but then what is the point of Eureka? Also, for when this works, would fiegn clients automatically use the context path? or I have to specify it in every url on client?

UPDATE 1: Response to: http://localhost:8761/eureka/apps/secondservice Response from Eureka to second service

3

There are 3 best solutions below

1
NikhilWanpal On BEST ANSWER

Found it! Its nothing to do with discovery, or config in general, it is because feign does not support context path!

In an attempt to 'dumb' down, I went on removing every single config to bare-minimum to keep the services up. It suddenly worked when I removed the context path of the second service. Thing is Feign+Ribbon do not support custom context path if set by other service. This is an old bug, still not fixed.

There are two possible solutions:

  1. Remove context path.
  2. Add context path in your Feign clients. So basically your Feign client becomes:

// This needs to be here for the formatting below to be right

@FeignClient(name = "secondservice/secondservice", configuration = FeignConfig.class)
public interface MessageServiceClient {
    @RequestMapping(method = RequestMethod.GET, value = "/dummy")
    public String getMessage();
}

I personally don't like either of the solutions. I like to have context path, well, to give context to a url, it becomes self-explanatory with a context. But it is a property of the other service (secondservice) and should be chosen/changed by that service. And so should not be hardcoded in dependent services. I would have liked it to be supported, but meanwhile, I am going to go for:

@FeignClient(name = "${dependencies.secondservice.url}")
public interface MessageServiceClient {....}

And in application.properties: dependencies.secondservice.url=secondservice/secondservice. This makes it clear that the property is owned by the dependency and not by this service.

Few more notes: 1. I could trace the request to SynchronousMethodHandler#executeAndDecode, response = client.execute(request, options);. Till here the url is note resolved. 2. The url being logged: GET http://secondservice/secondservice/dummy is in fact the correct URL, the first secondservice string gets replaced with IP after the log statement. Here is the documentation supporting that: https://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#_using_ribbon. Notice the url passed to restTemplate. This is what triggered the search for alternative causes.

0
Amey Joshi On

I change the value of eureka.client.serviceUrl.defaultZone, from both services properties file. Then I replaced localhost with IP Address and it worked for me.

Hope it helps?

0
Ivan Polovyi On

I had the same problem when I used a different implementation of a FeignClient. In my case I used OkHttpClient. The project had a class annotated with @Configuration containing the following method

@Bean
public OkHttpClient client() {
    return new OkHttpClient();
}

When I removed this method it started to work.