Skip to content

Instantly share code, notes, and snippets.

@icebob
Last active October 22, 2023 19:50
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save icebob/c717ae22002b9ecaa4b253a67952da3a to your computer and use it in GitHub Desktop.
Save icebob/c717ae22002b9ecaa4b253a67952da3a to your computer and use it in GitHub Desktop.
Health-check middleware for Moleculer (for Kubernetes liveness readiness checks)

Health-check middleware for Moleculer

Use it for Kubernetes liveness & readiness checks. The middleware opens a HTTP server on port 3001. To check, open the http://localhost:3001/live & http://localhost:3001/ready URL.

Response

{
  "state": "up",
  "uptime": 7.419,
  "timestamp": 1562790370161
}

The state can be "starting" (503), "up" (200), "stopping" (503) or "down" (503).

Usage

Load with default options

// moleculer.config.js

const HealthMiddleware = require("./health-check.middleware.js");

module.exports = {
  middlewares: [
    HealthMiddleware()
  ]
};

Load with custom options

// moleculer.config.js

const HealthMiddleware = require("./health-check.middleware.js");

module.exports = {
  middlewares: [
    HealthMiddleware({
      port: 3333,
      readiness: {
        path: "/ready"
      },
      liveness: {
        path: "/live"
      }
    })
  ]
};

Usage in Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: greeter
spec:
  selector:
    matchLabels:
      app: greeter
  replicas: 1
  template:
    metadata:
      labels:
        app: greeter
    spec:
      containers:
      - name: greeter
        image: moleculer/demo:1.4.2
        livenessProbe:
          httpGet:
            path: /live
            port: 3001      
        readinessProbe:
          httpGet:
            path: /ready
            port: 3001      
"use strict";
const _ = require("lodash");
const http = require("http");
module.exports = function(opts) {
opts = _.defaultsDeep(opts, {
port: 3001,
readiness: {
path: "/ready"
},
liveness: {
path: "/live"
},
});
let state = "down";
let server;
function handler(req, res) {
if (req.url == opts.readiness.path || req.url == opts.liveness.path) {
const resHeader = {
"Content-Type": "application/json; charset=utf-8"
};
const content = {
state,
uptime: process.uptime(),
timestamp: Date.now()
};
if (req.url == opts.readiness.path) {
// Readiness if the broker started successfully.
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes
res.writeHead(state == "up" ? 200 : 503, resHeader);
} else {
// Liveness if the broker is not stopped.
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-command
res.writeHead(state != "down" ? 200 : 503, resHeader);
}
res.end(JSON.stringify(content, null, 2));
} else {
res.writeHead(404, http.STATUS_CODES[404], {});
res.end();
}
}
return {
created(broker) {
state = "starting";
server = http.createServer(handler);
server.listen(opts.port, err => {
if (err) {
return broker.logger.error("Unable to start health-check server", err);
}
broker.logger.info("");
broker.logger.info("K8s health-check server listening on");
broker.logger.info(` http://localhost:${opts.port}${opts.readiness.path}`);
broker.logger.info(` http://localhost:${opts.port}${opts.liveness.path}`);
broker.logger.info("");
});
},
// After broker started
started(broker) {
state = "up";
},
// Before broker stopping
stopping(broker) {
state = "stopping";
},
// After broker stopped
stopped(broker) {
state = "down";
server.close();
}
};
};
@giovanni-bertoncelli
Copy link

giovanni-bertoncelli commented Jan 14, 2022

@icebob I think there can be a issue with node >= 16 since the request handler is registered twice:

  • First in the createServer(handler)
  • Then in the server.on('request', handler)

The server.on line should be omitted.

@icebob
Copy link
Author

icebob commented Jan 15, 2022

You are right, thanks!

@nbvehbq
Copy link

nbvehbq commented Feb 3, 2022

Hi @icebob!
Is it correct to change state when broker 'not received heartbeat'?
And how we can do it in this middleware (may be subscribe to $node.disconnected or something else)?

@icebob
Copy link
Author

icebob commented Feb 3, 2022

Yeah, $node.disconnected with unexpected: true is called when heartbeat not received from a remote node.
https://github.com/moleculerjs/moleculer/blob/master/src/registry/node-catalog.js#L191

It's a local event, so you can also subscribe to it as broker.localBus.on("$node.disconnected", () => {}) in a middleware

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment