健康检查

本指南介绍 AdonisJS 应用中的健康检查。你将学到如何:

  • 理解存活探针(liveness)与就绪探针(readiness)之间的区别
  • 使用内置的设置命令配置健康检查
  • 为监控服务和编排器暴露端点
  • 使用针对磁盘空间、内存、数据库和 Redis 的内置检查
  • 缓存健康检查结果以优化性能
  • 为应用特定需求创建自定义健康检查

概述

健康检查允许你的应用向外部系统(如负载均衡器、容器编排器(Kubernetes、Docker Swarm)和监控服务)报告其运行状态。这些系统会定期探测你的应用,以判断它应接收流量还是应被重启。

AdonisJS 在核心框架中内置了一套健康检查系统,并针对常见的基础设施关注点提供了若干现成的检查。你也可以为应用特定需求创建自定义检查,例如验证外部 API 连通性或队列连接。

存活探针 vs 就绪探针

在实现健康检查之前,理解这两种探针以及各自何时使用非常重要。

**存活探针(liveness probe)**回答的问题是:“进程是否存活且能响应?”这是一个简单的检查,用于验证你的应用能否响应 HTTP 请求。如果存活探针反复失败,编排器将重启该容器。存活探针应保持轻量,避免检查外部依赖,因为数据库故障不应导致你的应用陷入重启循环。

**就绪探针(readiness probe)**回答的问题是:“应用是否准备好接收流量?”此检查验证你的应用及其依赖(数据库连接、Redis、外部服务)是否正常工作。如果就绪探针失败,编排器会将该实例从负载均衡器中移除,但不会重启它。这让应用有时间恢复,例如当数据库连接暂时不可用时。

探针目的失败时是否应检查依赖
存活进程是否存活?重启容器
就绪能否处理请求?从负载均衡器移除

配置健康检查

运行以下命令在你的应用中设置健康检查。这会创建配置文件以及一个同时带有存活和就绪端点的控制器。

node ace configure health_checks

该命令会创建两个文件。第一个是健康检查配置,你在其中注册要运行的检查。

start/health.ts
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
])

第二个是暴露两个探针端点的控制器。

app/controllers/health_checks_controller.ts
import { healthChecks } from '#start/health'
import type { HttpContext } from '@adonisjs/core/http'

export default class HealthChecksController {
  /**
   * Liveness probe: Returns 200 if the process is running.
   * Does not check dependencies.
   */
  async live({ response }: HttpContext) {
    return response.ok()
  }

  /**
   * Readiness probe: Runs all registered health checks
   * and returns the detailed report.
   */
  async ready({ response }: HttpContext) {
    const report = await healthChecks.run()
    if (report.isHealthy) {
      return response.ok(report)
    }

    return response.serviceUnavailable(report)
  }
}

存活方法只是返回一个 200 状态码,证明进程存活且能处理 HTTP 请求。就绪方法运行所有已注册的健康检查,健康时返回 200,任一检查失败时返回 503(服务不可用)。

暴露端点

在你的路由文件中注册健康检查路由。为每个探针使用单独的路径,可让编排器独立配置它们。

start/routes.ts
import router from '@adonisjs/core/services/router'
import { controllers } from '#generated/controllers'

router.get('/health/live', [controllers.HealthChecks, 'live'])
router.get('/health/ready', [controllers.HealthChecks, 'ready'])

有了这些路由,你的监控系统就可以探测 /health/live 进行存活检查,探测 /health/ready 进行就绪检查。

理解就绪报告

就绪端点返回一份详细的 JSON 报告,包含所有已注册检查的结果。

{
  "isHealthy": true,
  "status": "warning",
  "finishedAt": "2024-06-20T07:09:35.275Z",
  "debugInfo": {
    "pid": 16250,
    "ppid": 16051,
    "platform": "darwin",
    "uptime": 16.271809083,
    "version": "v21.7.3"
  },
  "checks": [
    {
      "name": "Disk space check",
      "isCached": false,
      "message": "Disk usage is 76%, which is above the threshold of 75%",
      "status": "warning",
      "finishedAt": "2024-06-20T07:09:35.275Z",
      "meta": {
        "sizeInPercentage": {
          "used": 76,
          "failureThreshold": 80,
          "warningThreshold": 75
        }
      }
    },
    {
      "name": "Memory heap check",
      "isCached": false,
      "message": "Heap usage is under defined thresholds",
      "status": "ok",
      "finishedAt": "2024-06-20T07:09:35.265Z",
      "meta": {
        "memoryInBytes": {
          "used": 41821592,
          "failureThreshold": 314572800,
          "warningThreshold": 262144000
        }
      }
    }
  ]
}

