Filter services based on Eureka metadata with Zuul - spring-cloud

We use Spring Cloud Netflix Zuul along with Eureka in our environment.
I understand Ribbon obtains the server list from Eureka for specified serviceId. In our case we have 4 containers of a service, one of the container is the master which performs resource intense tasks.
I would like to filter out master container from list of servers so Ribbon doesnt forward any requests to the container.
How do I filter ?
Any help is appreciated.

I was able to filter by creating a new custom IRule
homeautomation.ribbon.NFLoadBalancerRuleClassName=com.example.CustomZoneAvoidanceRule
and extending the default ZoneAvoidanceRule
public class CustomZoneAvoidanceRule extends ZoneAvoidanceRule {
#Override
public Server choose(final Object key) {
final Server server = super.choose(key);
// code to skip specific instance based on eureka metadata.
return server;
}
}

Related

How to forward headers when using Zuul, Hystrix (and Feign) with Spring Cloud HATEOAS?

Context
My micro-services application is based on spring-cloud: a zuul gateway is configured in front of two micro-services: service-a and service-b.
One of my API requires that service-a requests service-b; I use feign for that.
Zuul send X-FORWARDED-* headers to the services, for them to rewrite the HATEOAS links correctly (when the services are configured with ForwardedHeaderFilter).
My problem is that the services communicate with each other using Feign, which relies on Hystrix.
Hystrix creates a new thread for each request (we don't use the SEMAPHORE config), so the request in Spring's RequestContextHolder is lost in the Feign request from service-a to service-b, I can't enrich the feign request with an feign interceptor anymore since the original request is lost.
Some potential solutions
Forwarding authorization token is now supported directly by Spring with the parameter hystrix.shareSecurityContext: true
There isn't any "out of the box" configuration to have Hystrix shares the request between threads.
A solution could be to implement my own HystrixConcurrencyStrategy, which is a class from netflix.hystrix.
My latest find is this Pull Request that has been sent to spring-cloud-netflix, but unfortunately not integrated.
I can try to copy the code of the Pull request, and create a bean, just as what "eacdy" wrote:
#Bean
public RequestAttributeHystrixConcurrencyStrategy hystrixRequestAutoConfiguration() {
return new RequestAttributeHystrixConcurrencyStrategy();
}
Is there an easier solution to forward the headers from Zuul with Hystrix?
I suppose that what I am trying to do is very standard when using Zuul, Hystrix, and HATEOAS micro-services that communicate with each other, so maybe there is something that exists already (and that I couldn't find)?
Thanks !
I thought it was quite a common thing to achieve, but after a lot of research, I couldn't find a way to forward the X-FORWARDED-* headers automatically with Feign and Hystrix.
So, I looked for another solution, which works and is quite clean:
In the Feign client from service-a to service-b, I declared a specific configuration "ServiceBFeignConfig", which, in addition to forward the token, also add the X-Forwarded-* headers corresponding to the gateway:
#Configuration
public class ServiceBFeignConfig {
#Autowired
private ApplicationProperties applicationProperties;
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
OAuth2AuthenticationDetails details =
(OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
if (applicationProperties.getFeign().getGatewayEnabled()) {
requestTemplate.header("X-Forwarded-Host", applicationProperties.getFeign().getGatewayHost());
requestTemplate.header("X-Forwarded-Port", applicationProperties.getFeign().getGatewayPort());
requestTemplate.header("X-Forwarded-Proto", applicationProperties.getFeign().getGatewayProtocol());
requestTemplate.header("X-Forwarded-Prefix", applicationProperties.getFeign().getServiceBPrefix());
}
}
};
}
}
You can see that the gateway host and port is configured in the properties files (that is served by Spring Cloud Config in my case). The service-b prefix is also set in these files.
These headers are only added if the "gatewayEnabled" property is set in the properties files.
You have to ignore this configuration from the component scan of Spring Boot, even if it needs the #Configuration annotation, so put it in a "ignorescan" package, and on your main Spring boot class, use:
#ComponentScan(basePackages = { "com.myservice" }, excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.myservice.ignorescan.*"))
At the end, the Forward headers will be added if you have the gatewayEnabled set to true, and the API call to the gateway get the correct HATEOAS links.

Communication between microservice using ServiceID from discovery instead of directory host?

I'm new microservice, I'm reading some example about discovery server, I see we can call another microservice api by using url like:
http://inventory-service/api/inventory/{productCode}.
"inventory-service" is a service instance I registered in discovery.
So my question is what is the benefit of using serviceId intead of call directory host:port:
http://localhost:9009/api/inventory/{productCode}.
Let asume you register inventory-service with Eureka server by configuring Eureka serviceUrl in src/main/resources/bootstrap.properties.
spring.application.name=inventory-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
Then build inventory-service and start 2 instances of it by running following commands.
java -jar -Dserver.port=9001 target/inventory-service-0.0.1-SNAPSHOT-exec.jar
java -jar -Dserver.port=9002 target/inventory-service-0.0.1-SNAPSHOT-exec.jar
When you visit Eureka Dashboard http://localhost:8761/ you will see 2 instances of inventory-service registered.
If you want to apply Client Load Balancing from your consumer application you would need a config like this:
server.ribbon.listOfServers=localhost:9001,localhost:9002
server.ribbon.eureka.enabled=false
If you want to start new instances you would need to register them in your consumer configuration.
With ServiceID you don't have to worry about it, because all instances will register with the same identifier. It will be added automatically in the list of available servers.It is one of the advantages of using ServiceId instead hostname

