0

I'm trying to configure Actuator's health probes to include checks for an external service that is nested beyond the first level. For example, when calling /actuator/health, these are the available health indicators:

    {
       "status":"DOWN",
       "components":{
          "jms":{
             "status":"DOWN",
             "components":{
                "broker1":{
                   "status":"DOWN",
                   "details":{
                      "error":"javax.jms.JMSException: Failed to create session factory"
                   }
                },
                "broker2":{
                   "status":"UP",
                   "details":{
                      "provider":"ActiveMQ"
                   }
                }
             }
          },
          "livenessState":{
             "status":"UP"
          },
          "readinessState":{
             "status":"UP"
          }
       },
       "groups":[
          "liveness",
          "readiness"
       ]
    }

Under the jms component, there are two brokers - broker1 and broker2. I can configure Actuator to include jms in the readiness group like:

  endpoint:
    health:
      probes:
        enabled: true
      enabled: true
      show-details: always
      group:
        readiness:
          include: readinessState, jms

But, this will include all brokers in the readiness probe.

When calling /actuator/health/readiness, I get:

{
   "status":"DOWN",
   "components":{
      "jms":{
         "status":"DOWN",
         "components":{
            "broker1":{
               "status":"DOWN",
               "details":{
                  "error":"javax.jms.JMSException: Failed to create session factory"
               }
            },
            "broker2":{
               "status":"UP",
               "details":{
                  "provider":"ActiveMQ"
               }
            }
         }
      },
      "readinessState":{
         "status":"UP"
      }
   }
}

Since the readiness probe in Kubernetes will only prevent routing web requests to my pod, I have cases where I can process the request if broker1 is down as long as broker2 is up. Is there a way to configure Actuator to include nested health indicators in a Health Group instead of just the root ones? I tried combinations like broker1, jms.broker1, jms/broker1, jms\broker1 to no avail.

If it is not directly supported through configuration, is there a custom component I could create that would give me the desired behavior. For example, I thought of the possibility of writing a custom CompositeHealthContributor, but I am not sure if I can aggregate existing health indicators. I do not want to replicate the health checks that are already being done.

One other related use case is to consider the service as healthy as long as one of a group of external resources is available. For example, I have an identical broker in two data centers. As long as one of the brokers is available, then my service could be considered healthy. What would be a good approach for this use case?

1 Answers1

0

I believe that as long as you expose nested indicators as Health objects, you will get the desired result:

@Component
public class CustomHealthIndicator implements ReactiveHealthIndicator {

    private final List<CustomContributor> customContributors;

    public CustomHealthIndicator(List<CustomContributor> customContributors) {
        Assert.notNull(customContributors, "At least one contributor must be available");
        this.customContributors = customContributors;
    }

    @Override
    public Mono<Health> health() {
        return checkServices()
            .onErrorResume(ex -> Mono.just(new Health.Builder().down(ex).build()));
    }

    private Mono<Health> checkServices() {
        return Mono.fromSupplier(() -> {
            final Builder builder = new Builder();
            AtomicBoolean partial = new AtomicBoolean();
            this.customContributors.parallelStream()
                .forEach(customContributor -> {
                    final Object status = customContributor.getStatus();
                    final boolean isAnErrorState = status instanceof Throwable;
                    partial.set(partial.get() || isAnErrorState);

                    if(isAnErrorState){
                        builder.withDetail(customContributor.getName(), new Builder().down().withException((Throwable) status).build());
                    } else {
                        builder.withDetail(customContributor.getName(), new Builder().up().withDetail("serviceDetail", status).build());
                    }
                });
            builder.status(partial.get() ? Status.DOWN : Status.UP);
            return
                builder.build();
        });
    }
}
Darek
  • 4,687
  • 31
  • 47