I'm trying to set up a couple of services using Spring Cloud and everything seems to work fine up until the moment I deploy the Eureka client services to Tomcat. When I call a service through my gateway app, I get the following error:
o.s.c.n.z.filters.post.SendErrorFilter : Error during filtering
com.netflix.zuul.exception.ZuulException: Forwarding error
...
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: hello timed-out and no fallback available.
...
Caused by: java.util.concurrent.TimeoutException: null
It works perfectly from eclipse however. It even works when I run the discovery and gateway services from Tomcat, and run the Eureka client service from eclipse. But as soon as I run the same service on tomcat, I get the error.
I'm using Brixton.M5, Java 8 and Tomcat 8.
Again, the code seems to work, the problem is it doesn't work after being deployed to Tomcat.
I have one Tomcat instance for the Discovery and Gateway services, and a second Tomcat instance for the Eureka client services.
Here's some code and config..
DiscoveryServerApp
#SpringBootApplication
#EnableEurekaServer
public class DiscoveryServerApp extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(DiscoveryServerApp.class, args);
}
}
DiscoveryServer - application.yml
# Configure this Discovery Server
eureka:
instance:
hostname: discovery
client: # Not a client, don't register with yourself
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:1111/discovery/eureka/
server:
port: 1111 # HTTP (Tomcat) port
context-path: /discovery
DiscoveryServer - bootstrap.yml
spring:
application:
name: discovery
jmx:
default-domain: com.example.cloud.discovery
GatewayApplication
#SpringCloudApplication
#EnableZuulProxy
public class GatewayApplication extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(GatewayApplication.class, args);
}
}
GatewayApplication - application.yml
# Discovery Server Access
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/discovery/eureka/
instance:
instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}}
# HTTP Server
server:
port: 4444 # HTTP (Tomcat) port
context-path: /api
GatewayApplication - bootstrap.yml
# Spring properties
spring:
application:
name: gateway-service # Identify this application
jmx:
default-domain: com.example.cloud.gateway
encrypt:
failOnError: false
DummyApplication
#SpringCloudApplication
#RestController
public class DummyApplication extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(DummyApplication.class, args);
}
#RequestMapping( path = "/hello-resource", method = RequestMethod.GET )
public String hello()
{
return "hello";
}
}
DummyApplication - application.yml
# Discovery Server Access
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/discovery/eureka/
instance:
instanceId: ${spring.application.name}:${spring.application.instance_id:${random.value}} # Unique id for multiple instances
# HTTP Server
server:
port: 3333 # HTTP (Tomcat) port
context-path: /hello-context
DummyApplication - bootstrap.yml
# Spring properties
spring:
application:
name: hello-service # Service registers under this name
jmx:
default-domain: com.example.cloud.hello
encrypt:
failOnError: false
I figured it out by accident... turns out that the value of server.port needs to match the port of the Tomcat instance where it's deployed. It seems obvious now, but I thought Spring would somehow magically figure that out from the container it's running in. I guess it would be a good idea to read that configuration from an external location to handle different environments without having to make 'code changes'.
So anyways, the answer is: make sure your server.port in application.yml matches the port on the target container.
Thanks to everyone who took the time to help me with this!
You should point your bowser to port 4444 (gateway), not 1111 (eureka).
Ok, #SpringCloudApplication wraps #EnableDiscoveryClient which causes DummyApplication to register itself with Eureka on startup. You can confirm this is happening through the Eureka dashboard.
Assuming DummyApplication registers with Eureka as service name "hello-service", then Zuul / Ribbon will create a route for that service name. Thus, your "/hello-resource" endpoint should be proxied through Zuul at:
http://localhost:4444/api/hello-service/hello-resource/
Related
I'm currently replacing an api gateway using Netflix Zuul with spring cloud gateway. The setup uses discovery client (Eureka) for most of the routes, but we also have a solr instance running which requires manually defined routes (as solr doesn't support eureka)
Using a static route to solr running on localhost works fine using the following config:
routes:
- id: solr
predicates:
- Path=/solr/**
uri: http://localhost:10983
filters:
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location,
However, I would like to use a load-balanced uri for this route as we have multiple solr instances. Looking at the documentation I've found that the way to implement this is to define a Bean returning a ServiceInstanceListSupplier. I've imlemented the following function:
#Bean
ServiceInstanceListSupplier serviceInstanceListSupplier() {
List<String> servers = Arrays.asList(microserviceGatewayConfig.getServers().split(","));
return new SolrServiceInstanceListSupplier("solrhosts", servers);
}
However, this seems to override the ServiceInstances defined from Eureka, meaning only the manual services are used...
Do anyone know if it is possble to combine manually defined serviceinstances with those generated from eureka?
The approach with creating a Bean returning a ServiceInstanceListSupplier doesn't seem to work in any way... However, I've found a way to achieve the same in application.yml, by adding the following config:
spring:
cloud:
discovery:
client:
simple:
instances:
solr-cluster:
- instanceId: cluster1
serviceId: solr-cluster
host: soa03i-t.usrv.ubergenkom.no
port: 10983
- instanceId: cluster2
serviceId: solr-cluster
host: soa04i-t.usrv.ubergenkom.no
port: 10983
This can be combined with autogenerated routes from service discovery (e.g. Eureka)
I develop a micro services environment.It is developed by the spring cloud.
The gateway server has been developed by the Zuul and has secured by X.509 certificate authentication(mutual authentication).
I use spring boot admin for monitoring the micro services.
I already use Spring Cloud Discovery for my applications and i added a DiscoveryClient to Spring Boot Admin Server:
#Configuration
#EnableAutoConfiguration
#EnableDiscoveryClient
#EnableAdminServer
public class ApiAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(ApiAdminServerApplication.class, args);
}
}
Spring boot admin properties:
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=ALWAYS
spring.security.user.name=admin
spring.security.user.password=password
Service discovery properties:
management.endpoint.metrics.enabled=true
management.endpoints.web.exposure.include=*
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=${spring.application.name}
Gateway properties:
server.ssl.enabled=true
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:tls/keyStore.p12
server.ssl.key-store-password=changeit
server.ssl.trust-store=classpath:tls/trustStore.jks
server.ssl.trust-store-password=changeit
server.ssl.trust-store-type=JKS
server.ssl.client-auth=need
The gateway server is shown in spring boot admin panel as an application but its status is down.
How do I config spring boot admin for monitoring https gateway application?
Spring Boot Admin makes a call to /actuator/health endpoint to know the application health. Please check if this endpoint is working fine, otherwise, you might need to configure it based on your requirements.
By default Spring check for a number of health indicators mentioned here:
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#auto-configured-healthindicators
You can configure your own indicator as well, a snippet from documentation:
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
#Component
public class MyHealthIndicator implements HealthIndicator {
#Override
public Health health() {
int errorCode = check(); // perform some specific health check
if (errorCode != 0) {
return Health.down().withDetail("Error Code", errorCode).build();
}
return Health.up().build();
}
}
I disable SSL authentication in the application.properties file of gateway :
management.server.ssl.enabled=false
And ignore actuator url path in the WebSecurityConfigurerAdapter class of gateway:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers( "/actuator/health/**").permitAll()
.anyRequest().authenticated().and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService());
}
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/**");
}
But be careful , this solution expose important health info of the gateway server for all request.
I want to deploy an IDAS (FIWARE Backend Device Manager) i.e. IOTA instance that will communicate and send data to an already existing Orion Context Broker instance running in a different virtual machine from the one hosting IDAS. Is that possible? Or is it necessary for the two services to be in the same virtual machine?
I am using an IoTAgent-JSON (I think it's 1.6.2 version) for MQTT transport.
This is the config.js file (I have already replaced the contextBroker host with the host of my Orion Context Broker, as you can see, it was "localhost" before):
var config = {};
config.mqtt = {
host: 'localhost',
port: 1883,
thinkingThingsPlugin: true
};
config.iota = {
logLevel: 'DEBUG',
timestamp: true,
contextBroker: {
host: '147.27.60.182',
port: '1026'
},
server: {
port: 4041
},
deviceRegistry: {
type: 'mongodb'
},
mongodb: {
host: 'localhost',
port: '27017',
db: 'iotagentjson'
},
types: {},
service: 'howtoService',
subservice: '/howto',
IoTA endpoints:
http://147.27.60.202:5351/iot/services
(Fiware-Service: openiot, Fiware-ServicePath: /, X-Auth-Token: [TOKEN])
http://147.27.60.202:4041/iot/devices/
(Fiware-Service: tourguide, Fiware-ServicePath: /)
My Orion Context Broker (where I want to send data) endpoint:
http://147.27.60.182:1026/v2
P.S.: I have tried to change the mongodb host, too.
Image: how the service runs
Yes, you can have Orion and one or more IOTAs running in different virtual machine. The only requirement is mutual interconnection, I mean IOTA needs access to the Orion endpoint and Orion needs access to IOTA endpoint.
Check contextBroker.url (alternatively contextBroker.host and contextBroker.port) and providerUrl IOTA configuration parameters in IOTA documentation.
spring cloud eureka question: there are 3 microservices, 1 eureka server, 2 eureka client;
microservice A,B use annotation #EnableEurekaClient;
microservice A have a RESTful api "http://localhost:8080/hi". the api return "hello".
now, I call the api , use url "http://client/hi", but it doesn't work.
how use application name replace ip:port about spring cloud eureka?
the bootstrap.yml content:
spring:
application:
name: client
eureka:
client:
service-url:
defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/
There are many ways to do that and it depends on how you call REST API in your code.
If you are using RestTemplate to call the API, you can do that with #LoadBalanced RestTemplate
In your code that wants to invoke REST api, please define RestTemplate with #LoadBalanced like below.
#LoadBalanced
#Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
And when you call API, just use application name instead of host:port like below.
this.restTemplate.getForObject("http://client/hi", String.class)
If you are using SpringCloud Feign, you can define the interface to call your REST api like below (without URL)
#FeignClient(name="client")
public interface ProductResource {
:
}
And add annotation #EnableFeignClients in your spring boot application like below.
#SpringBootApplication
#EnableFeignClients
: // other annotations you need.
public class YourAPIInvokerApplication {
In both ways, you need to add a few dependencies.
I'm using RestTemplate, but i got Connection timed out: connect after put serviceName instead of localhost:port
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
call API:
FraudCheckResponse fraudCheckResponse = customerConfig.restTemplate().getForObject(
"http://fraud/api/v1/fraud-check/{customerId}",
FraudCheckResponse.class,
customer.getId()
);
I setup my services to use the spring cloud eureka based config server.
version info: spring cloud 1.0.1.RELEASE
When I set it up as a fixed endpoint, I can see that it gets the right configuration file and that I can access actuator endpoints like health, info etc. so a .../manage/info returns the correct information.
However when I set it up to use discovery, the same actuator endpoints timeout on trying got access them.
In each case the configuration file is retrieved and downloaded (included log file).
Is there an issue with how I setup config server and bookmark service (the service which uses the config server)?
My configuration server setting is as follows:
server:
port: 8888
contextPath: /configurationservice
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
statusPageUrlPath: /configurationservice/info
homePageUrlPath: /configurationservice/
healthCheckUrlPath: /configurationservice/health
preferIpAddress: true
spring:
cloud:
config:
server:
native:
searchLocations: file:/Users/larrymitchell/libertas/configserver/configfiles
The service bootstrap.yml settings are:
spring:
profiles:
default: development
active: development
application:
name: bookmarkservice
cloud:
config:
enabled: true # note this needs to be turned on if you wnat the config server to work
# uri: http://localhost:8888/configurationservice
label: 1.0.0
discovery:
enabled: true
serviceId: configurationservice
The application.yml settings are:
# general spring settings
spring:
application:
name: bookmarkservice
profiles:
default: development
active: development
# name of the service
service:
name: bookmarkservice
# embedded web server settings
# some of these are specific to tomcat
server:
port: 9001
# the context path is the part after http:/localhost:8080
contextPath: /bookmarkservice
tomcat:
basedir: target/tomcat
uri-encoding: UTF-8
management:
context-path: /manage
security:
enabled: false
eureka:
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
statusPageUrlPath: /bookmarkservice/manage/info
homePageUrlPath: /bookmarkservice/manage
healthCheckUrlPath: /bookmarkservice/manage/health
preferIpAddress: true
The startup log for bookmark service is as follows:
2015-06-24 17:52:49.806 DEBUG 11234 --- [ main] o.s.web.client.RestTemplate : Created GET request for "http://10.132.1.56:8888/configurationservice/bookmarkservice/development/1.0.0"
2015-06-24 17:52:49.890 DEBUG 11234 --- [ main] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2015-06-24 17:52:50.439 DEBUG 11234 --- [ main] o.s.web.client.RestTemplate : GET request for "http://10.132.1.56:8888/configurationservice/bookmarkservice/development/1.0.0" resulted in 200 (OK)
2015-06-24 17:52:50.441 DEBUG 11234 --- [ main] o.s.web.client.RestTemplate : Reading [class org.springframework.cloud.config.environment.Environment] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#2b07e607]
2015-06-24 17:52:50.466 INFO 11234 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='file:/Users/larrymitchell/libertas/configserver/configfiles/1.0.0/bookmarkservice-development.yml']]]
2015-06-24 17:52:50.503 INFO 11234 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext#5fa23965: startup date [Wed Jun 24 17:52:50 EDT 2015]; parent: org.springframework.context.annotation.AnnotationConfigApplicationContext#5cced717
2015-06-24 17:52:51.723 WARN 11234 --- [ main] .i.s.PathMatchingResourcePatternResolver : Skipping [/var/folders/kq/ykvl3t4n3l71p7s9ymywb4ym0000gn/T/spring-boot-libs/06f98804e83cf4a94380b46591b976b1d17c36b8-eureka-client-1.1.147.jar] because it does not denote a directory
2015-06-24 17:52:53.662 INFO 11234 --- [ main] o.s.b.f.config.PropertiesFactoryBean : Loading properties file from URL [jar:file:/Users/larrymitchell/libertas/vipaas/applicationservices/bookmarkservice/target/bookmarkservice.jar!/lib/spring-integration-core-4.1.2.RELEASE.jar!/META-INF/spring.integration.default.properties]
Ok, after talking it over with another coworker I figured out what the actual issue is.
Part of the confusion is that I am using the spring cloud (https://github.com/VanRoy/spring-cloud-dashboard) which is a great front end by the way. So when the service starts we see it for to discovery and retrieve the correct configuration file and load it. After I go to the spring cloud console and see a setting of UP which means that it is discovered and registered though discovery. There is a second status indicator which is when the spring cloud dashboard takes the registered endpoint and get health. In my issue the endpoint was showing up as UNKNOWN.
If I then use the endpoint that shows up in the console and try the info actuator endpoint then the request times out. This was the nature of my problem
Ok, so what was the issue?
Basically since I defined the in the application.yml and since when the service registers in bootstrap it does not know the port yet and then it picks a default of 8080 (my assumption since that is what it does). The server port is set in application.yml to 9001 but discovery sees a registration of 8080 so the spring cloud console cannot access the localhost:8080/bookmarkservice/manage/health since there is no service at that endpoint (which is at 9001 actually). Other services also cannot find the service.
By moving the server.port to bootstrap.yml then the correct endpoint of rate service is registered and the service properly accessible.