You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask+uWSGI优雅关闭方案求助:无法捕获SIGTERM/SIGKILL信号

可行的优雅关闭方案

一、保留Flask+uWSGI的方案

1. 用uWSGI信号API捕获终止信号

uWSGI会拦截原生Python信号,直接用uWSGI提供的API注册信号处理更可靠,适配你1进程4线程的场景:

from flask import Flask
import uwsgi
import signal

app = Flask(__name__)

def cleanup(signum, frame):
    # 写入你的关闭逻辑:关闭数据库连接、停止后台线程、清理临时资源等
    print("Executing graceful shutdown...")
    # 示例:等待后台线程结束
    # if background_thread.is_alive():
    #     background_thread.join(timeout=10)
    # 示例:关闭数据库连接池
    # db_pool.close()
    uwsgi.stop()  # 通知uWSGI停止当前worker

# 仅在uWSGI环境下注册信号
if 'uwsgi' in locals():
    # 捕获TERM(优雅关闭)和INT(强制中断)信号
    signal.signal(signal.SIGTERM, cleanup)
    signal.signal(signal.SIGINT, cleanup)

对应的uWSGI配置文件(uwsgi.ini):

[uwsgi]
module = your_app:app
processes = 1
threads = 4
enable-threads = true  # 确保线程被正确管理
die-on-term = true  # 收到TERM信号后立即启动退出流程

启动命令:uwsgi --ini uwsgi.ini
关闭时发送TERM信号:kill -TERM $(ps aux | grep uwsgi | grep -v grep | awk '{print $2}')

2. 使用uWSGI Master FIFO(适合未来扩展多进程)

如果之后要扩展到多进程,Master FIFO是官方推荐的全局控制方式,以下是可运行示例:

第一步:修改uWSGI配置

[uwsgi]
module = your_app:app
master = true  # 启用master进程管理worker
processes = 1
threads = 4
enable-threads = true
master-fifo = /tmp/uwsgi_control_fifo  # 定义FIFO文件路径
die-on-term = true

第二步:在Flask应用中添加FIFO处理逻辑

from flask import Flask
import uwsgi
import os
import signal

app = Flask(__name__)

# Worker进程的清理逻辑
def worker_cleanup(signum, frame):
    print("Worker process cleaning up...")
    # 线程级资源清理
    uwsgi.stop()

# Master进程的FIFO处理逻辑
def handle_fifo_commands(signum, frame):
    if not os.path.exists('/tmp/uwsgi_control_fifo'):
        return
    with open('/tmp/uwsgi_control_fifo', 'rb') as f:
        cmd = f.read().strip()
    if cmd == b'stop':
        print("Master received stop command, triggering global shutdown...")
        # 通知所有worker执行清理
        uwsgi.send_to_all_workers(signal.SIGTERM)
        # 等待worker退出后停止master
        uwsgi.stop()

# 根据进程角色注册逻辑
if 'uwsgi' in locals():
    if uwsgi.masterpid() == 0:
        # 当前是worker进程,注册终止信号
        signal.signal(signal.SIGTERM, worker_cleanup)
    else:
        # 当前是master进程,注册FIFO检查信号
        uwsgi.register_signal(20, "master", handle_fifo_commands)
        # 每隔1秒检查一次FIFO(可调整间隔)
        uwsgi.add_timer(20, 1)

第三步:触发优雅关闭

执行命令向FIFO写入停止指令:

echo "stop" > /tmp/uwsgi_control_fifo

二、替换为Gunicorn+Flask(更简单的优雅关闭)

Gunicorn对信号处理的支持更成熟,文档也更完善,适合快速解决问题:

1. 安装依赖

pip install gunicorn

2. 在Flask应用中添加清理逻辑

from flask import Flask
import signal
import sys

app = Flask(__name__)

def graceful_shutdown(signum, frame):
    print("Executing graceful shutdown...")
    # 执行你的清理逻辑
    sys.exit(0)

# 注册终止信号
signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)

3. 启动Gunicorn并启用优雅关闭

gunicorn --workers=1 --threads=4 --timeout=30 --graceful-timeout=15 your_app:app --pid=gunicorn.pid
  • --graceful-timeout=15:给15秒时间完成清理,超时后强制退出
  • 关闭时发送TERM信号:kill -TERM $(cat gunicorn.pid)

三、替换为FastAPI+Uvicorn(ASGI原生优雅关闭)

FastAPI基于ASGI标准,Uvicorn原生支持生命周期管理,不需要手动处理信号:

1. 安装依赖

pip install fastapi uvicorn

2. 利用生命周期事件实现优雅关闭

from fastapi import FastAPI
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 服务启动时的初始化逻辑(可选)
    print("Service starting...")
    yield  # 服务运行中
    # 服务关闭时的清理逻辑
    print("Executing graceful shutdown...")
    # 清理异步资源:关闭数据库连接、停止异步任务等

app = FastAPI(lifespan=lifespan)

@app.get("/")
async def root():
    return {"message": "Hello World"}

3. 启动Uvicorn

uvicorn --workers=1 --threads=4 --timeout-graceful-shutdown=15 your_app:app --pid=uvicorn.pid
  • --timeout-graceful-shutdown=15:设置优雅关闭超时时间
  • 关闭时发送TERM信号即可:kill -TERM $(cat uvicorn.pid)

关于Kubernetes preStop钩子的修复

你之前的preStop没生效,可能是因为钩子执行时机或命令问题,试试这个配置:

spec:
  containers:
  - name: your-app
    image: your-image
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 3 && curl -X POST http://localhost:8000/internal/shutdown"]

然后在你的应用中添加一个仅限本地访问的关闭接口:

# Flask示例
from flask import request

@app.route('/internal/shutdown', methods=['POST'])
def shutdown():
    if request.remote_addr != '127.0.0.1':
        return "Forbidden", 403
    graceful_shutdown(signal.SIGTERM, None)
    return "Shutting down..."

这样preStop钩子会先调用接口触发清理,再等待容器终止。

内容的提问来源于stack exchange,提问作者Vaibhav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 07:59:56