Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const options = {
timeout: 1000, // [optional = 1000] number of milliseconds before forceful exiting
signal, // [optional = 'SIGTERM'] what signal to listen for relative to shutdown
signals, // [optional = []] array of signals to listen for relative to shutdown
useExit0, // [optional = false] instead of sending the received signal again without beeing catched, the process will exit(0)
sendFailuresDuringShutdown, // [optional = true] whether or not to send failure (503) during shutdown
beforeShutdown, // [optional] called before the HTTP server starts its shutdown
onSignal, // [optional] cleanup function, returning a promise (used to be onSigterm)
Expand Down Expand Up @@ -183,13 +184,18 @@ server.listen(PORT || 3000);

When Kubernetes or a user deletes a Pod, Kubernetes will notify it and wait for `gracePeriod` seconds before killing it.

During that time window (30 seconds by default), the Pod is in the `terminating` state and will be removed from any Services by a controller. The Pod itself needs to catch the `SIGTERM` signal and start failing any readiness probes.
During that time window (30 seconds by default), the Pod is in the `terminating` state and will be removed from any Services by a controller.
The Pod itself needs to catch the `SIGTERM` signal and start failing any readiness probes.

> If the ingress controller you use route via the Service, it is not an issue for your case. At the time of this writing, we use the nginx ingress controller which routes traffic directly to the Pods.

During this time, it is possible that load-balancers (like the nginx ingress controller) don't remove the Pods "in time", and when the Pod dies, it kills live connections.

To make sure you don't lose any connections, we recommend delaying the shutdown with the number of milliseconds that's defined by the readiness probe in your deployment configuration. To help with this, terminus exposes an option called `beforeShutdown` that takes any Promise-returning function.
To make sure you don't lose any connections, we recommend delaying the shutdown with the number of milliseconds that's defined by the readiness probe in your deployment configuration.
To help with this, terminus exposes an option called `beforeShutdown` that takes any Promise-returning function.

Also it makes sense to use the `useExit0 = true` option to signal Kubernetes that the container exited gracefully.
Otherwise APM's will send you alerts, in some cases.

```javascript
function beforeShutdown () {
Expand All @@ -201,7 +207,8 @@ function beforeShutdown () {
})
}
createTerminus(server, {
beforeShutdown
beforeShutdown,
useExit0: true
})
```

Expand Down
13 changes: 13 additions & 0 deletions lib/standalone-tests/terminus.useExit0.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'
const http = require('http')
const server = http.createServer((req, res) => res.end('hello'))

const { createTerminus } = require('../../')

createTerminus(server, {
useExit0: (process.argv[2] === 'true')
})

server.listen(8000, () => {
process.kill(process.pid, 'SIGTERM')
})
14 changes: 11 additions & 3 deletions lib/terminus.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function decorateWithHealthCheck (server, state, options) {
}

function decorateWithSignalHandler (server, state, options) {
const { signals, onSignal, beforeShutdown, onShutdown, timeout, logger } = options
const { signals, useExit0, onSignal, beforeShutdown, onShutdown, timeout, logger } = options

stoppable(server, timeout)

Expand All @@ -140,8 +140,14 @@ function decorateWithSignalHandler (server, state, options) {
await asyncServerStop()
await onSignal()
await onShutdown()
signals.forEach(sig => process.removeListener(sig, cleanup))
process.kill(process.pid, signal)
if (useExit0) {
// Exit process
process.exit(0)
} else {
// Resend recieved signal but remove traps beforehand
signals.forEach(sig => process.removeListener(sig, cleanup))
process.kill(process.pid, signal)
}
} catch (error) {
logger('error happened during shutdown', error)
process.exit(1)
Expand All @@ -168,6 +174,7 @@ function terminus (server, options = {}) {
const {
signal = 'SIGTERM',
signals = [],
useExit0 = false,
timeout = 1000,
healthChecks = {},
sendFailuresDuringShutdown = true,
Expand Down Expand Up @@ -201,6 +208,7 @@ function terminus (server, options = {}) {
if (!signals.includes(signal)) signals.push(signal)
decorateWithSignalHandler(server, state, {
signals,
useExit0,
onSignal,
beforeShutdown,
onShutdown,
Expand Down
51 changes: 32 additions & 19 deletions lib/terminus.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,25 +658,38 @@ describe('Terminus', () => {
})

it('accepts custom headers', async () => {

createTerminus(server, {
healthChecks: {
'/health': () => {
return Promise.resolve()
createTerminus(server, {
healthChecks: {
'/health': () => {
return Promise.resolve()
}
},
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS, POST, GET'
}
},
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS, POST, GET",
},
})
server.listen(8000)

const res = await fetch('http://localhost:8000/health')
expect(res.status).to.eql(200)
expect(res.headers.has('Access-Control-Allow-Methods')).to.eql(true)
expect(res.headers.get('Access-Control-Allow-Methods')).to.eql('OPTIONS, POST, GET')
expect(res.headers.has('Access-Control-Allow-Origin')).to.eql(true)
expect(res.headers.get('Access-Control-Allow-Origin')).to.eql('*')
})
server.listen(8000)

const res = await fetch('http://localhost:8000/health')
expect(res.status).to.eql(200)
expect(res.headers.has('Access-Control-Allow-Methods')).to.eql(true)
expect(res.headers.get('Access-Control-Allow-Methods')).to.eql('OPTIONS, POST, GET')
expect(res.headers.has('Access-Control-Allow-Origin')).to.eql(true)
expect(res.headers.get('Access-Control-Allow-Origin')).to.eql('*')

describe('useExit0', () => {
it('allows to exit(0) with useExit0 - true', async () => {
const result = spawnSync('node', ['lib/standalone-tests/terminus.useExit0.js', 'true'])
expect(result.status).to.eql(0)
expect(result.signal).to.eql(null)
})

it('allows to exit(0) with useExit0 - false', async () => {
const result = spawnSync('node', ['lib/standalone-tests/terminus.useExit0.js', 'false'])
expect(result.status).to.eql(null)
expect(result.signal).to.eql('SIGTERM')
})
})
})
})
Loading