When running your ASP.NET core container/application you probably noticed that the first requests take longer on average. The cause of the longer request can be normal application loading and/or logic you have written that must initialize on your first call. It would be nice to warm up your container before a customer call is handled by your container. Kubernetes gives you the possibility to use a readiness probe to check and warm up your application.
Kubernetes probes
When running Kubernetes there are two probes to check your container. The readiness probe does checks if your container is ready to be called and the liveness probe is to check the health of the container. After starting the pod the readiness endpoint will be called. If the probe succeeds, the pod will be marked as ready and requests will be routed to the container/pod. The kubelet will repeat the check after a specific time period. The liveness probe is to check if the container is running in a healthy state. When the probe fails, the kubelet will killed the container/pod, and the container is subjected to its restart policy. The YAML to configure can look like this:
apiVersion: v1
kind: Pod
metadata:
name: mytestcontainer
labels:
app: mytestcontainer
spec:
containers:
- name: mytestcontainer
image: mytestimage/mytest
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /readiness
port: liveness-port
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthy
port: liveness-port
initialDelaySeconds: 15
periodSeconds: 20
Readiness and Healthy endpoints
Basically the readiness endpoint tells the kubelet when the container can handle requests, and the healthy endpoint if the container is in an error state. If your readiness fails your container will not receive requests till it succeeds again. When the healthy endpoint fails the container will be restarted.
Implement Readiness endpoint
For each project the the readiness can be a totally different implementation. It really depends on what you are doing in your application. For example you can check you SQL Migrations; setup connections to external dependencies like databases queues or other services; Initialize your Dependency objects; pre-fill your cache objects; do some basic operations on your controllers. In the implementation you can have a difference between your first warm-up and the readiness check it self. If the warm-up succeeds once you can skip it the next time. An simple skeleton implementation of a warmup and readiness.
[Route("warmup")]
public class WarmupController : Controller
{
private static bool isWarmedUp = false;
private readonly IMyDependencies dependency;
public WarmupController (IMyDependencies dependency, ... )
{
// Inject all dependencies needed to check your warm-up and readiness checks
this.dependency = dependency;
}
[HttpGet]
public async Task<IActionResult> Get()
{
if (!isWarmedUp)
{
// Do your warmup logic here!
dependency.DoWarmup();
isWarmedUp = true;
}
// Do your readiness logic here!
dependency.CheckReadiness();
return Ok();
}
}
Implement Healthy endpoint
In ASP.NET Core 2.2 you can use Middleware to implement and configure the health check for the container. The basic configuration of the health check can be done like:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks()
.AddCheck<MyHealthCheck>("my health check");
}
public void Configure(IApplicationBuilder app)
{
app.UseHealthChecks("/healthy");
}
The MyHealthCheck
implements the IHealthCheck
interface. In the MyHealthCheck class you can implement your own logic. You can have mutliple health checks. An simple skeleton implementation:
public class MyHealthCheck : IHealthCheck
{
private readonly IMyDependencies dependency;
public MyHealthCheck (IMyDependencies dependency, ... )
{
// Inject all dependencies needed to check your warm-up and readiness checks
this.dependency = dependency;
}
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default(CancellationToken))
{
// Execute health check logic here. This example sets a dummy
// variable to true.
var healthCheckResultHealthy = true;
if (healthCheckResultHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("The check indicates a healthy result."));
}
return Task.FromResult(
HealthCheckResult.Unhealthy("The check indicates an unhealthy result."));
}
}
You can have approaches for the implementations:
– active: do a checks when the endpoint is invoked
– passive : keep a global health status that can be changed when by any running code and report that status when the endpoint is invoked
Final thoughts
Warm-up and health checks made easy when running on Kubernetes. When you have implemented the endpoints, it is just adding the probes to the pod its configuration. Now you have configured when the pod will receive requests and when it is killed because it is unhealthy.
Links
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-2.2
https://www.hanselman.com/blog/HowToSetUpASPNETCore22HealthChecksWithBeatPulsesAspNetCoreDiagnosticsHealthChecks.aspx