@numbereight/lifecyclemanager
TypeScript icon, indicating that this package has built-in type declarations

1.0.8 • Public • Published

Lifecycle Manager

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'.

Integrating

into a server

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

into a service

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');
        }
    }
}

API

new LifecycleManager({ logger: Logger, exitOnTerminate: boolean }) => LifecycleManager

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.

#ready: boolean

True if the system is ready.

#draining: boolean

True if the system is draining.

#terminating: boolean

True if the system is terminating.

#onReady(handler: (() => Promise<void>) | (() => void)) => void

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().

#onDrain(handler: (() => Promise<void>) | (() => void)) => void

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.

#onTerminate(handler: (() => Promise<void>) | (() => void)) => void

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.

#setReady() => Promise<void>

Call this to mark the system as ready. Should only be called at the highest level of the program.

#getEndpoints() => Router

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.

#terminate(code: number = 0) => Promise<void | never>

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.

Package Sidebar

Install

npm i @numbereight/lifecyclemanager

Weekly Downloads

3

Version

1.0.8

License

MIT

Unpacked Size

23.4 kB

Total Files

8

Last publish

Collaborators

  • nechris