I am trying to use spring cloud gateway with kubernetes service discovery. Below is the setup which i am using
build.gradle
plugins {
id 'org.springframework.boot' version '2.2.0.BUILD-SNAPSHOT'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
ext {
set('springCloudVersion', "Hoxton.BUILD-SNAPSHOT")
set('springCloudKubernetesVersion', "1.0.3.RELEASE")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes'
implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-ribbon'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "org.springframework.cloud:spring-cloud-kubernetes-dependencies:${springCloudKubernetesVersion}"
}
}
test {
useJUnitPlatform()
}
application.yml
spring:
application.name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
kubernetes:
reload:
enabled: true
server:
port: 8080
logging:
level:
org.springframework.cloud.gateway: TRACE
org.springframework.cloud.loadbalancer: TRACE
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
enabled: true
info:
enabled: true
DemoApplication.java
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class DemoApplication {
#Autowired
private DiscoveryClient discoveryClient;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#GetMapping("/services")
public List<String> services() {
return this.discoveryClient.getServices();
}
}
Spring cloud gateway is not able to redirect the request to other services. The log being printed is
2019-10-13 18:29:38.303 TRACE 1 --- [or-http-epoll-2]
o.s.c.g.f.WeightCalculatorWebFilter : Weights attr: {} 2019-10-13
18:29:38.305 TRACE 1 --- [or-http-epoll-2]
o.s.c.g.h.RoutePredicateHandlerMapping : No RouteDefinition found
for [Exchange: GET http://gateway-url/service-name/hello]
Although when I call http://<gateway-url>/services, then I can see the list of all services. So all the permission is being provided at pod level and service discovery is working fine. I am pretty sure there is some configuration, which i am missing but I am not able to figure it out even after looking at documentation several times.
So, it looks like there is an issue in Spring Cloud Hoxton.M3 release, as it's working fine with Hoxton.M2.
I have opened an issue for the same.
Related
I ask for your patience if this is a trivial question. I have googled it, and I have not found the answer.
I'm following a tutorial for Spring event driven development. In this moment, the goal is to have an agnostic/portable implementation of an event driven implementation.
For this, I have 2 Spring Boot projects, one producing/publishing topics and the other consuming the topics.
The both projects are created to manage RabbitMQ and Kafka without no code changes (or this is the intention).
With RabbitMQ everything works fine, but in Kafka it does not work. The problem seems to be that Kafka producer is adding the project name as prefix in the topic, and the consumer does not know this prefix.
Configuration:
Producer project application.yml (only relevant parts)
spring.cloud.stream:
bindings:
output-products:
destination: products
producer:
required-groups: auditGroup
---
spring.config.activate.on-profile: kafka
spring.cloud.stream.kafka.binder.brokers: kafka
management.health.rabbit.enabled: false
spring.cloud.stream.defaultBinder: kafka
spring.zipkin.sender.type: kafka
spring.kafka.bootstrap-servers: kafka:9092
Consumer project application.yml (only relevant parts)
spring.cloud.stream.bindings.input:
destination: products
group: productsGroup
---
spring.config.activate.on-profile: kafka
spring.cloud.stream.kafka.binder.brokers: kafka
management.health.rabbit.enabled: false
spring.cloud.stream.defaultBinder: kafka
spring.zipkin.sender.type: kafka
spring.kafka.bootstrap-servers: kafka:9092
For the producer class, I have created an interface and declared as a bean
public interface MessageSources {
String OUTPUT_PRODUCTS = "output-products";
String OUTPUT_RECOMMENDATIONS = "output-recommendations";
String OUTPUT_REVIEWS = "output-reviews";
#Output(OUTPUT_PRODUCTS)
MessageChannel outputProducts();
#Output(OUTPUT_RECOMMENDATIONS)
MessageChannel outputRecommendations();
#Output(OUTPUT_REVIEWS)
MessageChannel outputReviews();
}
And later, I use this bean to publish the topic
#EnableBinding(ProductCompositeIntegration.MessageSources.class)
#Component
public class ProductCompositeIntegration {
public Product createProduct(Product body) {
messageSources.outputProducts().send(MessageBuilder.withPayload(new Event(CREATE, body.getProductId(), body)).build());
return body;
}
For the consumer class binding, I use the Sink.class (remember, I want a portable solution)
#EnableBinding(Sink.class)
public class MessageProcessor {
private static final Logger log = LoggerFactory
.getLogger(MessageProcessor.class);
private final ProductService productService;
#Autowired
public MessageProcessor(ProductService productService) {
this.productService = productService;
}
#StreamListener(target = Sink.INPUT)
public void process(Event<Integer, Product> event) {
log.info("Process message created at {}...", event.getEventCreatedAt());
switch (event.getEventType()) {
....
With everything in place, and configured for RabbitMQ, this work fine. But when I try with Kafka, I get the error:
Dispatcher has no subscribers for channel 'product-composite-1.output-products'
Where product-composite is the name of the producer project.
For reference, this is the listing of topics automatically created
bash-4.4# kafka-topics.sh --zookeeper zookeeper:2181 --list
__consumer_offsets
error.products.productsGroup
error.recommendations.recommendationsGroup
error.reviews.reviewsGroup
products
recommendations
reviews
zipkin
So, it seems that the kafka library, with autoconfiguration active, is not able to connect to the topics:
spring.cloud.stream.bindings.<messagechannel>.destination: <topicname>
The problem solved when I upgraded the library versions:
NOT WORKING
plugins {
id 'org.springframework.boot' version '2.4.0'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext {
set('springCloudVersion', "2020.0.0-M5")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-stream'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
implementation 'org.springframework.kafka:spring-kafka'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
testImplementation 'org.springframework.kafka:spring-kafka-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
WORKING
plugins {
id 'org.springframework.boot' version '2.4.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext {
set('springCloudVersion', "2020.0.0")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-amqp'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-stream'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka'
implementation 'org.springframework.cloud:spring-cloud-stream-binder-rabbit'
implementation 'org.springframework.kafka:spring-kafka'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
testImplementation 'org.springframework.kafka:spring-kafka-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
I'm using Eclipse 2020-03, Gradle and Tomcat. all I did follows.
Installing gradle through eclipse marketplace.
making gradle project.
adding those on build.gradle dependencies
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:28.2-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.27'
compile group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.27'
compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.4.0-b180830.0359'
compile group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.27'
}
adding "rest" package on src/main/java
adding ApplicationConfig.java and RestApiService.java on rest package.
ApplicationConfig.java
package rest;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
#ApplicationPath("/api")
public class ApplicationConfig extends Application {
#Override
public Map<String, Object> getProperties(){
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("jersey.config.server.provider.packages", "A_UnivG.rest");
return properties;
}
}
RestApiService.java
package rest;
import java.util.logging.Logger;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import Data.*;
#Path("/Data")
public class RestApiService {
Logger logger = Logger.getLogger("RestApiService");
IntegrationDAO dao = new IntegrationDAO();
#GET
#Path("hello")
#Produces(MediaType.TEXT_PLAIN)
public String getHello() {return "Hello";}
}
and when I try to request http://localhost:portnumber/A_UnivG/api/Data/hello it spits out only 404 error.
cannot figure out why.
My project works with plain jsp files. I have a JSP page which uses DAO Read but it works just fine. well, that shouldn't be a problem I just tried hello world and it doesn't work at all.
I have a simple setup with gradle and Open Liberty. From this blogpost https://openliberty.io/blog/2018/12/14/microprofile21-18004.html
it says
"Open Liberty now has reactive extensions to JAX-RS 2.1 so that you can use providers from Apache CXF and Jersey"
But I can't get it to work with Jersey
I've tried various configurations of my build.gradle file and server.xml but still can't get it to work and find documentation lacking.
build.gradle
apply plugin: 'war'
apply plugin: 'liberty'
apply plugin: 'java-library'
group = 'x.y.z'
version = '1.0-SNAPSHOT'
description = "X Y Z"
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'net.wasdev.wlp.gradle.plugins:liberty-gradle-plugin:2.6.3'
}
}
repositories {
mavenCentral()
}
//required for compiling, testing and running the application
dependencies {
implementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.26'
implementation group: 'org.glassfish.jersey.core', name: 'jersey-common', version: '2.26'
implementation group: 'org.glassfish.jersey.core', name: 'jersey-server', version: '2.26'
providedCompile group:'javax.servlet', name:'javax.servlet-api', version:'3.1.0'
testCompile group:'commons-httpclient', name:'commons-httpclient', version:'3.1'
testCompile group:'junit', name:'junit', version:'4.12'
libertyRuntime group:'io.openliberty', name:'openliberty-runtime', version:'19.0.0.3'
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:27.0.1-jre'
testImplementation 'junit:junit:4.12'
}
//Extra properties for project level
ext {
appName = project.name
testServerHttpPort = 9080
testServerHttpsPort = 9443
warContext = appName
}
liberty {
server {
name = "${appName}Server"
configFile = file("src/main/liberty/config/server.xml")
bootstrapProperties = ['default.http.port': testServerHttpPort,
'default.https.port': testServerHttpsPort,
'app.context.root': warContext]
packageLiberty {
archive = "$buildDir/${appName}.zip"
include = "usr"
}
}
}
war {
archiveName = "${baseName}.${extension}"
}
//unit test configuration
test {
reports.html.destination = file("$buildDir/reports/unit")
reports.junitXml.destination = file("$buildDir/test-results/unit")
exclude '**/it/**'
}
//integration test configuration
task integrationTest(type: Test) {
group 'Verification'
description 'Runs the integration tests.'
reports.html.destination = file("$buildDir/reports/it")
reports.junitXml.destination = file("$buildDir/test-results/it")
include '**/it/**'
exclude '**/unit/**'
systemProperties = ['liberty.test.port': testServerHttpPort, 'war.name': warContext]
}
task openBrowser {
description = 'Open browser to http://localhost:8080/${warContext}'
doLast {
java.awt.Desktop.desktop.browse
"http://localhost:${testServerHttpPort}/${appName}".toURI()
}
}
task openTestReport {
description = 'Open browser to the test report'
doLast {
java.awt.Desktop.desktop.browse file("$buildDir/reports/it/index.html").toURI()
}
}
clean.dependsOn 'libertyStop'
check.dependsOn 'integrationTest'
integrationTest.dependsOn 'libertyStart'
integrationTest.finalizedBy 'libertyStop'
integrationTest.finalizedBy 'openTestReport'
libertyPackage.dependsOn 'libertyStop'
server.xml
<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">
<featureManager>
<!-- <feature>jsp-2.3</feature> -->
<!-- <feature>jaxrs-2.1</feature> -->
<feature>servlet-4.0</feature>
<!--
-->
</featureManager>
<httpEndpoint id="defaultHttpEndpoint"
httpPort="${default.http.port}"
httpsPort="${default.https.port}" />
<applicationManager autoExpand="true"/>
<webApplication contextRoot="${app.context.root}" location="${app.context.root}.war" />
</server>
HelloWorldService
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
#Path("/hello")
public class HelloWorldService {
#GET
#Path("/{param}")
public Response getMsg(#PathParam("param") String msg) {
String output = "Jersey say : " + msg;
return Response.status(200).entity(output).build();
}
}
When I try to hit the endpoint I get a 404
curl localhost:9080/XYZ/hello/jersey
Error 404: java.io.FileNotFoundException: SRVE0190E: File not found: /hello/jersey
Any help MUCH appreciated!
server
My stock-service client:
#Autowired
RestTemplate restTemplate;
ResponseEntity<List<String>> quoteResponse =
restTemplate.exchange("http://db-service/rest/db/" + userName,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<String>>() {}
);
Here db-service is registered service in eureka server.
Its working if i hit directly like
/* ResponseEntity<List<String>> quoteResponse =
restTemplate.exchange("http://localhost:8300/rest/db/" + userName,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<String>>() {}
);*/
#Configuration
public class Config {
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
As far as I understand that db-service is your any micro-service and you want to send there request. You need to configure ribbon because RestTemplate doesn't understand db-service like host. You should add follow config:
db-service:
ribbon:
eureka:
enabled: false
listOfServers: localhost:8090
ServerListRefreshInterval: 15000
and maven dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
or you could add Discovery service(Eureka) to your system and you don't need to describing listOfSerevers because ribbon will get all information from discovery service
Please try by using spring-cloud-starter-netflix-eureka-client.
And check the version of spring-cloud-dependencies along with spring-boot-starter-parent version.
spring-cloud-starter-netflix-eureka-client should resolve the issue
Trying to write Eureka Client With Spring Cloud Netflix v1.2.0.Release
but facing the below mentioned issue. PFB code and configurations.
EurekaClient.java
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Configuration
#EnableAutoConfiguration
#EnableEurekaClient
#RestController
#ComponentScan(basePackages={"com.west.eas.netflix.config"})
public class EurekaClient {
#RequestMapping("/")
public String home() {
return "Hello World";
}
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaClient.class).run(args);
}
}
application.yml
server:
port: 9000
spring:
application:
name: eas-eureka-client
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
preferIpAddress: true
bootstrap.yml
spring:
application:
name: eu-client
cloud:
config:
uri: http://localhost:8888
encrypt:
failOnError: false
Client fails to start with following error
" Parameter 0 of method eurekaHealthIndicator in
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration$EurekaHealthIndicatorConfiguration
required a bean of type 'com.netflix.discovery.EurekaClient' that
could not be found."
the below screenshot will have more details on error stack
I even tried setting healthcheck enable to false in application.yml but it still wont work. Any Help would be appreciated.
Regards
The problem seems to be that you are naming your client EurekaClient, There is already a bean with that name. Rename that class to something else and it should work
as #spencergibb mentioned it did go wrong in adding dependencies, I've tried creating a new project via http://start.spring.io/ which resolved my issue.