← 中文资源

简体中文版本

Docker SIGTERM 优雅停机

在 Node 服务里捕获 docker stop 发来的 SIGTERM:先摘除就绪状态,排空在途请求与后台任务,再在宽限期内主动退出进程。

阅读英文原文

示例说明

docker stop 默认给容器 10 秒宽限期,随后 SIGKILL 强杀。如果应用不处理 SIGTERM,正在处理的请求会被拦腰截断,写入到一半的数据可能损坏。正确姿势是收到信号后先让健康检查变假让负载均衡摘流量,再等待在途请求完成,最后有意识地以零码退出,配合编排器的滚动更新实现无缝发布。

技术要点

  • SIGTERM 与 SIGINT 都要注册处理器
  • 就绪探针翻转阻止新流量进入
  • close 超时兜底防止无限等待

代码解读

shutdown 里先把 shuttingDown 置真,live 端点随即返回未就绪,编排器会停止转发新请求;app.close 会停止接受新连接并等待现有请求完成,timeout 参数防止个别慢请求拖过宽限期;最后显式 exit(0) 让容器以成功状态终止。

使用建议

宽限期要与编排器配置匹配,Kubernetes 默认 30 秒而 Docker 默认 10 秒,应用内预算要小于平台预算。后台队列消费者停机前应等待当前消息处理完并确认。绝不要在信号处理器里做长阻塞操作。

代码示例

import fastify from 'fastify';

const app = fastify({ logger: true });
let shuttingDown = false;

app.get('/live', async () => ({ ready: !shuttingDown }));
app.post('/jobs', async () => ({ queued: true }));

const server = await app.listen({ port: 3000, host: '0.0.0.0' });

async function shutdown(signal) {
  shuttingDown = true;
  console.log('received', signal, 'draining connections');

  const deadline = Date.now() + 20_000;
  await app.close({ timeout: 15_000 });

  if (Date.now() > deadline) {
    console.warn('grace period exceeded, forcing exit');
  }
  process.exit(0);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));

// Dockerfile 用途提示:
// STOPSIGNAL SIGTERM 让 docker stop 发送可捕获信号而非默认逻辑
console.log('docker-ready on port 3000');