报告包含以下属性:

isHealthy

布尔值,表示是否所有检查都通过。如果一项或多项检查失败,则设为 false

status

总体状态:ok(全部通过)、warning(存在警告)或 error(存在失败)。

finishedAt

检查完成时的时间戳。

debugInfo

进程信息,包括 PID、平台、以秒为单位的运行时间,以及 Node.js 版本。

checks

包含每项已注册检查详细结果的数组。

checks 数组中的每项检查都包含其名称、状态、消息、结果是否来自缓存,以及该检查类型特有的任何元数据。

保护就绪端点

就绪报告包含关于你基础设施的详细信息,你可能不希望将其公开暴露。你可以使用一个密钥头部来保护该端点,你的监控系统会在每个请求中带上它。

Kubernetes 和大多数监控工具都支持在探针请求上使用自定义 HTTP 头部,因此你可以配置它们带上你的密钥。

start/routes.ts
import router from '@adonisjs/core/services/router'

const HealthChecksController = () => import('#controllers/health_checks_controller')

router.get('/health/live', [HealthChecksController, 'live'])
router
  .get('/health/ready', [HealthChecksController, 'ready'])
  .use(({ request, response }, next) => {
    if (request.header('x-monitoring-secret') === 'some_secret_value') {
      return next()
    }
    return response.unauthorized({ message: 'Unauthorized access' })
  })

在 Kubernetes 中,配置探针使其带上该头部。

readinessProbe:
  httpGet:
    path: /health/ready
    port: 3333
    httpHeaders:
      - name: x-monitoring-secret
        value: some_secret_value

可用的健康检查

AdonisJS 提供了若干内置的健康检查,你可以在 start/health.ts 文件中注册它们。

DiskSpaceCheck

监控可用磁盘空间,并在使用率超过配置的阈值时报告警告或错误。

start/health.ts
import { HealthChecks, DiskSpaceCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck()
])

默认警告阈值为 75%,失败阈值为 80%。你可以自定义这些值。

start/health.ts
export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck()
    .warnWhenExceeds(80)
    .failWhenExceeds(90),
])

MemoryHeapCheck

监控由 process.memoryUsage() 报告的堆大小,并在内存消耗超过阈值时告警。

start/health.ts
import { HealthChecks, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new MemoryHeapCheck()
])

默认警告阈值为 250MB,失败阈值为 300MB。你可以使用人类可读的字符串自定义这些值。

start/health.ts
export const healthChecks = new HealthChecks().register([
  new MemoryHeapCheck()
    .warnWhenExceeds('300 mb')
    .failWhenExceeds('700 mb'),
])

MemoryRSSCheck

监控由 process.memoryUsage() 报告的 RSS(常驻集大小,即为进程分配的总内存)。

start/health.ts
import { HealthChecks, MemoryRSSCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new MemoryRSSCheck()
])

默认警告阈值为 320MB,失败阈值为 350MB。

start/health.ts
export const healthChecks = new HealthChecks().register([
  new MemoryRSSCheck()
    .warnWhenExceeds('600 mb')
    .failWhenExceeds('800 mb'),
])

DbCheck

@adonisjs/lucid 包提供,此检查验证与你 SQL 数据库的连通性。

start/health.ts
import db from '@adonisjs/lucid/services/db'
import { DbCheck } from '@adonisjs/lucid/database'
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new DbCheck(db.connection()),
])

一次成功的检查会返回如下报告。

{
  "name": "Database health check (postgres)",
  "isCached": false,
  "message": "Successfully connected to the database server",
  "status": "ok",
  "finishedAt": "2024-06-20T07:18:23.830Z",
  "meta": {
    "connection": {
      "name": "postgres",
      "dialect": "postgres"
    }
  }
}

要监控多个数据库连接,请为每个连接各注册一次该检查。

start/health.ts
export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new DbCheck(db.connection()),
  new DbCheck(db.connection('mysql')),
  new DbCheck(db.connection('pg')),
])

DbConnectionCountCheck

监控活跃的数据库连接数,并在数量超过阈值时告警。此检查仅支持 PostgreSQL 和 MySQL 数据库。

start/health.ts
import db from '@adonisjs/lucid/services/db'
import { DbCheck, DbConnectionCountCheck } from '@adonisjs/lucid/database'
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new DbCheck(db.connection()),
  new DbConnectionCountCheck(db.connection()),
])

