Generic service routing rule with Spring Cloud Gateway - spring-cloud

I'm trying to make a generic route rule to my services using Spring Cloud Gateway and Consul. Services are correctly registered in Consul and the Gateway is finding them with no problem.
The problem is, I want to make a generic rule to match my services. For example, I do have a hello-service and goodbye-service and the following RouteLocator on my gateway:
#SpringBootConfiguration
#EnableAutoConfiguration
public class ConsulGatewayApplication {
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.path("/hello-service/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://hello-service")
)
.route(r -> r.path("/goodbye-service/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://goodbye-service")
)
.build();
}
public static void main(String[] args) {
SpringApplication.run(ConsulGatewayApplication.class, args);
}
}
Now Image an environment with a lot of microservices, with the current solution I have to make a routing rule for each of them.
What I want is make a generic and more simple routing rule, something like a wild card. If the path contains *-service, it should send to the specified service, such as:
If the path is /a-service/, it should route to the a-service;
If the path is /b-service/, it should route to the b-service and so on;
Without explicit making a rule for a-service and b-service.
Is this possible?

Related

How Can I configure separate resilience4j circuit breaker for each instance registered under the SAME Eureka service

Here is my scenario:
Eureka Server: MY-APP-SERVICE with 3 instances of same spring boot app running on e.g localhost:8080, localhost:8081 and localhost:8082
OpenFeign client (Annotated as follow)
#FeignClient(name="MY-APP-SERVICE")
public interface MyFeignClient {...}
I have a proxy controller e.g. MyAppServiceClientController as follow:
#RestController
public class MyAppServiceClientController {
Autowired
MyFeignClient myFeignClient;
#CircuitBreaker(name = "backendA")
public String doSomething() {
return myFeignClient.doSomething();
}
}
When FeignClient asks Eureka registry for the service MY-APP-SERVICE it gets all 3 instances; it seems like resilience4j has only 1 circuit breaker at service level, here MY-APP-SERVICE.
How can I configure/annotate my FeignClient to have a separate resilience4j circuit breaker for each instance/URL in my MY-APP-SERVICE ?

How to share a kafka connector and its configuration between multiple services in Quarkus?

