Eureka never unregisters a service - spring-cloud

I'm currently facing an issue where Eureka does not unregister a registered service. I've pulled the Eureka server example straight from git hub and made only one change, eureka.enableSelfPreservation = false. My application.yml looks like this:
server:
port: 8761
eureka:
enableSelfPreservation: false
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0
I've read that if 85% of the registered services stop delivering heartbeats within 15 minutes, Eureka assumes the issue is network related and does not de-register the services that are not responding. In my case I have only one service running, so I disabled self-preservation mode. I am abruptly killing the process and Eureka leaves the service registered for what seems like an indefinite amount of time.
My client's application.yml looks like this:
eureka:
instance:
leaseRenewalIntervalInSeconds: 3
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
appInfo:
replicate:
interval: 3
initial:
replicate:
time: 3
spring:
rabbitmq:
addresses: ${vcap.services.${PREFIX:}rabbitmq.credentials.uri:amqp://${RABBITMQ_HOST:localhost}:${RABBITMQ_PORT:5672}}
My goal is to create a demo where Eureka quickly detects the service is no longer running and another service that is started can quickly register itself.
As of now, once the eureka client is started, it registers in 3 seconds. It just never un-registers when the service is abruptly terminated. After I kill the service, the Eureka dashboard reads:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
How can I prevent this behavior?

I realized that self preservation mode was never actually being disabled. It turns out the actual property is
eureka.server.enableSelfPreservation=false
(See DefaultEurekaServerConfig Code), which I haven't found documented anywhere. This resolved my issue.

I made service de-registration work by setting the below values
Eureka server application.yml
eureka:
server:
enableSelfPreservation: false
Service application.yml
eureka:
instance:
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
The full example is here https://github.com/ExampleDriven/spring-cloud-eureka-example

After struggling a lot, finally I got solution if any service unregistered from Eureka server due to some issue. It will notify to the Admin by extending the HealthCallback of Eureka-Server APIs.
Let Say Service-A register with Eureka. Hence Eureka Client is integrate with Service-A and Implement following Callbacks in Service A.
Service-A [Eureka-Client]
Add following properties in properties files.
#Eureka Configuration
eureka.client.eureka-server-port=8761
eureka.client.register-with-eureka=true
eureka.client.healthcheck.enabled=false
eureka.client.prefer-same-zone-eureka=true
eureka.client.fetchRegistry=true
eureka.client.serviceUrl.defaultZone=${eurekaServerURL1}, ${eurekaServerURL2}
eureka.client.eureka.service-url.defaultZone=${eurekaServerURL1}, ${eurekaServerURL2}
eureka.instance.hostname=${hostname}
eureka.client.lease.duration=30
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=30
Add following java files.
#Component
public class EurekaHealthCheckHandler implements HealthCheckHandler, ApplicationContextAware, InitializingBean {
static Logger logger = LoggerFactory.getLogger(EurekaHealthCheckHandler.class);
private static final Map<Status, InstanceInfo.InstanceStatus> healthStatuses = new HashMap<Status, InstanceInfo.InstanceStatus>() {{
put(Status.UNKNOWN, InstanceInfo.InstanceStatus.UNKNOWN);
put(Status.OUT_OF_SERVICE, InstanceInfo.InstanceStatus.OUT_OF_SERVICE);
put(Status.DOWN, InstanceInfo.InstanceStatus.DOWN);
put(Status.UP, InstanceInfo.InstanceStatus.UP);
}};
#Autowired
ComunocationService comunocationService ;
private final CompositeHealthIndicator healthIndicator;
private ApplicationContext applicationContext;
public EurekaHealthCheckHandler(HealthAggregator healthAggregator) {
Assert.notNull(healthAggregator, "HealthAggregator must not be null");
this.healthIndicator = new CompositeHealthIndicator(healthAggregator);
Health health = healthIndicator.health();
logger.info(" =========== Testing =========== {}", health.toString() );
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
#Override
public void afterPropertiesSet() throws Exception {
final Map<String, HealthIndicator> healthIndicators = applicationContext.getBeansOfType(HealthIndicator.class);
for (Map.Entry<String, HealthIndicator> entry : healthIndicators.entrySet()) {
logger.info("======"+ entry.getKey() +"============= "+entry.getValue());
healthIndicator.addHealthIndicator(entry.getKey(), entry.getValue());
}
}
#Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus instanceStatus) {
logger.info("============== Custome Eureka Implementation ==================="+ getHealthStatus());
return getHealthStatus();
}
protected InstanceInfo.InstanceStatus getHealthStatus() {
final Status status = healthIndicator.health().getStatus();
return mapToInstanceStatus(status);
}
protected InstanceInfo.InstanceStatus mapToInstanceStatus(Status status) {
logger.info("============== Test Custome Eureka Implementation ==================={}", status);
if(status.equals(InstanceInfo.InstanceStatus.UP)) {
// Send mail after configured times
comunocationService.sendEmail("ServiceName");
}
if(!healthStatuses.containsKey(status)) {
return InstanceInfo.InstanceStatus.UNKNOWN;
}
return healthStatuses.get(status);
}
public void getstatusChangeListner() {
ApplicationInfoManager.StatusChangeListener statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
#Override
public String getId() {
return "statusChangeListener";
}
#Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
}
};
}
}
and
#Configuration
public class EurekaHealthCheckHandlerConfiguration {
#Autowired(required = false)
private HealthAggregator healthAggregator = new OrderedHealthAggregator();
#Bean
#ConditionalOnMissingBean
public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
return new EurekaHealthCheckHandler(healthAggregator);
}
}
This is absolutely working and well tested code

