Health Checks and OpenTelemetry
Understand the readiness/liveness health checks and OpenTelemetry tracing and metrics the client registers by default.
When you call AddKubeMQClient (or AddKeyedKubeMQClient), the client package wires observability into your service for you. It registers two health checks — readiness and liveness — and enrolls the KubeMQ SDK's OpenTelemetry tracing source and meter into the Aspire telemetry pipeline. All of this happens automatically, and each piece can be turned off independently through KubeMQClientSettings.
The registration lives in the ConfigureObservability step of KubeMQClientExtensions: it only registers health checks when DisableHealthChecks is false, and only adds the tracing source or meter when DisableTracing / DisableMetrics are false.
Prerequisites
builder.AddKubeMQClient(...)(orAddKeyedKubeMQClient) already registered in the service'sProgram.cs(see Getting Started)- The Aspire
ServiceDefaultsproject'sMapDefaultEndpoints()wired up, if you want the checks exposed over/healthand/alive
Readiness Check (ready tag)
The readiness check answers "can this service currently reach KubeMQ and handle traffic?" It first inspects IKubeMQClient.State, and only when the client is Ready does it make a network call — PingAsync — to confirm the broker is actually reachable.
var state = _client.State;
switch (state)
{
case ConnectionState.Ready:
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(_timeout);
await _client.PingAsync(cts.Token).ConfigureAwait(false);
return HealthCheckResult.Healthy("KubeMQ connection is healthy");
}
catch (OperationCanceledException)
{
return HealthCheckResult.Unhealthy("KubeMQ health check timed out");
}
catch (Exception)
{
return HealthCheckResult.Unhealthy(
"KubeMQ health check failed: unable to reach server");
}
case ConnectionState.Reconnecting:
return HealthCheckResult.Degraded("KubeMQ client is reconnecting");
case ConnectionState.Connecting:
return HealthCheckResult.Unhealthy("KubeMQ client is connecting");
case ConnectionState.Idle:
return HealthCheckResult.Unhealthy("KubeMQ client is not connected");
case ConnectionState.Closed:
return HealthCheckResult.Unhealthy("KubeMQ client has been disposed");
}The PingAsync call is bounded by HealthCheckTimeout: the check creates a linked cancellation token that cancels after the configured timeout, so a hung broker produces an Unhealthy result ("KubeMQ health check timed out") rather than blocking the probe. The default timeout is 5 seconds.
The ping fires only in the Ready state. In every other state the readiness check returns a status from the connection state alone, without a network round trip — so a disconnected client fails fast instead of waiting for a ping to time out.
Liveness Check (live tag)
The liveness check answers a different question: "is this process still in a state worth keeping alive?" It is state-only — it never touches the network — and is therefore cheap enough to call frequently.
var state = _client.State;
var result = state switch
{
ConnectionState.Ready => HealthCheckResult.Healthy("KubeMQ client is ready"),
ConnectionState.Reconnecting => HealthCheckResult.Degraded("KubeMQ client is reconnecting"),
_ => HealthCheckResult.Unhealthy($"KubeMQ client state: {state}"),
};
return Task.FromResult(result);A failed liveness check signals that the process should be restarted — for example by a Kubernetes liveness probe. Ready maps to Healthy, Reconnecting maps to Degraded (the SDK is still trying to recover on its own, so a restart would be premature), and any other state — Connecting, Idle, or Closed — maps to Unhealthy.
Health Check Naming
For a connection named messaging, the client registers two checks: kubemq-messaging-ready (tagged ready) and kubemq-messaging-live (tagged live). The general form is kubemq-{connectionName}-ready and kubemq-{connectionName}-live, so multiple keyed clients each get their own distinctly named pair.
var healthCheckName = $"kubemq-{name}";
var readyName = $"{healthCheckName}-ready";
var liveName = $"{healthCheckName}-live";
builder.Services.AddHealthChecks()
.Add(new HealthCheckRegistration(
readyName,
sp => sp.GetRequiredKeyedService<IHealthCheck>(readyName),
failureStatus: null,
tags: ["ready"]))
.Add(new HealthCheckRegistration(
liveName,
sp => sp.GetRequiredKeyedService<IHealthCheck>(liveName),
failureStatus: null,
tags: ["live"]));Both checks map the SDK connection state to a health status as follows:
| Connection State | Health Status |
|---|---|
| Ready | Healthy (readiness includes a live PingAsync) |
| Reconnecting | Degraded |
| Connecting | Unhealthy |
| Idle | Unhealthy |
| Closed | Unhealthy |
Mapping Checks to Endpoints
The health checks are registered in the DI container, but they are not exposed over HTTP until you map them to endpoints. In the standard Aspire ServiceDefaults project, MapDefaultEndpoints filters checks by tag: the /health endpoint serves the readiness set, and /alive serves the liveness set.
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
if (app.Environment.IsDevelopment())
{
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
}
return app;
}Because the predicate filters on the ready and live tags, the KubeMQ checks are picked up automatically alongside any other tagged checks in your app (for example the default self check, which ServiceDefaults tags live). Point a Kubernetes readiness probe at /health and a liveness probe at /alive.
The sample maps these endpoints only when app.Environment.IsDevelopment(). In production you typically expose them unconditionally (and secure them) — adjust the guard to match your deployment.
Disabling Health Checks
Set DisableHealthChecks to true to skip registering both checks. When health checks are enabled, HealthCheckTimeout must be a positive value; otherwise BindAndResolveSettings throws an ArgumentOutOfRangeException before the client is built.
if (!settings.DisableHealthChecks && settings.HealthCheckTimeout <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
nameof(settings.HealthCheckTimeout),
settings.HealthCheckTimeout,
"Health check timeout must be a positive value.");
}HealthCheckTimeout defaults to 5 seconds, so you only need to set it explicitly when you want a different bound on the readiness ping:
/// <summary>Gets or sets whether to disable health check registration. Default: false.</summary>
public bool DisableHealthChecks { get; set; }
/// <summary>Gets or sets the health check timeout. Default: 5 seconds.</summary>
public TimeSpan HealthCheckTimeout { get; set; } = TimeSpan.FromSeconds(5);OpenTelemetry Tracing
Tracing is enabled by default. The client registers the SDK's activity source — KubeMQ.Sdk — with the OpenTelemetry tracer provider, so spans produced by KubeMQ operations flow into the same trace pipeline configured by your ServiceDefaults.
if (!settings.DisableTracing)
{
otel.WithTracing(t => t.AddSource("KubeMQ.Sdk"));
}Set DisableTracing to true to opt out of registering the source.
OpenTelemetry Metrics
Metrics work the same way: by default the client registers the SDK meter — KubeMQ.Sdk — with the OpenTelemetry meter provider.
if (!settings.DisableMetrics)
{
otel.WithMetrics(m => m.AddMeter("KubeMQ.Sdk"));
}Set DisableMetrics to true to opt out. Both the source and the meter use the same name, KubeMQ.Sdk, matching the instrumentation built into the KubeMQ .NET SDK.
Configuration via appsettings.json
All four flags bind from the Aspire:KubeMQ:Client configuration section, so you can control observability without changing code:
{
"Aspire": {
"KubeMQ": {
"Client": {
"DisableHealthChecks": false,
"DisableTracing": false,
"DisableMetrics": false,
"HealthCheckTimeout": "00:00:05"
}
}
}
}HealthCheckTimeout is a duration string in hh:mm:ss form — 00:00:05 is five seconds. For a keyed client, nest the same keys under the keyed section (for example Aspire:KubeMQ:Client:orders).
These flags are independent. You can, for example, keep tracing on while turning off metrics — set DisableMetrics to true and leave DisableTracing and DisableHealthChecks at their defaults. Disabling both DisableTracing and DisableMetrics skips the OpenTelemetry registration entirely.
Try It Locally
To exercise the health checks against a real broker, run KubeMQ in Docker. Port 50000 is the gRPC port the client connects to — it is always on, so the native gRPC client needs no connector enable flag.
docker run -d \ --name kubemq \ -p 50000:50000 \ -e KUBEMQ_TOKEN=YOUR_LICENSE_KEY \ europe-docker.pkg.dev/kubemq/images/kubemq:nextWith the broker up and your service running, the readiness endpoint reports Healthy once the client reaches the Ready state:
curl http://localhost:5000/health # readiness (ready tag)
curl http://localhost:5000/alive # liveness (live tag)Replace 5000 with your service's actual HTTP port. Stop the broker container to watch the readiness check transition to Unhealthy while the liveness check follows the SDK's Reconnecting (Degraded) state.
Related
- Getting Started to provision a broker and register the client
- Keyed multi-instance clients for per-broker health checks and observability
- .NET Aspire overview for the two-package model and architecture
Was this page helpful?