What is the correct way to use feign with spring cloud kubernetes?

12/6/2019

I am using Spring Cloud Kubernetes and I am trying to make feign able to send requests based on the name of the services present in kubernetes, but I can't, when I try to make a request the following error occurs:

  "timestamp": "2019-12-06T15:37:50.285+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "com.netflix.client.ClientException: Load balancer does not have available server for client: poc-saldo",
    "trace": "java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: poc-saldo\n\tat org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute....

I tried to call other services within the cluster and the problem is the same for all of them, I did a test by going into the poc-deposit pod and doing a poc-balance curl and it works normally, so the problem is not with the poc-deposit service. balance or with kubernetes's service discovery apparently.

The project has a public profile at:

https://gitlab.com/viniciusxyz/spring-kubernetes-feign

For those who want more direct information:

My main class is as follows:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceDiscoveryApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceDiscoveryApplication.class, args);
    }

}

My interface with feign is as follows:

@FeignClient("poc-saldo")
public interface ProxyGenerico {

    @RequestMapping(method = RequestMethod.GET)
    String getHttpResponse();

}

I can list the services available in kubernetes within the application as follows:

@RestController
public class RestTest {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private ProxyGenerico proxyGenerico;

    @GetMapping("/services")
    public ResponseEntity<?> services()  {
        return new ResponseEntity<Object>(discoveryClient.getServices(), HttpStatus.OK);
    }

    @GetMapping("/pocsaldo")
    public ResponseEntity<?> gitlab()  {

        return new ResponseEntity<Object>(proxyGenerico.getHttpResponse(), HttpStatus.OK);
    }

}

And in this list I have several services among them the service I want to access called poc-balance, the return json looks like the following:

[

    "poc-deposito",
    "poc-saldo",
    "sonarqube",
    "sql-server-sonar",
    "zookeeper",
    "gitlab"

]

To complement the list follow my dependencies:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
</dependency>

The discoveryClient.getInstances ("poc-saldo") command returns:

[
    {
        "instanceId": "32a4db0d-0549-11ea-8850-e0d55ef66cf8",
        "serviceId": "poc-saldo",
        "secure": false,
        "metadata": {
            "helm.sh/chart": "spring-boot-app-0.1.23",
            "port.http": "8080",
            "app.kubernetes.io/managed-by": "Tiller",
            "app.kubernetes.io/name": "poc-saldo",
            "app.kubernetes.io/instance": "banco-digital-poc-saldo",
            "app.kubernetes.io/version": "1.0"
        },
        "port": 8080,
        "host": "10.42.0.60",
        "scheme": "http://",
        "uri": "http://10.42.0.60:8080"
    }
]

Can you think of where the problem might be?

-- Vinicius Vieira
java
kubernetes
spring-boot
spring-cloud
spring-cloud-feign

1 Answer

5/22/2020

If you use Ribbon for load balancing I see it in your error org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient

You need say Ribbon about real address all your microservices and use for this action discovery Client.

  1. Above your main class, add a few annotation

    @EnableDiscoveryClient
    @AutoConfigureAfter(RibbonAutoConfiguration.class)
    @RibbonClients(defaultConfiguration = RibbonConfiguration.class) 
    public class MyApp
    
  2. Add implementations yours dynamic Server List

    public class MyServerList extends AbstractServerList<Server> {
        private final DiscoveryClient discoveryClient;
        private IClientConfig clientConfig;
    
        public MyServerList(DiscoveryClient discoveryClient) {
            this.discoveryClient = discoveryClient;
        }
    
        @Override
        public List<Server> getInitialListOfServers() {
            return getUpdatedListOfServers();
        }
    
        @Override
        public List<Server> getUpdatedListOfServers() {
            Server[] servers = discoveryClient.getInstances(clientConfig.getClientName()).stream()
                .map(i -> new Server(i.getHost(), i.getPort()))
                .toArray(Server[]::new);
            return Arrays.asList(servers);
        }
    
        @Override
        public void initWithNiwsConfig(IClientConfig clientConfig) {
            this.clientConfig = clientConfig;
        } 
    } 
    
  3. Add RibbonConfiguration.class

    public class RibbonConfiguration {
    
        @Autowired
        private DiscoveryClient discoveryClient;
    
        @Bean
        @ConditionalOnMissingBean
        public ServerList<?> ribbonServerList(IClientConfig config) {
            MyServerList myserverLis = new MyServerList(discoveryClient);
            myserverLis.initWithNiwsConfig(config);
            return myserverLis;
        }
    }
    

and you can config in your app property refresh time I mean period of time call getUpdatedListOfServers

-- Oleksandr Ivanovich
Source: StackOverflow