Spring Config-Client doesn't refresh if Config-Server is down during initial startup - spring

I am running a test with a barebones Spring cloud config-server and a client-application. I executed a refresh scenario (by calling /refresh endpoint on the client-application)
after config-server was down initially. Here is what I found
Client starts up with locally packaged properties when config-server is not reachable on startup. (I have the properties in application.yml that is bundled with client-application)
Git backend has different values for the properties compared to locally packaged version. Config-server is aware of the changes in git (Confirmed by connecting directly to config-server)
I bring up config-server and do a POST to /refresh endpoint on the client-application.
Client-application is not aware of the new properties from config-server.
In the second usecase
Client-application starts up and connects to config-server successfully. I see that the values from config-server have been fetched by the client-application successfully
I make a change in Git and call the /refresh endpoint on the client-application. Properties are refreshed successfully.
At this point it looks like /refresh doesn't work if the client-application comes up initially without being able to successfully connect to config-server. I am doing this to test
a fallback strategy for the client-application if config-server is not reachable when the client-application is starting up. (The fallback strategy is to have locally packaged properties
that will be used if config-server is not available on startup. If the config-server is available then the local properties are overriden). Any pointers to why this is not working and
what I could do differently? Thanks in advance.
Edit
Server-Code
#EnableConfigServer
#SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
Client-Code
#RestController
#RefreshScope
#Component
public class Greeter {
#Value("${message.greeting}")
String greeting;
#RequestMapping(value = "/",produces = "application/json")
public List<String> index(){
List<String> env = Arrays.asList("message.greeting: " + greeting);
return env;
}
}
bootstrap.yml (On config-client application)
spring:
application:
name: configclient
cloud:
config:
uri: http://localhost:8888
management:
security:
enabled: false
logging:
config: classpath:logback.xml
server:
port: 8000
application.yml
message:
greeting: Hello from Local!
Config in Git (Served through config-server)
message:
greeting: Hello from Git-Edited!

According to spring-cloud-config documentation -
If you expect that the config server may occasionally be unavailable
when your app starts, you can ask it to keep trying after a failure.
First you need to set spring.cloud.config.failFast=true, and then you
need to add spring-retry and spring-boot-starter-aop to your
classpath. The default behaviour is to retry 6 times with an initial
backoff interval of 1000ms and an exponential multiplier of 1.1 for
subsequent backoffs. You can configure these properties (and others)
using spring.cloud.config.retry.* configuration properties.
Reference -> http://cloud.spring.io/spring-cloud-static/spring-cloud-config/1.3.1.RELEASE/

Related

spring-cloud-loabalancer configuring static server list