I've got the following project structure:
project
- serviceA
- serviceB
- serviceC
- serviceD
And I would like to have a common kafka connector in all the project services. Something like this:
public class CommonProcessor {
private final CommonService commonService;
public CommonProcessor(final CommonService commonService) {
this.commonService= commonService;
}
#Incoming("channel-name")
public CompletionStage<Void> process(final Message<CommonMessage> message) {
final CommonMessage commonMessage = message.getPayload();
commonService.commonMethod(commonMessage.getAttribute());
return message.ack();
}
}
With its corresponding configuration:
mp.messaging.incoming.channel-name.connector=smallrye-kafka
mp.messaging.incoming.channel-name.topic=channel-name
mp.messaging.incoming.channel-name.value.deserializer=foo.bar.CommonDeserializer
How could I share this CommonProcessor (and its configuration) between all services?
Can I put it in a common jar and add it as a dependency in all services? I've tried this option but it does not work because of the configuration. When starting the service with the dependency it says Impossible to bind mediators, some media
tors are not connected: [foo.bar.CommonProcessor#process]
Can it be done through an extension? In this case, I have the same question, how can I share the configuration in application.properties?

Discovery / Registration only works when K8s pod already registered.

Thanks for Spring Boot Admin!
I am using it with Spring Cloud Kubernetes, our k8s pods only get discovered when we start the Spring Boot Admin, after the service pods have been started.
It seems looking at InstanceDiscoveryListener, the discovery of clients,
will happen based on events. Like ApplicationReadyEvent (When starting) and i.e. InstanceRegisteredEvent.
Is it correct to say, that Spring Boot Admin, will not try to discover periodically? If so how do I make sure an event is fired from the application to Spring boot admin picks it up and registers the instance?
Especially to make sure, that instanced are registered when they are started after spring boot admin, was started. (The order in which k8s pods are started is arbitrary/hard to control, something in general we don't want to do).
Thank You!
Christophew
Version:
springBootAdminVersion = '2.0.1'
springCloudVersion = 'Finchley.RELEASE'
springCloudK8s = '0.3.0.RELEASE'
Not sure if this is the best way to solve it but seems to work:
class TimedInstanceDiscoveryListener extends InstanceDiscoveryListener {
private static final Logger log = LoggerFactory.getLogger(TimedInstanceDiscoveryListener.class);
public TimedInstanceDiscoveryListener(DiscoveryClient discoveryClient, InstanceRegistry registry, InstanceRepository repository) {
super(discoveryClient, registry, repository);
log.info("Starting custom TimedInstanceDiscoveryListener");
}
#Scheduled(fixedRate = 5000)
public void periodicDiscovery() {
log.info("Discovering new pod / services");
super.discover();
}
}
#Bean
#ConfigurationProperties(prefix = "spring.boot.admin.discovery")
public InstanceDiscoveryListener instanceDiscoveryListener(ServiceInstanceConverter serviceInstanceConverter,
DiscoveryClient discoveryClient,
InstanceRegistry registry,
InstanceRepository repository) {
InstanceDiscoveryListener listener = new TimedInstanceDiscoveryListener(discoveryClient, registry, repository);
listener.setConverter(serviceInstanceConverter);
return listener;
}

loadbalanced ribbon client initialization against discovery service (eureka)

I have service which runs some init scripts after application startup (implemented with ApplicationListener<ApplicationReadyEvent>). In this scripts I need to call another services with RestTemplate which is #LoadBalanced. When the call to service is invoked there's no information about instances of remote service because discovery server was not contacted at that time (I guess).
java.lang.IllegalStateException: No instances available for api-service
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:79)
So is there way how to get list of available services from discovery server at application startup, before my init script will execute?
Thanks
edit:
The problem is more related to fact, that in current environment (dev) all services are tied together in one service (api-service). So from within api-service I'm trying to call #LoadBalanced client api-service which doesn't know about self? Can I register some listener or something similar to know when api-service (self) will be available?
here are the sample applications. I'm mainly interested how to have working this method
edit2:
Now there could be the solution to create EurekaListener
public static class InitializerListener implements EurekaEventListener {
private EurekaClient eurekaClient;
private RestOperations restTemplate;
public InitializerListener(EurekaClient eurekaClient, RestOperations restTemplate) {
this.eurekaClient = eurekaClient;
this.restTemplate = restTemplate;
}
#Override
public void onEvent(EurekaEvent event) {
if (event instanceof StatusChangeEvent) {
if (((StatusChangeEvent) event).getStatus().equals(InstanceInfo.InstanceStatus.UP)) {
ResponseEntity<String> helloResponse = restTemplate.getForEntity("http://api-service/hello-controller/{name}", String.class, "my friend");
logger.debug("Response from controller is {}", helloResponse.getBody());
eurekaClient.unregisterEventListener(this);
}
}
}
}
and then register it like this:
EurekaEventListener initializerListener = new InitializerListener(discoveryClient, restTemplate);
discoveryClient.registerEventListener(initializerListener);
However this is only executed only when application is registered to discovery service first time. Next time when I stop the api-service and run it again, event is not published. Is there any other event which can I catch?
Currently, in Camden and earlier, applications are required to be registered in Eureka before they can query for other applications. Your call is likely too early in the registration lifecycle. There is an InstanceRegisteredEvent that may help. There are plans to work on this in the Dalston release train.

annotation #RibbonClient not work together with RestTemplate

I am trying Ribbon configuration with RestTemplate based on bookmark service example but without luck, here is my code:
#SpringBootApplication
#RestController
#RibbonClient(name = "foo", configuration = SampleRibbonConfiguration.class)
public class BookmarkServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BookmarkServiceApplication.class, args);
}
#Autowired
RestTemplate restTemplate;
#RequestMapping("/hello")
public String hello() {
String greeting = this.restTemplate.getForObject("http://foo/hello", String.class);
return String.format("%s, %s!", greeting);
}
}
with error page as below:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Mar 22 19:59:33 GMT+08:00 2016
There was an unexpected error (type=Internal Server Error, status=500).
No instances available for foo
but if I remove annotation #RibbonClient, everything will be just ok,
#RibbonClient(name = "foo", configuration = SampleRibbonConfiguration.class)
and here is SampleRibbonConfiguration implementation:
public class SampleRibbonConfiguration {
#Autowired
IClientConfig ribbonClientConfig;
#Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
#Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
Is it because RibbonClient can not work with RestTemplate together?
and another question is that does Ribbon configuration like load balancing rule could be configured via application.yml configuration file?
as from Ribbon wiki, seems we can configure Ribbon parameters like NFLoadBalancerClassName, NFLoadBalancerRuleClassName etc in property file, does Spring Cloud also supports this?
I'm going to assume you're using Eureka for Service Discovery.
Your particular error:
No instances available for foo
can happen for a couple of reasons
1.) All services are down
All of the instances of your foo service could legitimately be DOWN.
Solution: Try visiting your Eureka Dashboard and ensure all the services are actually UP.
If you're running locally, the Eureka Dashboard is at http://localhost:8761/
2.) Waiting for heartbeats
When you very first register a service via Eureka, there's a period of time where the service is UP but not available. From the documentation
A service is not available for discovery by clients until the
instance, the server and the client all have the same metadata in
their local cache (so it could take 3 heartbeats)
Solution: Wait a good 30 seconds after starting your foo service before you try calling it via your client.
In your particular case I'm going to guess #2 is likely what's happening to you. You're probably starting the service and trying to call it immediately from the client.
When it doesn't work, you stop the client, make some changes and restart. By that time though, all of the heartbeats have completed and your service is now available.
For your second question. Look at the "Customizing the Ribbon Client using properties" section in the reference documentation. (link)