Terminus Health checks

导读

Terminus 集成为您提供就绪性/活跃性健康检查。健康检查对于复杂的后端设置至关重要。简而言之,Web 开发领域的健康检查通常由一个特殊地址组成,例如https://my-website.com/health/readiness。 您的基础设施的服务或组件(例如 Kubernetes)会持续检查此地址。根据对此地址的 GET 请求返回的 HTTP 状态代码,服务将在收到不健康响应时采取行动。 由于健康不健康的定义因您提供的服务类型而异,因此 Terminus 集成为您提供了一组 健康指标

例如,如果您的 Web 服务器使用 MongoDB 存储其数据,那么 MongoDB 是否仍在运行将是至关重要的信息。 在这种情况下,您可以使用 MongooseHealthIndicator。如果配置正确(稍后会详细介绍),您的健康检查地址将返回 健康或不健康的 HTTP 状态代码,具体取决于 MongoDB 是否正在运行。

入门

要开始使用 @nestjs/terminus,我们需要安装所需的依赖项。

bash
$ npm install --save @nestjs/terminus

设置健康检查

健康检查代表健康指标的摘要。健康指标会检查服务是否处于健康或不健康状态。如果所有分配的健康指标都正常运行,则健康检查结果为阳性。由于许多应用程序都需要类似的健康指标,@nestjs/terminus 提供了一组预定义指标,例如:

  • HttpHealthIndicator
  • TypeOrmHealthIndicator
  • MongooseHealthIndicator
  • SequelizeHealthIndicator
  • MikroOrmHealthIndicator
  • PrismaHealthIndicator
  • MicroserviceHealthIndicator
  • GRPCHealthIndicator
  • MemoryHealthIndicator
  • DiskHealthIndicator

要开始进行第一次健康检查,让我们创建 HealthModule 并在其导入数组中导入 TerminusModule

提示

要使用 Nest CLI 创建模块,只需执行 $ nest g module health 命令。

health.module
ts
import { Module } from '@nestjs/common'
import { TerminusModule } from '@nestjs/terminus'

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

我们的健康检查可以通过 控制器 执行,并且可以通过 Nest CLI 轻松设置。

bash
$ nest g controller health
Info

强烈建议在您的应用程序中启用关闭钩子。如果启用,Terminus 集成将使用此生命周期事件。阅读有关关闭钩子的更多信息此处

HTTP 健康检查

一旦我们安装了 @nestjs/terminus、导入了我们的 TerminusModule 并创建了一个新的控制器,我们就可以创建健康检查了。

HTTPHealthIndicator 需要 @nestjs/axios 包,因此请确保已安装它:

bash
$ npm i --save @nestjs/axios axios

Now we can setup our HealthController:

ts
health.controller
ts
import { Controller, Get } from '@nestjs/common'
import { HealthCheck, HealthCheckService, HttpHealthIndicator } from '@nestjs/terminus'

@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'),
    ])
  }
}
ts
health.module
ts
import { Module } from '@nestjs/common'
import { TerminusModule } from '@nestjs/terminus'
import { HttpModule } from '@nestjs/axios'
import { HealthController } from './health.controller'

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

我们的健康检查现在将向 https://docs.nestjs.com 地址发送 GET 请求。如果我们从该地址获得健康响应,则 http://localhost:3000/health 处的路由将返回带有 200 状态代码的以下对象。

json
{
  "status": "ok",
  "info": {
    "nestjs-docs": {
      "status": "up"
    }
  },
  "error": {},
  "details": {
    "nestjs-docs": {
      "status": "up"
    }
  }
}

可以使用 HealthCheckResult 接口从 @nestjs/terminus 包访问此响应对象的接口。

status如果任何健康指标失败,状态将为 'error'。如果 NestJS 应用程序正在关闭但仍在接受 HTTP 请求,则健康检查将具有 'shutting_down' 状态。'error' | 'ok' | 'shutting_down'
info包含状态为 'up' 的每个健康指标信息的对象,换句话说是健康object
error包含状态为 'down' 的每个健康指标信息的对象,换句话说是不健康object
details包含每个健康指标的所有信息的对象object

检查特定的 HTTP 响应代码

在某些情况下,您可能需要检查特定条件并验证响应。例如,假设https://my-external-service.com 返回响应代码204。使用HttpHealthIndicator.responseCheck,您可以 专门检查该响应代码,并将所有其他代码确定为不健康。

