Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readiness check support and documentation #114

Merged
merged 3 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 53 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,42 @@

A library for adding reform standard compliant healthchecks to nodejs applications.

It exposes 3 endpoints:

1. `/health` - Returns 200 by default along with `buildInfo`, can optionally include result evaluating all `checks` passed in config.
2. `/health/livness` - Returns 200 always.
3. `/health/readiness` - Returns 200 by default , can optionally include result evaluating all `readinessChecks` passed in config.

## Usage

Configure an express.js handler with checks.

```javascript
const healthcheck = require('@hmcts/nodejs-healthcheck');
const config = {
checks: {
mySimpleWebCheck: healthcheck.web("https://example.com/status"),
myComplexWebCheck: healthcheck.web("https://example.com/other", {
callback: (err, res) => {
return res.body.status == "good" ? healthcheck.up() : healthcheck.down()
},
timeout: 5000,
deadline: 10000,
}),
myRawCheck: healthcheck.raw(() => {
return myInternalCheck() ? healthcheck.up() : healthcheck.down()
})
},
buildInfo: {
myCustomBuildInfo: "yay"
}
};
healthcheck.addTo(app, config);
```

app.get("/status", healthcheck.configure({
You can optionally include [readiness checks](#what-to-include-in-readiness-checks).

```javascript
const config = {
checks: {
mySimpleWebCheck: healthcheck.web("https://example.com/status"),
myComplexWebCheck: healthcheck.web("https://example.com/other", {
Expand All @@ -25,12 +53,34 @@ app.get("/status", healthcheck.configure({
return myInternalCheck() ? healthcheck.up() : healthcheck.down()
})
},
readinessChecks: {
mySimpleWebCheck: healthcheck.web("https://example.com/status")
},
buildInfo: {
myCustomBuildInfo: "yay"
}
}));
};
healthcheck.addTo(app, config);
```

## what to include in readiness checks

- On Kubernetes, readiness probes will be called periodically throughout the lifetime of the container. Container will be made temporarily unavailable from serving traffic when the readiness check fails.
- The requests won't even reach your application to handle errors. So, it is very important to consider what checks should be included into readiness probe.
- While adding all dependant services to readiness check can help in identifying any misconfiguration during startup, it could cause unwanted downtime for the application.
- K8s introduced startUp Probes (Alpha in 1.16 ) to handle startup cases separately.

Based on above, you should include a dependency into readiness checks only if they are exclusive/hard dependencies for your service. Unavailability of soft dependencies needs to be handled in code to give appropriate customer experience.

Good example for check to be included in readiness:

- A private cache / database like `Redis` or `Elastic Search` which are exclusive to the application (not shared).

Bad example for check to be included in readiness:

- Any shared components like IDAM, S2S or CCD.


## Publishing

Bump the version (SemVer) and create a release in the GitHub UI, Travis CI will then build test and release to the npm registry.
Expand Down
1 change: 1 addition & 0 deletions healthcheck/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const outputs = require('./outputs')
function addTo (app, config) {
app.get('/health', routes.configure(config))
app.get('/health/liveness', (req, res) => res.json(outputs.status(outputs.UP)))
app.get('/health/readiness', routes.checkReadiness(config.readinessChecks))
}

module.exports = {
Expand Down
28 changes: 27 additions & 1 deletion healthcheck/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,33 @@ function configure (config) {
}
}

function checkReadiness (readinessChecks = {}) {
const check = new checks.CompositeCheck(readinessChecks)

return (req, res) => {
return Promise
.resolve(check.call(req, res))
.then((results) => {
const allOk = Object.values(results)
.every((result) => result.status === outputs.UP)
const output = Object.assign(
outputs.status(allOk),
results
)
const status = allOk ? 200 : 500
if (!allOk) {
const downHealthChecks = Object.values(results)
.filter((result) => result.status === outputs.DOWN)

logger.error('Health check failed, result for down endpoints: ', JSON.stringify(downHealthChecks))
}
res.status(status).json(output)
})
}
}

module.exports = {
getBuildInfo: getBuildInfo,
configure: configure
configure: configure,
checkReadiness: checkReadiness
}
17 changes: 16 additions & 1 deletion test/unit/installTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('Testing liveness', function () {
})
})

describe('Testing Readiness for 200 OK', function () {
describe('Testing health for 200 OK', function () {
it('should return 200 OK', function (done) {
request(app)
.get('/health')
Expand All @@ -46,3 +46,18 @@ describe('Testing Readiness for 200 OK', function () {
})
})
})

describe('Testing readiness for 200 OK', function () {
it('should return 200 OK', function (done) {
request(app)
.get('/health/readiness')
.expect(200)
.end((err, res) => {
if (err) {
return done(err)
}
expect(res.body.status).to.be.equal('UP')
done()
})
})
})
37 changes: 37 additions & 0 deletions test/unit/routesTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,43 @@ describe('Routes', () => {
return route(req, res)
})

it('should return 200 and UP if readiness check is undefined', () => {
const route = routes.checkReadiness()
const [req, res] = makeReqRes(200, {
status: 'UP'
})

return route(req, res)
})

it('should return 200 OK if all checks pass', () => {
const route = routes.checkReadiness({
check1: makeCheck(true),
check2: makeCheck(true)
})
const [req, res] = makeReqRes(200, {
status: outputs.UP,
check1: { status: 'UP' },
check2: { status: 'UP' }
})

return route(req, res)
})

it('should return 500 DOWN if any readiness checks fail', () => {
const route = routes.checkReadiness({
check1: makeCheck(false),
check2: makeCheck(true)
})
const [req, res] = makeReqRes(500, {
status: 'DOWN',
check1: { status: 'DOWN' },
check2: { status: 'UP' }
})

return route(req, res)
})

it('should return the extra build info', () => {
const route = routes.configure({
checks: {
Expand Down