1.1.0 • Published 3 years ago

@yocdev/lifecycle v1.1.0

Weekly downloads
2
License
MIT
Repository
github
Last release
3 years ago

lifecycle

Readiness, liveness, gracefully shutdown helpers for server app. Especially suitable for Kubernetes apps.

Node.js Package

Installation

$ npm install @yocdev/lifecycle

Usage

// --- Initialization
Lifecycle = require('@yocdev/lifecycle')
// Set logger to output debug messages.
// logger should have shape: { info, warn, error }.
Lifecycle.setLogger(console)
// When Lifecycle dependencies are ready, start the App.
Lifecycle.onReady(startApp)

// ...


// --- Create dependencies
// Lifecycle uses dependencies to manage App startup and shutdown processes:
// Lifecycle will become ready when all dependencies are ready and
// will shutdown all dependencies when App is going to terminate.

// Create two dependencies.
const [dbDependency, cacheDependency] = Lifecycle.createDependencies(['db', 'cache'])
// Configurate these dependencies.
onDbReady(() => dbDependency.setReady())
dbDependency.onShutdown(() => closeDbConnections())
onCacheReady(() => cacheDependency.setReady())
cacheDependency.onShutdown(() => closeCacheConnections())

// If your App has no dependencies, use `Lifecycle.markNoDependencies()`:
// This is necessary to let Lifecycle become ready immediately.
Lifecycle.markNoDependencies()

// ...


// --- Configurate shutdown and attach

function startApp() {
  // Start HTTP(s) server whatever...
  const server = http.createServer(/* ... */)
  // Express or Koa app.
  // const server = app.listen(...)

  /**
   * Shutdown configuration.
   *
   * @param {Object} config - Shutdown configuration.
   * @param {string[]} [signals=['SIGTERM']] - Array of signals to listen for relative to shutdown.
   * @param {number} [config.delay] - Number of milliseconds before beginning the shutdown process.
   *   Needed for HTTP server when traffics are not stopped immediately after process receiving
   *   terminating signal. (e.g. in K8s environment)
   * @param {(http.Server|https.Server)} [config.server] - http.Server or https.Server instance.
   * @param {number} [config.serverGracefullyShutdownTimeout] - Number of milliseconds before
   *   forcibly closing the HTTP server. Will be ignored if `config.server` is not specified.
   * @param {function} [config.onMainComponentShutdown] - Execute right after the `config.delay`
   *   milliseconds if no `config.server` specified. Only needed if your App is not a HTTP server
   *   and has its own (gracefully) shutdown logic. The shutdown logic goes here.
   * @param {number} [config.dependenciesShutdownTimeout] - Number of milliseconds before forcibly
   *   shutting down dependencies. Note: Dependencies shutdown in parallel.
   */
  Lifecycle.configurateShutdown({
    // In K8s environment and such, it's important to delay shutting down the App,
    // since the traffics are not stopped immediately even when the Pod received
    // terminating signals.
    delay: 5000,
    server,
    serverGracefullyShutdownTimeout: 30000,
    dependenciesShutdownTimeout: 5000,
  })

  // If your App is not a HTTP(s) server and has it's own (gracefully) shutdown process,
  // use `config.onMainComponentShutdown`
  Lifecycle.configurateShutdown({
    onMainComponentShutdown: () => shutdownAppGracefully(),
  })

  // Attach to listen termination signals (e.g. SIGTERM)
  Lifecycle.attach()
}

// ...


// --- Readiness, liveness routes (optional)
app.get('/readiness', (req, res) => {
  res.sendStatus(Lifecycle.isReady() ? 200 : 503)
})
app.get('/liveness', (req, res) => {
  res.sendStatus(Lifecycle.isAlive() ? 200 : 503)
})

Kubernetes readiness, liveness config example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      # > (delay + serverGracefullyShutdownTimeout + dependenciesShutdownTimeout) ms
      terminationGracePeriodSeconds: 50
      containers:
      - name: web
        image: YOURAPPIMAGE
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 1000m
            memory: 1Gi
        readinessProbe:
          httpGet:
            path: /readiness
            port: 80
          # App startup time
          initialDelaySeconds: 10
        livenessProbe:
          httpGet:
            path: /liveness
            port: 80
          # App startup time
          initialDelaySeconds: 10

API

Lifecycle
  - setLogger(logger)
  - createDependencies(dependencyNames)
  - markNoDependencies()
  - getDependency(dependencyName)
  - isReady()
  - onReady(callback)
  - isAlive()
  - setAlive()
  - setDead()
  - isShuttingDown()
  - configurateShutdown(config)
  - attach

Dependency
  - name
  - setReady()
  - onShutdown(callback)

Best practices for gracefully shutdown in Kubernetes

  1. Delay shutting down the Pod.
  2. Leave readiness probe alone. Not necessarily need a failing on readiness probe.

References:

Alternates

terminus

https://github.com/godaddy/terminus

@4.4.1

Cons:

  • Gracefully shutdown feature is base on stoppable whose last commit is on Oct 4, 2019.
  • Only works with HTTP(s) server type App.
  • Failing the readiness probe when shutting down (not necessarily). (Issue: https://github.com/godaddy/terminus/issues/112)

lightship

https://github.com/gajus/lightship

@6.2.1

Cons:

  • Only works with HTTP(s) server type App.
  • Gracefully shutdown may not work as expected. lightship creates a separated health server and the gracefully shutdown is applied on this server not the business one. (Issue: https://github.com/gajus/lightship/issues/27)