如果返回除204以外的任何其他响应代码,则以下示例将不健康。第三个参数 要求您提供一个函数(同步或异步),该函数返回一个布尔值,表示响应是被视为 健康(true)还是不健康(false)。

health.controller
ts
// Within the `HealthController`-class

@Get()
@HealthCheck()
check() {
  return this.health.check([
    () =>
      this.http.responseCheck(
        'my-external-service',
        'https://my-external-service.com',
        (res) => res.status === 204,
      ),
  ]);
}

TypeOrm 健康指示器

Terminus 提供了将数据库检查添加到健康检查的功能。为了开始使用此健康指示器,您 应该查看 数据库章节 并确保应用程序内的数据库连接已建立。

提示

在后台,TypeOrmHealthIndicator 仅执行 SELECT 1-SQL 命令,该命令通常用于验证数据库是否仍处于活动状态。如果您使用的是 Oracle 数据库,则它使用 SELECT 1 FROM DUAL

ts
health.controller
ts
@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('database'),
    ])
  }
}

如果您的数据库可以访问,那么在使用 GET 请求访问 http://localhost:3000/health 时您应该看到以下 JSON 结果:

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

如果您的应用使用 多个数据库,您需要将每个连接注入到您的 HealthController。然后,您只需将连接引用传递给 TypeOrmHealthIndicator

health.controller
ts
@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: TypeOrmHealthIndicator,
    @InjectConnection('albumsConnection')
    private albumsConnection: Connection,
    @InjectConnection()
    private defaultConnection: Connection,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.db.pingCheck('albums-database', { connection: this.albumsConnection }),
      () => this.db.pingCheck('database', { connection: this.defaultConnection }),
    ])
  }
}

磁盘健康指示器

使用 DiskHealthIndicator,我们可以检查正在使用的存储空间。首先,请确保将 DiskHealthIndicator 注入到您的 HealthController 中。以下示例检查路径 / 的存储使用情况(在 Windows 上,您可以使用 C:\\)。如果超过总存储空间的 50% 以上,它将响应不健康的健康检查。

ts
health.controller
ts
@Controller('health')
export class HealthController {
  constructor(
    private readonly health: HealthCheckService,
    private readonly disk: DiskHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.disk.checkStorage('storage', { path: '/', thresholdPercent: 0.5 }),
    ])
  }
}

使用DiskHealthIndicator.checkStorage函数,您还可以检查固定的空间量。 如果路径/my-app/超过 250GB,则以下示例将不健康。

health.controller
ts
// Within the `HealthController`-class

@Get()
@HealthCheck()
check() {
  return this.health.check([
    () => this.disk.checkStorage('storage', {  path: '/', threshold: 250 * 1024 * 1024 * 1024, })
  ]);
}

内存健康指示器

要确保您的进程不超过一定的内存限制,可以使用 MemoryHealthIndicator。 以下示例可用于检查进程的堆。

提示

堆是动态分配内存所在的内存部分(即通过 malloc 分配的内存)。从堆分配的内存将保持分配状态,直到发生以下情况之一:

  • 内存被释放
  • 程序终止
ts
health.controller
ts
@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private memory: MemoryHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
    ])
  }
}

还可以使用 MemoryHealthIndicator.checkRSS 验证进程的内存 RSS。此示例 如果您的进程分配了超过 150MB 的内存,则将返回不健康的响应代码。

提示

RSS 是驻留集大小,用于显示分配给该进程的内存量以及 RAM 中的内存量。

它不包括换出的内存。它包括来自共享库的内存,只要这些库中的页面确实在内存中。它包括所有堆栈和堆内存。

health.controller
ts
// Within the `HealthController`-class

@Get()
@HealthCheck()
check() {
  return this.health.check([
    () => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
  ]);
}

自定义健康指标

在某些情况下,@nestjs/terminus 提供的预定义健康指标不能满足您的所有健康检查要求。在这种情况下,您可以根据需要设置自定义健康指标。

让我们开始创建一个代表我们自定义指标的服务。为了对指标的结构有一个基本的了解,我们将创建一个示例 DogHealthIndicator。如果每个 Dog 对象都具有 'goodboy'' 类型,则此服务应具有 'up'' 状态。如果不满足该条件,则应该抛出错误。

