Azure API Management Healthcheck via Azure Functions
Azure API Management Healthcheck via Azure Functions

Azure API Management Healthcheck via Azure Functions

2021, May 09    

Hi All! Today we’ll see how it’s possible to programmatically check if an Azure API Management instance is alive (an’kickin).

TR;DR;

A simple GET request to https://[APIM-url-here]/status-0123456789abcdef will do the trick.


Slightly longer version

In a world of distributed transactions, microservices, reliability, and automation, it’s extremely important to keep under control all the “moving parts” of our applications. Health monitoring and proper instrumentation play a vital role in achieving the success of a system in production.

On some occasions, your microservices might not depend “just” on the Database, but also rely on other internal services or third-party APIs. Luckily for us, .NET has nice support for different types of healthchecks, so we don’t have to reinvent the wheel each time.

But what happens when we have a dependency on something different?

Here’s a real story. A friend of a cousin of an uncle of mine (yeah…) was working on an Azure Function App that had a dependency on an internal service handled by a different team. For some super-complex reasons, this team was not allowed to expose their service directly, but had to go through Azure API Management instead.

Now, we can of course expose a GET endpoint with a policy that probes the underlying service. But this very distant friend got curious: how can we assert if the actual APIM is alive?

Well, turns out that APIM exposes a default healthcheck endpoint. All you have to do is a GET request to /status-0123456789abcdef and if everything is ok, you’ll receive a 200 status back with an empty body. That’s it!

Let’s see how we can do this via code. The first thing is to install the Nuget package Microsoft.Extensions.Diagnostics.HealthChecks.

Then we have to create a custom class to run the healthcheck :

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace MyFunctionApp
{
    public class ApimHealthCheck : IHealthCheck
    {
        private readonly HttpClient _httpClient;

        public ApimHealthCheck(IHttpClientFactory httpClientFactory)
        {
            if (httpClientFactory is null)            
                throw new ArgumentNullException(nameof(httpClientFactory));            

            _httpClient = httpClientFactory.CreateClient("APIM");
        }

        public async Task<HealthCheckResult> CheckHealthAsync(
            HealthCheckContext context,
            CancellationToken cancellationToken = default)
        {
            var response = await _httpClient.GetAsync("/status-0123456789abcdef");
            return (response.StatusCode == System.Net.HttpStatusCode.OK) ?
                HealthCheckResult.Healthy() :
                HealthCheckResult.Unhealthy();
        }
    }
}

At this point we can register it in our Startup.cs :

[assembly: FunctionsStartup(typeof(Startup))]
namespace MyFunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
		{
			Services.AddHttpClient(); 
			
			Services.AddHttpClient("APIM", client =>
            {
                client.BaseAddress = new Uri(Environment.GetEnvironmentVariable("APIM_URL"));
            });
			
			Services.AddHealthChecks()
			        .AddCheck<ApimHealthCheck>("APIM");
		}
	}
}

Let’s see what’s happening here. First of all, we’re ensuring that IHttpClientFactory is available on our IoC Container. Then we’re registering a named client and configuring it with the APIM url. We can also include additional headers, like a Subscription key to handle authentication/authorization, for example.

And then, with the call to AddHealthChecks() we can start registering healthchecks for all our dependencies, including our custom ApimHealthCheck.

Now, since we’re writing a Function App, we also have to create a Function to run the checks:

public class HealthFunctions
{
    private readonly HealthCheckService _healthService;

    public HealthFunctions(HealthCheckService healthService)
    {
        _healthService = healthService ?? throw new System.ArgumentNullException(nameof(healthService));
    }

    [FunctionName(nameof(Healthcheck))]
    public async Task<IActionResult> Healthcheck(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "health")]
        HttpRequest req)
    {
        var healthResult = await _healthService.CheckHealthAsync();
        var status = (healthResult.Status == HealthStatus.Healthy) ? 200 : 500;

        return new JsonResult(new
        {
            status = healthResult.Status.ToString(),
            entries = healthResult.Entries.Select(e => new
            {
                name = e.Key,
                status = e.Value.Status.ToString(),
                e.Value.Description,
                e.Value.Exception
            })
        })
        {
            StatusCode = status
        };
    }
}

When invoked, this will return a 200 or a 500 status with this payload:

{"status":"Healthy","entries":[{"name":"APIM","status":"Healthy","description":null,"exception":null}]}

Ciao!