Hummus
Hummus

Reputation: 639

Spring Boot custom Kubernetes readiness probe

I want to implement custom logic to determine readiness for my pod, and I went over this: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.kubernetes-probes.external-state and they mention an example property: management.endpoint.health.group.readiness.include=readinessState,customCheck

Question is - how do I override customCheck? In my case I want to use HTTP probes, so the yaml looks like:

readinessProbe:
  initialDelaySeconds: 10
  periodSeconds: 10
  httpGet:
    path: /actuator/health
    port: 12345

So then again - where and how should I apply logic that would determine when the app is ready (just like the link above, i'd like to rely on an external service in order for it to be ready)

Upvotes: 12

Views: 14080

Answers (2)

KrzysztofS
KrzysztofS

Reputation: 86

customCheck is a key for your custom HealthIndicator. The key for a given HealthIndicator is the name of the bean without the HealthIndicator suffix

You can read: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.health.writing-custom-health-indicators

You are defining readinessProbe, so probably hiting /actuator/health/readiness is a better choice.

public class CustomCheckHealthIndicator extends AvailabilityStateHealthIndicator {

    private final YourService yourService;

    public CustomCheckHealthIndicator(ApplicationAvailability availability, YourService yourService) {
        super(availability, ReadinessState.class, (statusMappings) -> {
            statusMappings.add(ReadinessState.ACCEPTING_TRAFFIC, Status.UP);
            statusMappings.add(ReadinessState.REFUSING_TRAFFIC, Status.OUT_OF_SERVICE);
        });
        this.yourService = yourService;
    }

    @Override
    protected AvailabilityState getState(ApplicationAvailability applicationAvailability) {
        if (yourService.isInitCompleted()) {
            return ReadinessState.ACCEPTING_TRAFFIC;
        } else {
            return ReadinessState.REFUSING_TRAFFIC;
        }
    }

}

Upvotes: 5

izogfif
izogfif

Reputation: 7495

To expand KrzysztofS's answer:

First, create a custom health indicator like this:

import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.boot.actuate.availability.ReadinessStateHealthIndicator;
import org.springframework.boot.availability.ApplicationAvailability;
import org.springframework.boot.availability.AvailabilityState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.stereotype.Component;

@Component
public class MyCustomReadinessIndicator extends ReadinessStateHealthIndicator {
  private final AtomicBoolean ready = new AtomicBoolean();

  public MyCustomReadinessIndicator(ApplicationAvailability availability) {
    super(availability);
  }

  @Override
  protected AvailabilityState getState(ApplicationAvailability applicationAvailability) {
    return ready.get()
        ? ReadinessState.ACCEPTING_TRAFFIC
        : ReadinessState.REFUSING_TRAFFIC;
  }

  public void markAsReady() {
    if (ready.get()) {
      throw new IllegalStateException("Already initialized");
    }
    ready.set(true);
  }
}

This must be a bean, or Spring won't be able to discover it.

@Autowire your component to your service or another component, and call its markAsReady() function when this indicator should switch into "ready" state.

Next, add the name of the bean1 into "include" block for "readiness" group in your application.yaml file (if you're using application.properties, figure it out yourself).

management:
  endpoint:
    health:
      group:
        readiness:
          include: readinessState, myCustomReadinessIndicator
      show-components: always
      show-details: always
      probes:
        enabled: true

Next, try running your application and opening various Actuator endpoints.

Endpoint ready state of
your indicator
HTTP response
code
JSON response
/actuator/health false 503 {
"status": "OUT_OF_SERVICE",
"components":
{
"myCustomReadinessIndicator":
{
"status": "OUT_OF_SERVICE"
},
"livenessState":
{
"status": "UP"
},
"readinessState":
{
"status": "UP"
}
},
"groups": ["liveness", "readiness"]
}
/actuator/health/liveness false 200 {"status":"UP"}
/actuator/health/readiness false 503 {
"status": "OUT_OF_SERVICE",
"components":
{
"myCustomReadinessIndicator":
{
"status": "OUT_OF_SERVICE"
},
"readinessState":
{
"status": "UP"
}
}
}
/actuator/health true 200 {
"status": "UP",
"components":
{
"myCustomReadinessIndicator":
{
"status": "UP"
},
"livenessState":
{
"status": "UP"
},
"readinessState":
{
"status": "UP"
}
},
"groups": ["liveness", "readiness"]
}
/actuator/health/liveness true 200 {"status":"UP"}
/actuator/health/readiness true 200 {
"status": "UP",
"components":
{
"myCustomReadinessIndicator":
{
"status": "UP"
},
"readinessState":
{
"status": "UP"
}
}
}

Now, you need to set /actuator/health/readiness in your readiness probe's path

readinessProbe:
  initialDelaySeconds: 10
  periodSeconds: 10
  httpGet:
    path: /actuator/health/readiness
    port: 12345

Related: set liveness probe's path to /actuator/health/liveness not to /actuator/health, since /actuator/health will return 503 if your indicator isn't ready yet, even though livenessState is "UP".


1 Bean name is usually camel-cased name of the class, starting with lowercase letter, but you can override it by providing name in component annotation: @Component("overriddenName")

Upvotes: 15

Related Questions