Kubernetes with OpenShift World Tour: Get hands-on experience and build applications fast! Find a workshop!

Health checks in Kubernetes for your Node.js applications

Ensuring that your container-based applications are working is critical to your business’ success. The health checks feature in Kubernetes performs specific checks on pods to determine information about their current state and to assess the state of your application at different stages through its lifecycle. These checks include liveness and readiness.

In this tutorial, we look at the fundamentals of health checks and show you how to use them with a Node.js application deployed on Kubernetes.

Types of health checks

There are four types of health checks in Kubernetes: startup, liveness, readiness, and shutdown checks.

Startup checks

A startup check is a health check that’s performed when the pod is created or restarted. Startup checks ensure that any code that needs to run before an application is ready to receive traffic has been executed.

If there are any failures during the starting of an application, this check returns an error which causes Kubernetes to destroy the pod.

Liveness checks

Liveness checks are performed while the application is running. Specifically, you would use a liveness check to determine whether your application is live and to ensure that it’s not in a state where the container is still running, but the application is unable to function.

If a liveness check fails, Kubernetes will destroy the pod and create a new pod for the application.

Readiness checks

Readiness checks are also performed during runtime and are used to determine when an application is ready to receive traffic. You would typically use these checks to see the availability of dependent services or resources that your application relies on.

For example, if your application relies on an external API, you could register a readiness check that determines whether the external API is available. If the external API is available, the pod will start receiving traffic. If the external API is unavailable, the readiness endpoint will return as unavailable, and Kubernetes will not direct traffic to that pod.

Kubernetes provides information in their documentation on how to configure your liveness and readiness checks.

Shutdown checks

A shutdown check determines whether an application cleans up any resources being used. This is often used for activities such as closing network connections and saving any required data before the application shuts down.

Kubernetes allows you to configure these checks to run when a SIGTERM message is received by the container. Kubernetes will send a SIGTERM message to containers which means they will be shut down after a termination grace period. After this period, Kubernetes will then send a SIGKILL signal to the container, and it is removed. Note that Kubernetes does not wait for your pod to shut down; it sends SIGKILL once the grace period is over, regardless.

Therefore, if your container shutdown takes longer than the default grace period of 30 seconds, you should increase the terminationGracePeriodSeconds value in your pod YAML.

Node.js and health checks

If you have a Node.js or TypeScript application that you want to manage the lifecycle of, we recommend using the Cloud-Health or CloudHealthConnect modules provided by CloudNativeJS.

Cloud Health provides a framework for you to register and run Readiness, Liveness and a Combined Health (Readiness and Liveness) checks, as well as shutdown handling.

Cloud Health Connect wraps the Cloud Health functionality and exposes it as a connect based middleware that can be used with server frameworks such as Express and LoopBack.

Put it into practice: Register health checks in an Express.js app using CloudNativeJS

Now that you know about the different health checks, I’ll show you how to use each of these different health checks in a Node.js Express application to manage its lifecycle.

Prerequisites

To follow the rest of this tutorial, you need to have Node.js 8.2.0 or later installed on your machine.

To start, you use the Express generator to create an application skeleton. I’ll show you how to install the health checks in this application in a new empty directory. The Express generator will then use the name of the directory as the default name of the project.

Step 1. Create the application skeleton

  1. Create the new directory:

    
     mkdir express-app
    
     cd express-app
    
  2. Run the Express generator using npx

    
     npx express-generator
    
  3. Install and start your application

    
     npm install
    
     npm start
    

You should now have a skeleton Express app running on: http://localhost:3000

To stop your application, use \*\*Ctrl-C\*\* in the terminal window where your application is running.

The Express generator will give you the following directory structure:

.
├── app.js
├── bin
|   ├── www
├── package-lock.json
├── package.json
├── public
|   ├── images
|   ├── javascripts
|   ├── stylesheets
|   |   ├── style.css
└── routes
    ├── index.js
    └── users.js
├── views
    ├── error.jade
    ├── index.jade
    ├── layout.jade

7 directories, 10 files

Step 2. Add health checks to your app

Now you need to add the health checks to the app.js file with the Connect Middleware provided in the @cloudnative/health-connect package.

First, add a Liveness check, using the following steps:

  1. Add the @cloudnative/health-connect dependency to your project:

    
     npm install @cloudnative/health-connect
    
  2. Add a HealthChecker to your app.js:

    
     const health = require('@cloudnative/health-connect');
    
     let healthCheck = new health.HealthChecker();
    
  3. Register a Liveness check:

    
     const livePromise = () => new Promise((resolve, _reject) => {
    
       const appFunctioning = true;
    
       // You should change the above to a task to determine if your app is functioning correctly
    
       if (appFunctioning) {
    
         resolve();
    
       } else {
    
         reject(new Error("App is not functioning correctly"));
    
       }
    
     });
    
     let liveCheck = new health.LivenessCheck("LivenessCheck", livePromise);
    
     healthCheck.registerLivenessCheck(liveCheck);
    

Now that the Liveness check is configured, let’s configure a Readiness check. This is done similarly to how we used the healthCheck defined above in step 2.


let readyCheck = new health.PingCheck("example.com");

healthCheck.registerReadinessCheck(readyCheck);

The above readiness check uses the PingCheck functionality from the @cloudnative module to send a request to example.com. If a response is received, the check gives the status UP. You can use this type of check to send a request to a dependency of your application and to check your application’s ability to send and receive requests.

You can register all of the above checks in this way in both the @cloudnative/health and @cloudnative/health-connect modules. We chose to use the connect module since we will now register endpoints for each of our different health checks using Express’ middleware functionality.

  1. Register your Liveness endpoint using:

    
     app.use('/live', health.LivenessEndpoint(healthCheck));
    
  2. Register a readiness endpoint

    
     app.use('/ready', health.ReadinessEndpoint(healthCheck));
    
  3. Register a combined health endpoint

    
     app.use('/health', health.HealthEndpoint(healthCheck));
    

Now start your application again using npm start and you will have a Readiness, Liveness, and a combined Health endpoint which you can access at the following URLs:

You should see the following UP status from each of the endpoints:

  • Readiness endpoint:
      {“status”: ”UP”,”checks”:[{“name”: “PingCheck HEAD:example.com:80/“, “state”: “UP”, “data”:{“reason” : ““ }}]}
    
  • Liveness endpoint:
      {“status”: “UP”,“checks”: [{“name”: “LivenessCheck”, “state”: “UP”, “data” :{“reason” : ““ }}]}
    
  • Health endpoint:
      {“status”: ”UP”, ”checks” : [{“name”: “PingCheck HEAD:example.com:80/“, “state”: “UP”, “data”: {“reason”: “” }} , { “name”: “LivenessCheck”, “state”: “UP”, “data”: {“reason” : ““ }}]}
    

As you can see above, the /ready endpoint displays the result of the readiness check you implemented, the /live displays the liveness check, and the /health displays both the liveness and readiness checks as a combined health check. The /health endpoint is especially useful in cloud platforms where there is only one endpoint for liveness and readiness checks such as Cloud Foundry.

Now you have a Node.js app with health checking set up and ready to be deployed to your Kubernetes cluster.

Andrew Hughes