ts
dog.health
ts
import { Injectable } from '@nestjs/common'
import { HealthCheckError, HealthIndicator, HealthIndicatorResult } from '@nestjs/terminus'

export interface Dog {
  name: string
  type: string
}

@Injectable()
export class DogHealthIndicator extends HealthIndicator {
  private dogs: Dog[] = [
    { name: 'Fido', type: 'goodboy' },
    { name: 'Rex', type: 'badboy' },
  ]

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    const badboys = this.dogs.filter(dog => dog.type === 'badboy')
    const isHealthy = badboys.length === 0
    const result = this.getStatus(key, isHealthy, { badboys: badboys.length })

    if (isHealthy) {
      return result
    }
    throw new HealthCheckError('Dogcheck failed', result)
  }
}

接下来我们需要做的是将健康指标注册为提供者。

health.module
ts
import { Module } from '@nestjs/common'
import { TerminusModule } from '@nestjs/terminus'
import { DogHealthIndicator } from './dog.health'

@Module({
  controllers: [HealthController],
  imports: [TerminusModule],
  providers: [DogHealthIndicator]
})
export class HealthModule { }
提示

在实际应用中,应在单独的模块(例如DogModule)中提供DogHealthIndicator,然后由HealthModule导入。

最后一步是将现在可用的健康指标添加到所需的健康检查端点中。为此,我们返回HealthController并将其添加到check函数中。

ts
health.controller
ts
import { HealthCheck, HealthCheckService } from '@nestjs/terminus'
import { Dependencies, Get, Injectable } from '@nestjs/common'
import { DogHealthIndicator } from './dog.health'

@Injectable()
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private dogHealthIndicator: DogHealthIndicator
  ) {}

  @Get()
  @HealthCheck()
  healthCheck() {
    return this.health.check([
      () => this.dogHealthIndicator.isHealthy('dog'),
    ])
  }
}

日志记录

Terminus 仅记录错误消息,例如当 Healthcheck 失败时。使用 TerminusModule.forRoot() 方法,您可以更好地控制如何记录错误 以及完全接管日志记录本身。

在本节中,我们将引导您了解如何创建自定义记录器 TerminusLogger。此记录器扩展了内置记录器。 因此,您可以选择要覆盖记录器的哪一部分

信息

如果您想了解有关 NestJS 中自定义记录器的更多信息,在此处阅读更多信息

terminus-logger.service
ts
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common'

@Injectable({ scope: Scope.TRANSIENT })
export class TerminusLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string): void
  error(message: any, ...optionalParams: any[]): void
  error(
    message: unknown,
    stack?: unknown,
    context?: unknown,
    ...rest: unknown[]
  ): void {
    // Overwrite here how error messages should be logged
  }
}

一旦创建了自定义记录器,您需要做的就是将其传递到Terminus Module.forRoot()中。

health.module
ts
@Module({
  imports: [
    TerminusModule.forRoot({
      logger: TerminusLogger,
    }),
  ],
})
export class HealthModule {}

要完全抑制来自 Terminus 的任何日志消息(包括错误消息),请按此方式配置 Terminus。

health.module
ts
@Module({
  imports: [
    TerminusModule.forRoot({
      logger: false,
    }),
  ],
})
export class HealthModule {}

Terminus 允许您配置如何在日志中显示健康检查错误。

错误日志样式说明示例
json (默认)如果出现错误,则将健康检查结果摘要打印为 JSON 对象img
pretty如果出现错误,则在格式化的框内打印健康检查结果摘要,并突出显示成功/错误结果img

您可以使用 errorLogStyle 配置选项更改日志样式,如以下代码片段所示。

health.module
ts
@Module({
  imports: [
    TerminusModule.forRoot({
      errorLogStyle: 'pretty',
    }),
  ]
})
export class HealthModule {}

正常关闭超时

如果您的应用程序需要推迟其关闭过程,Terminus 可以为您处理。 此设置在使用 Kubernetes 等编排器时特别有用。 通过设置比就绪检查间隔稍长的延迟,您可以在关闭容器时实现零停机时间。

health.module
ts
@Module({
  imports: [
    TerminusModule.forRoot({
      gracefulShutdownTimeoutMs: 1000,
    }),
  ]
})
export class HealthModule {}

更多示例

更多工作示例可在此处找到(https://github.com/nestjs/terminus/tree/master/sample)。