We're moving away from the spring-cloud Netflix OSS ecosystem one step at a time. Currently we're implementing spring-cloud-loadbalancer and removing Ribbon.
However we used to have a lot of static services in our integration tests, now with the move from ribbon towards spring-cloud-loadbalancer those properties are not being picked up any longer.
i.e.:
foo-service.ribbon.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
foo-service.ribbon.listOfServers=localhost:9876
We've migrated towards using spring-cloud-loadbalancer in the following way
First we annotated our Webclient.Builder with #LoadBalanced like this
#Bean
#LoadBalanced
fun webClientBuilder() = WebClient.builder()
And then we've added the #LoadBalancerClient annotation on the client classes like this
#LoadBalancerClient(name = "foo-service", configuration = [FooServiceConfiguration::class])
class FooServiceClient(private val basicAuthWebClient: WebClient)
This results in our tests failing with an UnknownHostException for foo-service.
Now My question is how do we configure this static server list in the new spring-cloud-loadbalancer?
Based on #spencergibb's comment, I guess something like this should work:
spring:
cloud:
discovery:
client:
simple:
instances:
foo-service:
- instanceId: foo1
serviceId: foo-service
host: localhost
port: 9876```

Why spring client cannot obtain spring config server properties?

I've got a #SpringBootApplication, running with the production profile, and a spring config server,
{"name":"config-client","profiles":["production"],"label":null,"version":"97611975e6ddb87c7213e18ddbe203ab6ae5485d","state":null,"propertySources":[{"name":"http://git/scm/abm/abm-settings.git/application-production.yml","source":{"my.pretty.property.id":21}}]}
I cannot load property my.pretty.property.id from server (they are always null), I am using
#Getter
#Setter
#Component
#ConfigurationProperties(prefix = "my.pretty.property")
public class MyProperties {
private String id;
}
and my bootstrap.yml is
spring.cloud:
config:
uri: http://${SERVICE_HOST}/${PROJECT_KEY}-config-server
enabled: false
failFast: true
build.gradle contains this:
"org.springframework.cloud:spring-cloud-starter-consul-all",
"org.springframework.cloud:spring-cloud-consul-core",
"org.springframework.cloud:spring-cloud-starter-hystrix",
"org.springframework.cloud:spring-cloud-starter-hystrix-dashboard",
"org.springframework.cloud:spring-cloud-starter-zipkin",
"org.springframework.cloud:spring-cloud-config-client"
My client application is normally built and deployed, what am I missing?
From Spring Cloud Config:
The HTTP service has resources in the form:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
As you mentioned your application is running under production profile - hence Spring will try to find file: <cloud-config-server-url>/application-production.yml and load it, besides application.properties. But your properties are located in application-integration.yml and you don't use integration profile.
So the solution is to use an integration profile or to create an application-production.yml on your config. server.
The following config is right, but spring.cloud.config.enabled should be true, I didn't overrride it in my production environment

Unable to generate different spring-cloud-gateway routes based on active Spring profile

I'm trying to define different routes in Spring Cloud Gateway using Spring profiles. Everything I've read about how Spring manages profiles using yaml sems to indicate it should work, but for the life of me it just ... doesn't seem to. (My other Spring apps use properties, so I'm unused to the yaml config -- it's possible I'm missing something.)
Effectively what I'm trying to do is to have a "prod" profile which contains the URIs for my production servers, and a "dev" profile which contains the localhost equivalents.
I have two profiles, dev and prod. My application.yml file looks like this
spring:
profiles:
default: prod
management:
endpoint:
health:
enabled: true
endpoints:
web:
exposure:
include: hystrix.stream, gateway
---
spring
profiles: prod
cloud:
gateway:
routes:
- id: test_route
uri: http://foo.mycompany.com
predicates:
- Path=/status
- Method=GET
---
spring
profiles: dev
cloud:
gateway:
routes:
- id: test_route
uri: http://localhost:8080
predicates:
- Path=/status
- Method=GET
My understanding is as follows:
the spring.profiles.default property tells Spring that, if no profile is specified, to use the prod profile
Spring will treat the --- as a "file separator" and re-evaluate each set of properties and overwrite previous values if the spring.profiles parameter evaluates true
Given this understanding, I would expect Spring to parse the "default" properties first, learning that the default activated profile should be prod. Then it will parse the prod properties. Since "prod" is an active profile (the only active profile, being the default), it should parse and apply the prod routes. Then it would parse the dev routes, but recognize that dev is not an active profile, and not overwrite those values. This is my understanding from reading the documentation on how to change config based on the environment.
However, when I load this, and I hit the actuator endpoint -- /actuator/gateway/routes -- I get back [] where I would expect to see the prod routes. I do see in my logs that the prod profile is activated, but it seems like not having the properties in the "default" section at top causes them to not be applied when the parser reads them out of the profile section.
The other thing I tried was putting the "dev" properties as the defaults, and then attempting to use the "prod" profile properties to overwrite the URIs. A similar issue happened there -- I hit the actuator endpoint and got back routes, but they were just the dev ones from the default.
How can I leverage Spring profiles to configure different Spring Cloud Config routes in my application.yml ?
Versions:
spring-cloud-gateway 2.0.1.BUILD-SNAPSHOT (to get a workaround for this bug, probably not relevant)
spring-cloud-starter-gateway
spring-boot 2.0.3.RELEASE
spring-boot-starter-webflux
spring-boot-starter-actuator
(I can't use Spring Cloud Config for political reasons. My company's chief architect has a severe case of Not Invented Here Syndrome.)
You cannot use spring.profiles.default in the property file. It will be too late for setting such value.
So you can set it using program argument (or System property). E.g.
java -jar --spring.profiles.default=dev your-app.jar
Or you can do it in the code by hardcoding the default profile:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(DemoApplication.class);
ConfigurableEnvironment environment = new StandardEnvironment();
environment.setDefaultProfiles("dev");
application.setEnvironment(environment);
application.run(args);
}
}
Some related information you can find here by reading all comments:
https://github.com/spring-projects/spring-boot/issues/1219

How to register spring boot microservices on spring cloud Netflix eureka?

We were planning to use spring cloud Netflix oss components. So I was doing a small sample project.
I developed 2 spring microservices and those services runs well on
http://localhost:9000/microsvc-one http://localhost:9001/microsvc-two
And also wrote a sample spring cloud etflix eureka maven project which runs well on
http://localhost:8761
I used annotations #EurekaDiscoveryClient and #SpringBootApplication on both the spring boot microservices main class
I used annotation #EnableEurekaServer and #SpringBootApplication
Now I am facing a problem in registering those services in eureka server. I referred some samples. I am not understanding those.
I did some changes in application.yml files of microsvc-one and microsvc-two and also application.yml file of eureka server.
But still it shows empty.
What all changes are required or missing or correct configuration to be done so that my services are being registered on eureka.
I also have other question like do i need to create a separate project which has #EnableConfigServer and #SpringBootApplication Annotations other than the above 2 microservices and eureka server project module to register the services on eureka.
I see those in most of the examples.
If yes..how do we link between all these?
If you are using springBoot application you will need the annotaion #SpringBootApplication thats why that annotation is there on the project you are seeing. #EnableConfigServer is when you are using the spring-cloud config server it is used to externalize the configuration properties but since you have the application.yml inside the project so you donot need that either.
I am thinking you have a spring boot application for both Microservices and the Eureka server. You need to annotate the eureka main class with
#SpringBootApplication
#EnableEurekaServer
#EnableDiscoveryClient
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Additionally you need annotate you microservice's main class with..
#SpringBootApplication
#EnableDiscoveryClient
public class MicroApplication {
public static void main(String[] args) {
SpringApplication.run(MicroApplication.class, args);
}
}
Since you donot have you application.yml file in the question here is what you need.
You need the below configuration in application.yml of the microservices.
eureka:
client:
serviceUrl:
defaultZone: ${eurekaurl:http://localhost:8761/eureka/}
In the Eureka Server application.yml file I have this in mine. you might need to tweak it based on what you want.
info:
component: Registry Server
server:
port: ${port:8761}
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
enable-self-preservation: false
waitTimeInMsWhenSyncEmpty: 0
instance:
hostname: localhost
lease-expiration-duration-in-seconds: 15
lease-renewal-interval-in-seconds: 5
Suppose you have a microservice named "LoginServer" now, let's see how to register this service with discovery server (Eureka Server) at startup.
Here Spring Boot startup class of LoginServer.java:
#EnableAutoConfiguration
#EnableDiscoveryClient
public class LoginServer {
public static void main(String[] args) {
// Will configure using login-server.yml
System.setProperty("spring.config.name", "login-server");
SpringApplication.run(LoginServer.class, args);
}
}
The #EnableDiscoveryClient - enables service registration and discovery. In this case, this process registers itself with the discovery-server service using its application name, that is configured in YML configuration file.
let's see the complete setup:
First create a login-server.yml (any name but extension should be .yml) file into src/main/resources package folder. And write those configurations and save.
# Spring properties
spring:
application:
name: login-server # This name used as ID so ("spring.config.name",
#"login-server"); must be same.
# Discovery Server Access
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
# HTTP Server
server:
port: 2222 # HTTP (Tomcat) port
Run the LoginServer and let it finish initializing. Open the dashboard by putting this URL http://localhost:1111 in your favorite browser and refresh. After few seconds later you should see the LOGIN-SERVER. Generally registration takes up to 30 seconds (by default) so wait or restart.
And this is the microservice complete registration process.

Eureka on spring-cloud-netflix with DNS based config, all instances showing up as unavailable

I'm trying to setup a eureka cluster on aws with DNS-based EIP configuration as described at https://github.com/Netflix/eureka/wiki/Configuring-Eureka-in-AWS-Cloud
Everything seems to work, but the eureka dashboard insists that the eureka instances are unavailable. I'm now wondering if this is only an ui problem (i think so) or if i'm missing something.
As i understand the "unavailable-replicas" logic in the dashboard this is because eureka is comparing the registration hostname and the replica hostname. The instances register with their internal VPC ip at the discovery client but with their EIP when looking for replica peers (strange enough, in the eureka log i can see internaly they are also using the internal VPC ip).
The question is: Is that only some cosmetic ui problem that i shouldn't worry about or are bigger problems waiting to step in because of some misconfiguration? If it's only an ui thing: can i "repair" that somehow?
Edit:
Maybe related https://github.com/spring-cloud/spring-cloud-netflix/issues/102#issuecomment-74446709
With the help of #rozhok in the related github issue i now have a working solution. If anyone is facing the same problem, here's what i've done:
application.yml
eureka:
datacenter: cloud
client:
eurekaServerDNSName: your.dns.name
eurekaServerPort: 8761
eurekaServerURLContext: eureka
region: eu-west-1
registerWithEureka: true
fetchRegistry: true
useDnsForFetchingServiceUrls: true
server:
waitTimeInMsWhenSyncEmpty: 0
enableSelfPreservation: true
EurekaServer
#SpringBootApplication
#EnableEurekaServer
#EnableDiscoveryClient
public class EurekaServer {
#Value("${server.port:8761}")
private int port;
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
}
#Bean
#Autowired
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils) {
EurekaInstanceConfigBean config = new EurekaInstanceConfigBean(inetUtils);
AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
// Don't use spring cloud's hostname here.
// See comment below by Michal
config.setHostname(
info.get(AmazonInfo.MetaDataKey.publicHostname));
config.setIpAddress(info.get(AmazonInfo.MetaDataKey.publicIpv4));
config.setNonSecurePort(port);
config.setDataCenterInfo(info);
return config;
}
}
With that configuration each eureka server sees only the other servers as available replicas:

Resources