shutdown-check 的配置是一个名为 shutdown-check.json 的 JSON 文件。它告诉 CLI 如何启动你的服务、如何等待服务就绪、如何发起进行中的工作、如何发送 SIGTERM,以及如何判断这次关闭是否通过。
生成初始配置文件
运行:
npx shutdown-check init这个命令会写入下面这份完整配置。如果文件已存在,它会拒绝覆盖:
{
"command": ["node", "server.js"],
"cwd": ".",
"baseUrl": "http://127.0.0.1:3000",
"readiness": { "path": "/health", "status": 200, "timeoutMs": 10000 },
"workload": {
"path": "/slow",
"method": "GET",
"status": 200,
"started": { "type": "response-headers", "timeoutMs": 5000 }
},
"shutdown": { "deadlineMs": 10000, "exitCode": 0 }
}第一次运行前,先改好启动命令、端口、就绪检查路径和测试请求路径。快速开始里有一个和这份配置配套的服务端示例。
配置规则
- CLI 只接受 JSON,不允许注释和末尾逗号。
- 所有数值都必须是整数,并且在文档规定的范围内。
- 校验遇到第一个无效值就会停止。
- 配置无效时,CLI 会打印
shutdown-check: <message>,并以退出码2退出。 - 未知字段会被忽略。所以可选字段拼错时不会报错,而是直接回退到默认值。
- 路由路径只能是
baseUrl下的路径;完整 URL 和//host/path形式都会被拒绝。
全部字段
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| command* | string[] | — | 服务的启动命令,写成参数数组,而不是 shell 字符串:["node", "dist/server.js"]。命令会直接启动,并运行在独立的进程组中。 |
| cwd | string | 配置文件所在目录 | command 的工作目录,相对于配置文件解析(在 API 中相对于 baseDirectory)。 |
| env | Record<string, string> | {} | 传给启动进程的环境变量,会加到检查自身的环境变量中,或覆盖其中的同名变量。 |
| baseUrl* | string | — | 服务的源地址(origin)。必须是 localhost、127.0.0.1 或 [::1] 上的纯 HTTP 地址,不能带路径、查询参数或凭据。使用专门留给测试的端口。 |
| readiness* | object | — | 表明服务已可以接收流量的路由。检查会持续轮询这个路由,直到返回 status。 |
| readiness.path* | string | — | 就绪检查路由的本地路径,例如 /health。 |
| readiness.status | number | 200 | 表示服务已就绪的状态码,范围 100–599。 |
| readiness.timeoutMs | number | 10000 | 等待服务就绪的最长时间,范围 100–300000 ms。 |
| readiness.intervalMs | number | 100 | 两次就绪轮询之间的间隔,范围 10–10000 ms。 |
| workload* | object | — | SIGTERM 到达时仍在进行中的慢请求,以及它的响应必须满足的条件。 |
| workload.path* | string | — | 端点的本地路径。该端点的处理时间要足够长,确保发送信号时它仍在运行。 |
| workload.method | string | "GET" | HTTP 方法(大写)。 |
| workload.headers | Record<string, string> | {} | 请求头,值为字符串。 |
| workload.body | string | — | 请求体,以字符串形式提供。 |
| workload.status | number | 200 | 每个进行中的请求都必须以这个状态码结束。其他状态码会报 SC202。 |
| workload.bodyIncludes | string | — | 每个进行中请求的响应体都必须包含的文本,用来证明工作确实完成了。缺少这段文本会报 SC203。 |
| workload.concurrent | number | 1 | 同时处于进行中的测试请求数量,范围 1–20。大于 1 时需要使用 response-headers 启动确认。 |
| workload.started* | object | — | 发送 SIGTERM 之前,检查靠什么确认工作已经真正开始,即启动确认(start barrier)。参见启动确认。 |
| workload.started.type* | "response-headers" | "probe" | — | "response-headers":响应头已到达、响应体仍未结束时,视为工作已开始。"probe":由单独的路由报告工作是否已开始。 |
| workload.started.timeoutMs | number | 5000 | 等待工作开始的最长时间,范围 100–300000 ms。未按时开始会报 SC111。 |
| workload.started.path | string | — | 用于 type: "probe"(此时必填):报告是否有工作正在运行的路由。 |
| workload.started.inactiveStatus | number | 204 | 用于 type: "probe":没有工作在运行时,探测路由返回的状态码。 |
| workload.started.activeStatus | number | 200 | 用于 type: "probe":有工作在运行时,探测路由返回的状态码。必须与 inactiveStatus 不同。 |
| workload.started.intervalMs | number | 50 | 用于 type: "probe":两次探测轮询之间的间隔,范围 10–10000 ms。 |
| shutdown | object | — | 发送 SIGTERM 后,一次合格的优雅关闭必须满足的条件。 |
| shutdown.signal | "SIGTERM" | "SIGTERM" | 要发送的信号。1.0 版本只支持 SIGTERM。 |
| shutdown.deadlineMs | number | 10000 | 从发送信号算起,工作必须完成、进程必须退出的时限,范围 100–300000 ms。 |
| shutdown.exitCode | number | 0 | 进程退出时必须使用的退出码,范围 0–255。 |
| shutdown.readinessWithdrawal | boolean | false | 要求在 SIGTERM 之后、截止时间之前,就绪检查路由不再返回就绪状态码(或直接拒绝连接)。 |
| shutdown.newRequests | object | — | 在排空期间发送一个新的 GET 请求,并要求服务拒绝它。需要设置 readinessWithdrawal: true。 |
| shutdown.newRequests.path* | string | — | 一个安全、仅供测试使用的路由。旧工作排空期间,新请求会发到这里。 |
| shutdown.newRequests.rejectStatuses | number[] | [503] | 算作拒绝新请求的状态码。 |
| shutdown.newRequests.allowConnectionRefused | boolean | true | 连接被拒绝时是否也算作拒绝。超时一律不算。 |
| shutdown.repeatSignalAfterMs | number | — | 在第一次 SIGTERM 之后等待这段时间,趁工作仍在运行时再发送一次 SIGTERM,确认重复的信号不会中断工作。范围 10–300000 ms,且必须小于 deadlineMs。 |
上表是自动生成的,列出了每个字段的类型和默认值。下面几节说明这些字段如何配合使用,以及在真实服务中该怎么选。
command、cwd 和 env
{
"command": ["node", "dist/server.js"],
"cwd": ".",
"env": {
"PORT": "3510",
"NODE_ENV": "production"
}
}command
把可执行文件和每个参数分别写成数组中的一项。shutdown-check 不经过 shell 执行命令。
尽量直接启动真正的服务进程:
{ "command": ["node", "dist/server.js"] }能不用 shell 字符串或包装进程,就尽量不用:
{ "command": ["npm", "start"] }包装进程收到 SIGTERM 后可能不会转发给服务。它也可能自己先退出,而子进程里的服务仍占着端口,这会导致 SC302。
cwd
cwd 基于配置文件所在的目录解析。不写时,命令就在该目录下运行。
如果配置文件是 config/shutdown-check.json,下面的写法会让命令从项目根目录启动:
{ "cwd": ".." }env
env 会在 CLI 继承的环境变量基础上新增或覆盖变量。所有值都必须是字符串。可以用它给每个测试分配专用端口,或者开启仅供测试使用的路由。
{
"env": {
"PORT": "3510",
"ENABLE_TEST_ROUTES": "1"
}
}baseUrl
baseUrl 是就绪检查、测试请求、探测路由和新请求这些路径共用的源地址(origin),必须是本机上的普通 HTTP 地址。
| 接受 | 拒绝 |
|---|---|
http://127.0.0.1:3510 | https://127.0.0.1:3510 |
http://localhost:3510 | http://example.com:3510 |
http://[::1]:3510 | http://0.0.0.0:3510 |
http://127.0.0.1:3510/ | http://127.0.0.1:3510/api |
为一个测试专门预留这个端口。shutdown-check 要求启动前端口是空闲的,进程退出后端口已关闭。并行运行的测试必须使用不同端口。
就绪检查
就绪检查(readiness)用来告诉 shutdown-check 服务什么时候启动完成。
{
"readiness": {
"path": "/health",
"status": 200,
"timeoutMs": 30000,
"intervalMs": 100
}
}| 字段 | 默认值 | 允许的取值 |
|---|---|---|
path | 必填 | 以 / 开头的本地路径 |
status | 200 | 100 到 599 之间的 HTTP 状态码 |
timeoutMs | 10000 | 100 到 300000 |
intervalMs | 100 | 10 到 10000 |
shutdown-check 会不断轮询这个路由,直到它返回的状态码恰好等于 status。连接被拒绝或返回其他状态码时都会重试。如果进程一直在运行却始终没有就绪,结果是 SC100;如果进程在就绪前就退出了,结果是 SC101。
启用撤回就绪状态的检查后,发送 SIGTERM 之后也会使用这个路由。
测试请求
测试请求(workload)是 SIGTERM 到达时必须仍在进行中的请求。
{
"workload": {
"path": "/reports/export",
"method": "POST",
"headers": { "content-type": "application/json" },
"body": "{\"rows\":5000}",
"status": 200,
"bodyIncludes": "export complete",
"concurrent": 3,
"started": {
"type": "response-headers",
"timeoutMs": 5000
}
}
}| 字段 | 默认值 | 作用 |
|---|---|---|
path | 必填 | 执行这项工作的本地路由 |
method | GET | HTTP 方法 |
headers | {} | 请求头,值必须是字符串 |
body | 无 | 原始请求体 |
status | 200 | 期望的最终响应状态码 |
bodyIncludes | 无 | 响应中必须包含的文本,区分大小写 |
concurrent | 1 | 请求数量,1 到 20 |
started | 必填 | 用来确认测试请求已在进行中的规则 |
选一个可以安全调用、而且耗时足够长的路由,保证请求还没结束时信号就能到达。如果仅凭成功的状态码不足以证明工作已经完成,就加上 bodyIncludes。shutdown-check 对每个响应体最多捕获 1 MiB。
发送 SIGTERM 后,每个请求都必须在截止时间前完成。超时、请求被中断、状态码不对或响应体不对,分别会产生 SC200 到 SC203 中的相应诊断码。
启动确认
启动确认(start barrier)用来避免误判为通过:如果测试请求在信号到达前就已经结束,检查不应该通过。启动确认有两种类型,选其中一种。
response-headers
{
"started": {
"type": "response-headers",
"timeoutMs": 5000
}
}每个测试请求都必须先收到响应头,同时响应体仍未结束。这种启动确认支持 1 到 20 个并发请求。测试路由通常会先调用 response.flushHeaders(),稍后再发送完响应体。
probe
{
"started": {
"type": "probe",
"path": "/test/work-active",
"inactiveStatus": 204,
"activeStatus": 200,
"timeoutMs": 5000,
"intervalMs": 50
}
}发出测试请求之前,探测路由必须返回 inactiveStatus。请求还在进行时,它必须改为返回 activeStatus。这两个状态码必须不同。
probe 只支持一个测试请求,适用于工作全部完成后才发送响应头的处理函数。如果这个路由会暴露应用的内部状态,请只在测试环境中开放它。
完整的服务端示例和失败时的输出,见进行中的工作与启动确认。
关闭
shutdown 对象定义了第一次 SIGTERM 之后的关闭约定。
{
"shutdown": {
"signal": "SIGTERM",
"deadlineMs": 15000,
"exitCode": 0,
"readinessWithdrawal": true,
"newRequests": {
"path": "/test/new-work",
"rejectStatuses": [503],
"allowConnectionRefused": true
},
"repeatSignalAfterMs": 500
}
}signal
只支持 SIGTERM。除非你想在配置里显式写明信号,否则可以省略这个字段。
deadlineMs
默认值是 10000 ms。计时从第一次发送信号开始,涵盖进行中的响应、排空流量相关的检查以及进程退出。应把它设得大于最慢的正常请求加上清理所需的时间,同时小于部署平台强制终止进程的截止时间。
exitCode
默认值是 0。进程必须以这个退出码退出,完全一致才算通过。被信号终止的进程没有退出码,因此不算匹配。
readinessWithdrawal
设为 true 时,就绪检查路由必须在截止时间前不再返回 readiness.status。返回其他状态码或拒绝连接都算通过,超时则不算。
newRequests
这项检查要求 readinessWithdrawal: true。就绪状态撤回之后、原来的测试请求仍在进行时,shutdown-check 会向 newRequests.path 发送一个 GET 请求。
如果这个请求返回的状态码在 rejectStatuses 中,或者 allowConnectionRefused 为 true 且连接被拒绝,检查通过。超时或返回正常的成功响应都会失败。
这个请求是真实发出的,所以请使用一个可以安全调用的测试路由。就绪检查与排空指南介绍了返回 503 和关闭监听这两种做法。
repeatSignalAfterMs
设置后,shutdown-check 会在工作仍在进行时再发送一次 SIGTERM。这个值必须小于 deadlineMs,也必须小于测试请求的持续时间。用它可以发现那些收到重复信号后就提前强制退出的处理函数。
一份严格的完整配置
这个示例会检查三个请求、响应内容、撤回就绪状态、拒绝新请求、重复信号、进程退出和端口关闭:
{
"command": ["node", "dist/server.js"],
"cwd": ".",
"env": { "PORT": "3510", "NODE_ENV": "production" },
"baseUrl": "http://127.0.0.1:3510",
"readiness": { "path": "/health", "status": 200, "timeoutMs": 30000 },
"workload": {
"path": "/slow",
"status": 200,
"bodyIncludes": "work complete",
"concurrent": 3,
"started": { "type": "response-headers", "timeoutMs": 5000 }
},
"shutdown": {
"deadlineMs": 15000,
"exitCode": 0,
"readinessWithdrawal": true,
"newRequests": {
"path": "/test/new-work",
"rejectStatuses": [503],
"allowConnectionRefused": true
},
"repeatSignalAfterMs": 500
}
}测试请求保持进行的时间要足够长,覆盖就绪检查轮询、新请求和重复信号这几步,同时又要在截止时间前完成。
常见校验错误
| 消息 | 修复方法 |
|---|---|
command must be a non-empty array of strings, for example ["node", "server.js"] | 把可执行文件和参数拆成数组 |
baseUrl must use http://localhost, http://127.0.0.1 or http://[::1] | 使用本地的普通 HTTP 地址 |
readiness.path must be a local path beginning with one / | 写成 /health,不要写 URL 或 health |
workload.started.type must be response-headers or probe | 从支持的两种启动确认中选一种 |
workload.concurrent above 1 requires the response-headers start barrier, which verifies every request individually | 改用这种启动确认,或只发一个请求 |
shutdown.newRequests requires shutdown.readinessWithdrawal: true so rejection is tested after drain begins | 开启撤回就绪状态 |
shutdown.repeatSignalAfterMs must be shorter than shutdown.deadlineMs | 调小重复信号的延迟,或调大截止时间 |
v0.1 supports only SIGTERM | 删除 signal,或将它设为 SIGTERM |
在 1.0.1 版本的包中,报错信息里的确写的是 v0.1。按症状分类的错误说明见故障排查。
可以用 TypeScript 代替 JSON 吗?
CLI 只读取 JSON。在测试或脚本中,可以用 defineConfig() 创建一个带类型的配置对象,再传给 checkShutdown():
import { checkShutdown, defineConfig } from "shutdown-check";
const config = defineConfig({
command: ["node", "dist/server.js"],
baseUrl: "http://127.0.0.1:3510",
readiness: { path: "/health" },
workload: {
path: "/slow",
started: { type: "response-headers" },
},
shutdown: { readinessWithdrawal: true },
});
const result = await checkShutdown(config);这个对象遵循同样的规则和默认值。校验和结果处理见 Node API。