用 NestJS Terminus 构建健康检查



用 NestJS Terminus 构建健康检查

用 NestJS Terminus 构建健康检查

来源:NestJS 官方文档《Health checks》。核验时页面指向 NestJS v11;文档页未给出 @nestjs/terminus 的精确补丁版本。项目及文档采用 MIT 许可。本文保留来源作者与代码归属。

Terminus 将多个依赖探测聚合到健康检查端点,供负载均衡器或编排器决定是否继续向应用送流量。先定义端点代表的状态,再选择能反映该状态的探测项。readiness 与 liveness 常需要不同检查;外部依赖短暂故障不一定意味着应用进程应被重启。

多个健康检查 indicator 经 HealthCheckService 汇总为可接受流量、degraded、error 或 shutting_down 状态
原创状态流示意图,依据 NestJS Terminus 文档概念独立绘制。

安装并建立端点

HTTP indicator 需要 @nestjs/axios 与 axios。以下是原文的安装命令;本次未执行 npm 或项目构建。

npm i --save @nestjs/terminus @nestjs/axios axios

模块需要导入 TerminusModule;HTTP 探测还需 HttpModule:

import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [TerminusModule, HttpModule],
})
export class HealthModule {}

控制器通过 @HealthCheck() 标注探针端点,并把检查回调交给 HealthCheckService:

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private http: HttpHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
    ]);
  }
}

如果要验证特定 HTTP 状态码,可使用 responseCheck,例如把 204 当作成功条件。自定义请求地址应来自受控配置,不应由不可信请求参数决定。

() => this.http.responseCheck(
  'external-service',
  'https://service.example/health',
  (response) => response.status === 204,
)

数据库、磁盘与内存探测

TypeOrmHealthIndicator 会执行轻量查询来确认数据库连接仍可用:常见数据库为 SELECT 1;Oracle 使用 SELECT 1 FROM DUAL,SAP HANA 使用 SELECT now() FROM dummy。原文示例设置 1000 毫秒超时:

() => this.db.pingCheck('database').withTimeout(1000)

多数据源应用可分别注入 DataSource,再在 pingCheck 的 connection 选项中指定目标连接。

DiskHealthIndicator 可按挂载点与使用率百分比检查,例如根目录使用超过 50% 即为 unhealthy;也可用绝对阈值检查空间是否超过 250 GiB。容器中的 /、Windows 的 C:\,以及实际卷挂载路径可能不同,阈值应按工作负载和容器限制选择。

() => this.disk.checkStorage('storage', {
  path: '/',
  thresholdPercent: 0.5,
})

MemoryHealthIndicator 提供 checkHeap() 和 checkRSS()。原文示例对两者都使用 150 MiB 阈值。heapUsed 只衡量 V8 堆中正在使用的内存;RSS 是进程驻留在 RAM 的总量,还包括栈、堆和驻留的共享库。它们都不能代替容器 memory limit 的判断。

() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024)
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024)

数据库示例的来源输出为 status: ok,database.status: up,responseTime: 12。它是文档样例,不是本文运行得到的结果。

{
  "status": "ok",
  "info": {"database": {"status": "up", "responseTime": 12}},
  "error": {},
  "details": {"database": {"status": "up", "responseTime": 12}}
}

自定义指标:attempt、up、down 与 degraded

预置指标不覆盖所有场景时,可以用 HealthIndicatorService.check(key) 建立自定义指标。attempt() 会把抛出的操作错误转成 down;应把 withTimeout 提供的 AbortSignal 传给底层 I/O,让超时后请求真正取消。

isHealthy(key: string) {
  return this.healthIndicatorService
    .check(key)
    .attempt(async ({ signal }) => {
      const response = await fetch(
        'https://dog.ceo/api/breeds/list',
        { signal },
      );
      if (!response.ok) {
        throw new Error('Dog API returned a non-success status');
      }
      const { message: breeds } = await response.json();
      return { breeds: breeds.length };
    })
    .withTimeout(1000);
}

上面增加了 response.ok 检查;这是本文对来源示例的编辑补强,不是来源文档中的原始代码。

若成功条件需要显式判断,可以返回 up() 或 down(),并附加状态信息。可降级但仍可处理请求的依赖(例如缓存)可返回 degraded():整体状态为 degraded,HTTP 仍为 200,流量继续通过。如果任何指标为 down,总体状态会变为 error。由 up/down/degraded 建立的自定义指标若抛出未处理异常,会中断整项检查并产生 500;可能失败的 I/O 应用 attempt() 包裹。

文档示例给出的状态包括:

{
  "status": "degraded",
  "info": {"cache": {"status": "degraded",
    "message": "cache unreachable, serving without cache"}},
  "error": {},
  "details": {"cache": {"status": "degraded",
    "message": "cache unreachable, serving without cache"}}
}

当 Dog API 检查超过 1000 毫秒时,来源示例把 dog 标成 down,并将总体 status 设为 error;日志会显示 “timeout of 1000ms exceeded”。上述都是文档示例输出,不代表当前服务状态。

超时、缓存与日志

对 attempt() 返回值链式调用 withTimeout(1500) 可限制等待时长。到时后指标为 down,并会触发 AbortSignal;若底层请求没有使用 signal,它仍可能在后台继续运行。

cacheFor(5000) 会在 5 秒内复用同一 indicator key 的检查结果;并发检查共享一次 in-flight 执行。缓存跨请求共享,响应会带 cachedResponse: true。只对适合短期复用的探测使用缓存。

() => this.db.pingCheck('database')
  .withTimeout(1500)
  .cacheFor(5000)

Terminus 默认在检查失败时记录错误。可通过 TerminusModule.forRoot() 注入自定义 logger、设为 false 关闭全部日志,或设置 errorLogStyle 为 json(默认)或 pretty。下面两张原文图展示同一磁盘阈值失败的两种日志格式。

NestJS Terminus JSON 错误日志示例:磁盘使用量超过设定阈值
原文截图:JSON 格式的磁盘检查失败日志。来源:NestJS 文档。
NestJS Terminus pretty 错误日志示例:以框线突出显示磁盘状态 down 及超阈值消息
原文截图:pretty 格式的磁盘检查失败日志。来源:NestJS 文档。

SIGTERM 优雅关闭

gracefulShutdownTimeoutMs 可在 SIGTERM 后延迟退出。Terminus 可立即将健康状态切换为 shutting_down 并返回 503,让编排器停止分发新流量;等待窗口则给在途请求留出完成时间。该延迟只应用于 SIGTERM;SIGINT 等其他信号仍会立即关闭。必须启用 NestJS shutdown hooks,否则 Terminus 收不到信号。延迟长度还需结合 readiness 探针周期、请求最长耗时和编排器宽限期设置,不能仅凭配置宣称零停机。

TerminusModule.forRoot({
  gracefulShutdownTimeoutMs: 1000,
})

部署边界与核验范围

健康端点可能暴露依赖名称、内部路径、错误消息和进程指标。将其限制在可信网络或受控探针访问范围;考虑分开 readiness 与 liveness,避免外部服务抖动引发重启风暴。将回调目标限制在固定可信地址,并设置有界超时。

本文静态核对了来源页面的 HTTP、TypeORM、多数据源、磁盘、heap/RSS、自定义状态、超时、缓存、日志和 SIGTERM 示例。未运行 npm install、NestJS 项目、网络请求、数据库检查或 Kubernetes 探针;文档输出没有被当作本次运行结果。

来源与许可:NestJS 官方文档《Health checks》,页面指向 v11;项目和文档页面标注 MIT 许可。原文截图保留来源归属,代码中的 response.ok 检查为本文编辑补充。


© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容