Related

symfony6 $this->container->get('app.our.useful.thing')

I am sorry, I read the docs, but I don't get symfony6 anymore.
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/EventListener
app.our.useful.thing:
class: App\OurUsefulThing
public: true
My src/OurUsefulThing.php:
<?php
namespace App;
class OurUsefulThing
{
public function sayHello()
{
return "Hello Service";
}
}
In the controller:
public function someActionName(Request $request)() {
$value = $this->container->get('app.our.useful.thing')->sayHello();
}
Error Message
Service "app.our.useful.thing" not found: even though it exists in the app's container, the container inside "App\Controller\CategoriesController" is a smaller service locator that only knows about the "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer" and "twig" services. Try using dependency injection instead.
It was in symfony4 so easy. What I am doing wrong?

Request-based Sticky Session configuration with Spring Cloud LoadBalancer

I have the following configuration for request-based sticky session using Spring Cloud LoadBalancer
spring:
cloud:
discovery.client.simple.instances:
say-hello:
- instanceId: say-hello1
uri: http://localhost:8080
- instanceId: say-hello2
uri: http://localhost:8081
loadbalancer:
configurations: request-based-sticky-session
sticky-session:
add-service-instance-cookie: true
server.port:9090
the following call:
$ http :9090/hi 'Cookie:sc-lb-instance-id=say-hello1'
should go only to the say-hello1 instance based on the Request-based Sticky Session for LoadBalancer but instead is using round robin load balancing.
What do I miss here?
Here is the source code to try it: https://github.com/altfatterz/client-side-loadbalancing
There are 2 things to consider here:
In the sample, the cookie has to be passed on to the actual load-balanced request, for example like so:
#GetMapping("/hi")
public String hi(#RequestParam(value = "name", defaultValue = "Mary") String name) {
logger.info("Accessing /hi endpoint");
HttpHeaders headers = new HttpHeaders();
headers.set("Cookie", "sc-lb-instance-id=say-hello1");
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<String> greeting = restTemplate.exchange("http://say-hello/greeting", HttpMethod.GET, entity, String.class, new HashMap<>());
return greeting + " " + name;
}
This feature is only supported for WebClient-backed load-balancing. It was not properly documented. I have documented it here.
I have also created a GitHub issue for adding the non-reactive implementation, however, the decision to implement it will be dependant on larger community interest.

Spring OAuth2 Keycloak Kubernetes internal/external access