Is it possible to define a static server list with ribbon (via feign) when Eureka is in use?

Environment
Spring Boot 1.5.13.RELEASE
Spring Cloud Edgware.SR3
Java 8
Configuration
Eureka client is enabled and working correctly (I have tested and everything's working as I expect).
Some relevant properties from my configuration:
feign.hystrix.enabled=true
eureka.client.fetch-registry=true
spring.cloud.service-registry.auto-registration.enabled=true
service1.ribbon.listOfServers=https://www.google.com
Context
I have an application which speaks to 3 other services using feign clients. Two of these are discovered via Eureka service discovery. These are working well. The final service is an external one with a single static hostname and I do not want this resolved via Eureka. Since I do want Eureka for 2 of these services I would like to keep Eureka enabled.
Question
For the final service I tried adding service1.ribbon.listOfServers=https://www.google.com to the application.properties, however this cases the following error at runtime when invoking the feign client:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.netflix.hystrix.exception.HystrixRuntimeException: Service1Client#test() failed and no fallback available.] with root cause
pricing_1 |
pricing_1 | com.netflix.client.ClientException: Load balancer does not have available server for client: service1
pricing_1 | at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.2.5.jar!/:2.2.5]
My client is configured as follows:
#FeignClient("service1")
public interface Service1Client {
#GetMapping(value = "/")
String test();
}
Thanks in advance for any advice.
Consideration
Since the spirit of Ribbon as I understand it is to act as a client side load balancer and given in my case there is nothing to load balance (I have one fixed static hostname that returns a single A record in DNS). Ribbon actually feels like an unnecessary component - what I really wanted was the Feign client as I like the fact that it abstracts away the lower level HTTP request and object seralization. So I suppose an alternative follow up question is, can I use feign without ribbon - it seems the nice out of the box behaviour would be to use ribbon - even the Javadoc of the #FeignClient annotation says:
If ribbon is available it will be
used to load balance the backend requests, and the load balancer can be configured
using a #RibbonClient with the same name (i.e. value) as the feign client.
suggesting the two are quite closely related even if they are serving different purposes.
As you mentioned, there are two ways to solve your problem.
Use Feign without Ribbon
If you specify url attribute in #FeignClient annotation, it will work without Ribbon like the below.
#FeignClient(name = "service1", url = http://www.google.com)
public interface Service1Client {
#GetMapping(value = "/")
String test();
}
In this case, your other two Feign client will still work with Ribbon and Eureka.
Use Feign with Ribbon and without Eureka
What you are missing is in your configuration is NIWSServerListClassName.
Its default value is com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList and it will use Eureka to retrieve the list of server. If you set NIWSServerListClassName to ConfigurationBasedServerList for a ribbon client (feign client), only that client will work with listOfServers list without retrieving server list from Eureka. And other feign clients will still work with Eureka.
service1:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
listOfServers: http://www.google.com

I cant make rest calls with react

I am learning to use React at the moment, and I have a problem: I want react to work with my Java Spring Boot backend (Tomcat). React is running on port 3000 (default) and my Tomcat server on port 8080 (default). Now, when I make a REST call, I get the following error
script.js:13 GET http://localhost:8080/api/path?p=test net::ERR_CONNECTION_REFUSED
My rest call looks like:
fetch('http://localhost:8080/api/path?p=test')
.then(res => {
console.log(res);
});
What do I make wrong? I do not really have an idea.
An net::ERR_CONNECTION_REFUSED error is typically thrown when your frontend (React application) cannot connect to your backend (your Spring boot application). There are many possible reasons for this to happen, such as:
Your back-end application is not running
The path to your back-end application is not correct
There is some kind of firewall between your front- and back-end application that is stopping the traffic
...
In your case it appeared to be the back-end application that was not running properly.
Your second problem is related to CORS, which is a mechanism that prevents JavaScript from connecting to other APIs/websites that are not on the same (sub)domain and port. In your case your frontend is running on a different port than you backend, so that means you have to deal with CORS.
To handle CORS, you have to add a header to your backend (namely the Access-Contol-Allow-Origin header the error is mentioning). With Spring, you can do that by adding the following annotation to your controller:
#CrossOrigin(origins = "http://localhost:3000")
Or you can configure CORS globally with a filter or using WebMvcConfigurer:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedOrigins("http://localhost:3000");
}
};
}
As mentioned in the comments by #Lutz Horn, this is also described in a Spring guide.

Spring Cloud Feign Clients don't connect with a different Context Roots for different instances

I am in the process of evaluating Spring cloud with Eureka and the Feign Client with Ribbon.
My sample application I've been creating has a Eureka Server that receives the Clients just fine.
However, when using the FeignClient, the RequestMapping requires the Context Path to be included.
In my initial case, I had two services with the same VIP but different IDs.
- one service at localhost:8080/
- one service at localhost:7400/HelloWorld
The reason for the different context roots is due to potential deployment structure I can't easily change.
Is this something I need to configure within my Client? or Application or is this not possible?
Here is my #FeignClient
<code>
#Autowired
HelloClient client;
#FeignClient(value = "Hello-World")
interface HelloClient {
#RequestMapping(value = "/HelloWorld", method = RequestMethod.GET)
String hello();
}
</code>
Ideally, the RequestMapping would only be the value '/'