默认警告阈值为 10 个连接,失败阈值为 15 个连接。

start/health.ts
new DbConnectionCountCheck(db.connection())
  .warnWhenExceeds(4)
  .failWhenExceeds(10)

RedisCheck

@adonisjs/redis 包提供,此检查验证与你 Redis 服务器(包括集群配置)的连通性。

start/health.ts
import redis from '@adonisjs/redis/services/main'
import { RedisCheck } from '@adonisjs/redis'
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new RedisCheck(redis.connection()),
])

要监控多个 Redis 连接,请为每个连接各注册一次该检查。

start/health.ts
export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new RedisCheck(redis.connection()),
  new RedisCheck(redis.connection('cache')),
  new RedisCheck(redis.connection('locks')),
])

RedisMemoryUsageCheck

监控 Redis 服务器上的内存消耗,并在使用量超过阈值时告警。

start/health.ts
import redis from '@adonisjs/redis/services/main'
import { RedisCheck, RedisMemoryUsageCheck } from '@adonisjs/redis'
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck(),
  new MemoryHeapCheck(),
  new RedisCheck(redis.connection()),
  new RedisMemoryUsageCheck(redis.connection()),
])

默认警告阈值为 100MB,失败阈值为 120MB。

start/health.ts
new RedisMemoryUsageCheck(redis.connection())
  .warnWhenExceeds('200 mb')
  .failWhenExceeds('240 mb')

缓存结果

每次调用就绪端点时都会运行健康检查。对于不需要每次请求都运行的检查(如变化缓慢的磁盘空间),你可以将结果缓存指定的时长。

start/health.ts
import {
  HealthChecks,
  MemoryRSSCheck,
  DiskSpaceCheck,
  MemoryHeapCheck,
} from '@adonisjs/core/health'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck().cacheFor('1 hour'),
  new MemoryHeapCheck(),
  new MemoryRSSCheck(),
])

在本示例中,磁盘空间检查每小时最多运行一次,对后续请求返回缓存的结果。由于内存使用可能快速变化,内存检查在每个请求上都会运行。

当某个结果来自缓存时,该检查报告中的 isCached 属性将为 true

创建自定义健康检查

你可以创建自定义健康检查,以验证应用特定需求,如外部 API 连通性、消息队列连接,或业务关键服务的可用性。

自定义健康检查是一个继承 BaseCheck 并实现 run 方法的类。你可以将自定义检查放在项目的任何位置,不过 app/health_checks/ 是一个合理的位置。

app/health_checks/payment_gateway_check.ts
import { Result, BaseCheck } from '@adonisjs/core/health'
import type { HealthCheckResult } from '@adonisjs/core/types/health'

export class PaymentGatewayCheck extends BaseCheck {
  async run(): Promise<HealthCheckResult> {
    /**
     * Attempt to reach the payment gateway's health endpoint.
     * In a real implementation, you would make an HTTP request
     * to verify the service is reachable.
     */
    const isReachable = await this.checkGatewayConnectivity()

    if (!isReachable) {
      return Result.failed('Payment gateway is unreachable')
    }

    const latency = await this.measureLatency()

    if (latency > 2000) {
      return Result.warning('Payment gateway latency is high').mergeMetaData({
        latencyMs: latency,
      })
    }

    return Result.ok('Payment gateway is healthy').mergeMetaData({
      latencyMs: latency,
    })
  }

  protected async checkGatewayConnectivity(): Promise<boolean> {
    // Implementation here
    return true
  }

  protected async measureLatency(): Promise<number> {
    // Implementation here
    return 150
  }
}

Result 类提供了三个用于创建健康检查结果的工厂方法:

方法用途
Result.ok(message)检查成功通过
Result.warning(message)检查通过但存在隐患
Result.failed(message, error?)检查失败,可选地包含错误

使用 mergeMetaData 方法在检查报告中包含额外信息,如延迟测量值、连接数或版本号。

注册自定义健康检查

start/health.ts 中导入你的自定义检查,并与其他检查一起注册。

start/health.ts
import { HealthChecks, DiskSpaceCheck, MemoryHeapCheck } from '@adonisjs/core/health'
import { PaymentGatewayCheck } from '#health_checks/payment_gateway_check'

export const healthChecks = new HealthChecks().register([
  new DiskSpaceCheck().cacheFor('1 hour'),
  new MemoryHeapCheck(),
  new PaymentGatewayCheck(),
])

现在,每当调用就绪端点时,你的自定义检查都会与内置检查一起运行。