I have Keycloak (10.0.3) server configured inside a Kubernetes Cluster.
The keycloak server has to handle authentification for external user (using an external url) and also handle oauth2 token for Spring microservices communications.
Then web application spring services uses oidc providers :
spring:
security:
oauth2:
client:
provider:
oidc:
issuer-uri: http://keycloak-cluster-http.keycloak-cluster.svc.cluster.local/auth/realms/myrealm
authorization-uri: http://keycloak-cluster-http.keycloak-cluster.svc.cluster.local/auth/realms/myrealm/protocol/openid-connect/auth
jwk-set-uri: http://keycloak-cluster-http.keycloak-cluster.svc.cluster.local/auth/realms/myrealm/protocol/openid-connect/certs
token-uri: http://keycloak-cluster-http.keycloak-cluster.svc.cluster.local/auth/realms/myrealm/protocol/openid-connect/token
user-name-attribute: preferred_username
The external URL of keycloak is https://keycloak.localhost, managed by ingress redirection handled by Traefik v2
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: keycloak-https
namespace: keycloak-cluster
annotations:
traefik.frontend.passHostHeader: "true"
spec:
entryPoints:
- websecure
routes:
- match: Host(`keycloak.localhost`)
kind: Rule
services:
- name: keycloak-cluster-http
port: 80
tls:
options:
name: mytlsoption
namespace: traefik
store:
name: default
I can access Keycloak using https://keycloak.localhost, no problem, it works.
The problem is that when I try to access my web application, it will always redirect to 'http://keycloak-cluster-http.keycloak-cluster.svc.cluster.local/auth/realms/myrealm', which is not resolved outside k8s.
If I change issuer-uri to http://keycloak.localhost then it doesn't work as keycloak.locahost is not resolved inside k8s.
I tried to set the KEYCLOAK_FRONTEND_URL to https://keycloak.localhost/auth, but no change.
Please, does someone has the same kind of settings and managed to make it working ?
Best regards
Managed to fix it using coredns and adding a rewrite rule... :
rewrite name keycloak.localhost keycloak-cluster-http.keycloak-cluster.svc.cluster.local
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
rewrite name keycloak.localhost keycloak-cluster-http.keycloak-cluster.svc.cluster.local
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
The authorization_uri needs to be understood by the browser since that URI is processed in the front channel. The rest of the URIs are processed in the back channel.
Because of that, the authorization_uri should use the front channel way of addressing the authorization server:
authorization_uri: https://keycloak.localhost/auth/realms/myrealm/protocol/openid-connect/auth
EDIT Based on Joe Grandja's input below, it appears that it's also necessary to not specify the issuer-uri property. The issuer-uri property is a shortcut for specifying the other URIs, and since you are specifying those, you don't need it anyway.
Here A POC that helped me with the issue.
Similar configuration ,keycloak and spring gateway are in kubernetes
The external user uses keycloak external host with https protocol
https://external-https/auth/realms/myrealm/protocol/openid-connect/auth?...
The ingress break the https and moves it to http + change the host to internal-http
gateway uses internal-http to connect to keycloakon port 8080
In order for the issuer to be the same protocol as the external the configuration uses https in user-info-uri and authorization-uri but the rest are http
make sure that the keycloak pod is open for https connection (8443)
authorization-uri: https://internal-http:8443/auth/realms/myrealm/protocol/openid-connect/auth
user-info-uri: https://internal-http:8443/auth/realms/myrealm/protocol/openid-connect/userinfo
issuer-uri: http://internal-http:8080/auth/realms/myrealm
To fix the host part of the issuer
In the gateway code I updated the following based on https://github.com/spring-projects/spring-security/issues/8882#user-content-oauth2-client
#SneakyThrows
private WebClient webClient() {
SslContext sslContext = SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
HttpClient httpClient = HttpClient.create()
.secure(t -> t.sslContext(sslContext))
.wiretap(true)
;
ReactorClientHttpConnector conn = new ReactorClientHttpConnector(httpClient);
return WebClient.builder()
.defaultHeader("HOST", "external-https")
.clientConnector(conn)
.build();
}
#Bean
WebClientReactiveAuthorizationCodeTokenResponseClient webClientReactiveAuthorizationCodeTokenResponseClient() {
final WebClientReactiveAuthorizationCodeTokenResponseClient webClientReactiveAuthorizationCodeTokenResponseClient = new WebClientReactiveAuthorizationCodeTokenResponseClient();
final WebClient webClient = webClient();
webClientReactiveAuthorizationCodeTokenResponseClient.setWebClient(webClient);
return webClientReactiveAuthorizationCodeTokenResponseClient;
}
#Bean
WebClientReactiveClientCredentialsTokenResponseClient webClientReactiveClientCredentialsTokenResponseClient() {
final WebClientReactiveClientCredentialsTokenResponseClient webClientReactiveClientCredentialsTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
final WebClient webClient = webClient();
webClientReactiveClientCredentialsTokenResponseClient.setWebClient(webClient);
return webClientReactiveClientCredentialsTokenResponseClient;
}
#Bean
WebClientReactiveRefreshTokenTokenResponseClient webClientReactiveRefreshTokenTokenResponseClient() {
final WebClientReactiveRefreshTokenTokenResponseClient webClientReactiveRefreshTokenTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient();
final WebClient webClient = webClient();
webClientReactiveRefreshTokenTokenResponseClient.setWebClient(webClient);
return webClientReactiveRefreshTokenTokenResponseClient;
}
#Bean
WebClientReactivePasswordTokenResponseClient webClientReactivePasswordTokenResponseClient() {
final var client = new WebClientReactivePasswordTokenResponseClient();
final WebClient webClient = webClient();
client.setWebClient(webClient);
return client;
}
#Bean
DefaultReactiveOAuth2UserService reactiveOAuth2UserService() {
final DefaultReactiveOAuth2UserService userService = new DefaultReactiveOAuth2UserService();
final WebClient webClient = webClient();
userService.setWebClient(webClient);
return userService;
}
Disabled the certificate validation - the connection is only between keycloak and gateway , both are in the kubernetes and otherwise would have used http connection, if not for this issue
The host part tells the keyclock what is the host to use for the issuer
Another issue encountered is that the location return when redirecting to authentication contains the internal url and not the external which the outside world doesn't know of
For that ,update the location that returns from the gateway
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
...
oauth2Login(oAuth2LoginSpec -> oAuth2LoginSpec
...
.addFilterAfter(new LoginLocationFilter("external-https"), SecurityWebFiltersOrder.LAST)
...
public class LoginLocationFilter implements WebFilter {
private final String externalUrl;
public LoginLocationFilter(String externalUrl) {
this.externalUrl = externalUrl;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
//before commit ,otherwise the headers will be read only
exchange.getResponse().beforeCommit(() -> {
fixLocation(exchange);
return Mono.empty();
});
return chain.filter(exchange);
}
...

Kafka - Redirect messages from "Topic A" to "Topic B" based on header value

I would like to redirect kafka messages from a topic called "all-topic" to a topic named "headervalue-topic" where headervalue is the value of a custom header each message has.
At the moment i'm using a custom console application that consumes messages and redirects the messages to the correct topic, but it only process 16 messages per second.
Both kafka and zookeeper are running in a docker container, configured as such :
zookeeper:
image: "wurstmeister/zookeeper:latest"
restart: always
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_SERVER_ID: 1
kafka:
hostname: kafka
image: "wurstmeister/kafka:latest"
restart: always
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_ADVERTISED_HOST_NAME: kafka
KAFKA_ADVERTISED_PORT: 9092
What is the best and fastest way to achieve my goal?
I do know about the existence of Kafka Streams, but i'm not familiar with Java so in case you'd like to suggest Kafka Streams a little example would be appreciated :)
Many Thanks!
Here is the solution i came up with, using kafka-streams nodejs library :
const {KafkaStreams} = require("kafka-streams");
const {nativeConfig: config} = require("./config.js");
const kafkaStreams = new KafkaStreams(config);
const myConsumerStream = kafkaStreams.getKStream("all-topic");
myConsumerStream
.mapJSONConvenience()
.filter((element) => {
return element.value.type == "Article";
})
.tap((element) => {console.log("Got Article")})
.mapWrapKafkaValue()
.to("Article-topic", 1, "buffer");
myConsumerStream.start();
From what I know you can't access the header directly through DSL.
You can access it through ProcessorContext using stream processor though and here is a little example I came up with:
public class CustomProcessor1 implements Processor<String, String> {
private ProcessorContext context;
#Override
public void init(ProcessorContext processorContext) {
this.context = processorContext;
}
#Override
public void process(String key, String value) {
HashMap<String, String> headers = new HashMap<>();
for (Header header : context.headers()) {
headers.put(header.key(), new String(header.value()));
}
String headerValue = headers.get("certainHeader").replace("\"", "");
if (headerValue.equals("expectedHeaderValue")) {
context.forward(key, value);
}
}
Above is the processor which will forward messages with certainHeader that matches headerValue to downstream process. This processor will be used when creating the streaming topology like below:
public static void main(String[] args) throws Exception {
Properties props = getProperties();
final Topology topology = new Topology()
.addSource("SOURCE", "all.topic")
.addProcessor("CUSTOM_PROCESSOR_1", CustomProcessor1::new, "SOURCE")
.addProcessor("CUSTOM_PROCESSOR_2", CustomProcessor2::new, "SOURCE")
.addSink("SINK1", "headervalue1-topic", "CUSTOM_PROCESSOR_1")
.addSink("SINK2", "headervalue2-topic", "CUSTOM_PROCESSOR_2");

* Unrecognized field at: database Did you mean?: - metrics - server - logging - DROPWIZARD

I cannot start my dropwizard application after add database details in my application configuration file (server.yml).
server.yml (app config file)
server:
applicationConnectors:
- type: http
port: 8080
adminConnectors:
- type: http
port: 9001
database:
# the name of your JDBC driver
driverClass: org.postgresql.Driver
# the username
user: dbuser
# the password
password: pw123
# the JDBC URL
url: jdbc:postgresql://localhost/database
# any properties specific to your JDBC driver:
properties:
charSet: UTF-8
# the maximum amount of time to wait on an empty pool before throwing an exception
maxWaitForConnection: 1s
# the SQL query to run when validating a connection's liveness
validationQuery: "/* MyService Health Check */ SELECT 1"
# the timeout before a connection validation queries fail
validationQueryTimeout: 3s
# the minimum number of connections to keep open
minSize: 8
# the maximum number of connections to keep open
maxSize: 32
# whether or not idle connections should be validated
checkConnectionWhileIdle: false
# the amount of time to sleep between runs of the idle connection validation, abandoned cleaner and idle pool resizing
evictionInterval: 10s
# the minimum amount of time an connection must sit idle in the pool before it is eligible for eviction
minIdleTime: 1 minute
As result of run dropwizard application I can see:
has an error:
* Unrecognized field at: database
Did you mean?:
- metrics
- server
- logging
In addition to code given by dropwizard example you need to add a setter for database property.
#Valid
#NotNull
#JsonProperty("database")
private DataSourceFactory database = new DataSourceFactory();
public DataSourceFactory getDataSourceFactory() {
return database;
}
public void setDatabase(DataSourceFactory database) {
this.database = database;
}
In your application configuration java file, you have to add the matching property for "database". If the properties you're specifying are the standard ones (which they look to be, good!) then you can keep with the DataSourceFactory type:
public class ExampleConfiguration extends Configuration {
#Valid
#NotNull
#JsonProperty
private DataSourceFactory database = new DataSourceFactory();
public DataSourceFactory getDataSourceFactory() {
return database;
}
public void setDatabase(DataSourceFactory database) {
this.database = database;
}
}
Example here: http://www.dropwizard.io/0.9.0/docs/manual/jdbi.html