健康检查与优雅停机
问题
服务端如何实现健康检查和优雅停机?在 K8s 环境下有什么特别考虑?
答案
健康检查
health-check.ts
import express from 'express';
const app = express();
// 存活检查(liveness):是否需要重启
app.get('/health', (_req, res) => {
res.status(200).json({ status: 'ok' });
});
// 就绪检查(readiness):是否能接收流量
app.get('/ready', async (_req, res) => {
try {
// 检查数据库连接
await db.$queryRaw`SELECT 1`;
// 检查 Redis 连接
await redis.ping();
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error: String(error) });
}
});
K8s 探针配置
deployment.yaml
spec:
containers:
- name: api
livenessProbe: # 失败 → 重启 Pod
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe: # 失败 → 从 Service 摘除
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
startupProbe: # 启动完成前不执行其他探针
httpGet:
path: /health
port: 3000
failureThreshold: 30
periodSeconds: 2
优雅停机
graceful-shutdown.ts
const server = app.listen(3000);
let isShuttingDown = false;
async function gracefulShutdown(signal: string) {
console.log(`Received ${signal}, starting graceful shutdown...`);
isShuttingDown = true;
// 1. 停止接受新连接
server.close(async () => {
console.log('HTTP server closed');
// 2. 等待进行中的请求完成
// 3. 关闭数据库连接
await db.$disconnect();
// 4. 关闭 Redis 连接
await redis.quit();
// 5. 清理其他资源
console.log('All resources cleaned up');
process.exit(0);
});
// 超时强制退出
setTimeout(() => {
console.error('Forced shutdown after timeout');
process.exit(1);
}, 30000);
}
// 健康检查中间件:停机中返回 503
app.use((req, res, next) => {
if (isShuttingDown) {
return res.status(503).json({ error: 'Server is shutting down' });
}
next();
});
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
常见面试问题
Q1: liveness 和 readiness 的区别?
答案:
- liveness:应用是否存活。失败 → K8s 重启 Pod
- readiness:应用是否就绪。失败 → K8s 不发流量到该 Pod
典型场景:应用启动中(readiness 失败但 liveness 正常),或依赖的数据库暂时不可用。
Q2: 为什么需要优雅停机?
答案:
直接 kill 进程会导致:
- 进行中的请求被中断(用户看到 502)
- 数据库事务未提交
- 消息队列中的任务丢失
优雅停机确保已接收的请求处理完毕、资源正确释放。
Q3: NestJS 中如何实现优雅停机?
答案:
// main.ts
app.enableShutdownHooks(); // 启用生命周期钩子
// service.ts
@Injectable()
class AppService implements OnModuleDestroy {
async onModuleDestroy() {
await this.cleanup();
}
}
相关链接
- 容器与编排基础 - K8s 部署
- Node.js 部署与运维 - PM2 优雅停机