Lifecycle Manager is an injectable service provider that allows:
- code services to interact with lifecycle events.
- servers to support external lifecycle endpoints (e.g. Kubernetes lifecycle hooks).
The lifecycle is straightforwards:
- pre-ready
- The system is initialising, and is not currently ready
- ready
- The system has finished initialising, and is now ready
- draining
- The system is stopping accepting new requests of any form, and is awaiting the requests it is already handling to complete. It may 'pre-write' state to simplify the final terminating step.
- terminating
- The system is stopping all external connections and saving all final state so that it can terminate.
There is currently no support for a system marking itself as unhealthy by becoming 'unready' or 'unlive'.
This integration provides probes and hooks suitable for kubernetes via the getEndpoints
function, and drains the express server when termination starts.
import { LifecycleManager } from '@numbereight/lifecyclemanager';
import { RouteService } from './services/routes';
import express from 'express';
import { promisify } from 'util';
export function startServer(options: { routeService: RouteService, lifecycleManager: LifecycleManager }) {
const app = express();
// the core application that does "something"
app.use(options.routeService.routes());
// Provide external routes for systems like kubernetes
app.use(options.lifecycleManager.getEndpoints());
const server = app.listen();
const drainServer = promisify((cb: (e: Error | undefined) => void) =>
server.close(cb)
);
options.lifecycleManager.onDrain(() => drainServer());
// consider the server started
options.lifecycleManager.setReady();
}
This is example yaml for the hooks, suitable for a gitlab .auto-deploy-values
:
lifecycle:
preStop:
httpGet:
port: 5000
path: "/terminate"
readinessProbe:
probeType: "httpGet"
scheme: "HTTP"
path: "/ready"
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
probeType: "httpGet"
scheme: "HTTP"
path: "/live"
initialDelaySeconds: 15
timeoutSeconds: 5
This shows how a service using a database may support the termination hook.
import { LifecycleManager } from '@numbereight/lifecyclemanager';
import { Database, getDatabase } from '@numbereight/udatabase';
export function startService(options: { dburl: string, lifecycleManager: LifecycleManager }) {
const db = await getDatabase(options.dburl);
lifecycleManager.onTerminate(() => db.disconnect());
return {
doServiceThing: () => {
return db.run<{ meaning: number }>('postgres', 'thing', 'SELECT 42');
}
}
}
Create a lifecyle manager.
-
logger
the logger the manager should use. -
exitOnTerminate
whether to actually exit when called with terminate, or just to drain. This is useful for running in tests, where the running code just needs stopped and exiting is not desirable.
True if the system is ready.
True if the system is draining.
True if the system is terminating.
Register a handler to be called when the lifecycle manager enters the ready state.
This is generally not useful to register, unless you are implementing an alternative to getEndpoints()
.
Register a handler to be called when the lifecycle manager enters the drain state.
The lifecycle manager will not exit the drain state until all onDrain handlers have completed.
This is the most important handler to register. Use this to stop making onwards requests, and track the completion of ongoing requests.
Register a handler to be called when the lifecycle manager enters the terminating state.
The lifecycle manager will not terminate until all onTerminate handlers have completed.
This is the second most important handler to register. Use this to dispose of handlers to external services like databases.
Call this to mark the system as ready. Should only be called at the highest level of the program.
Returns an express router that provides the following routes:
-
/live
- suitable for k8s liveness probe. -
/ready
- suitable for k8s readiness probe. -
/terminate
- suitable for k8s pre-stop hook. -
/state
- returns JSON, similar to/observe
endpoints elsewhere, for custom management.
Causes all registered drainHandlers to be called. Once they have completed it exits with the provided code.
For tests, to prevent an actual process exit, configure exitOnTerminate
to be false
in the constructor.