千问3.5-27B生产环境部署:日志分级+端口监听+服务健康检查
千问3.5-27B生产环境部署:日志分级+端口监听+服务健康检查
1. 引言:从“能用”到“好用”的部署进阶
如果你已经成功部署了千问3.5-27B模型,体验了它的中文对话和图片理解能力,那么恭喜你,你已经迈出了第一步。但你可能也发现了,在浏览器里聊聊天、调调接口,这只是“能用”的层面。一旦想把模型真正用在生产环境,比如给团队内部使用、集成到自己的应用里,或者需要长时间稳定运行,就会遇到一堆新问题:
- 服务突然挂了,怎么快速知道?
- 日志文件越来越大,想查个错误信息得翻半天。
- 端口被占用了,服务起不来,一脸懵。
- 想看看服务的CPU、内存占用,不知道怎么下手。
这些问题不解决,模型再好用,也只是一个“玩具”,没法成为可靠的“生产力工具”。
这篇文章,就是帮你把千问3.5-27B从“能用”升级到“好用”的实战指南。我们不谈复杂的模型原理,只聚焦于生产环境部署必须掌握的三个核心运维技能:日志分级管理、端口监听配置和服务健康检查。我会手把手带你,基于现有的镜像环境,把这些能力加进去,让你的模型服务变得稳定、可观测、易维护。
读完本文,你将能搭建一个带有完善监控和自愈能力的千问3.5-27B服务环境。
2. 环境准备与现状分析
在开始改造之前,我们先摸清家底,看看当前镜像提供了什么,以及我们需要在什么基础上进行增强。
2.1 当前部署架构速览
根据提供的使用手册,当前的部署已经相当成熟,为我们打下了很好的基础:
- 模型与框架:使用的是
Qwen/Qwen3.5-27B模型,通过transformers + accelerate库加载,运行在conda环境qwen3527中。 - 服务化:使用
FastAPI提供了 Web 界面(端口7860)和 RESTful API(/generate,/generate_with_image)。 - 进程管理:通过
supervisor托管服务进程,服务名为qwen3527。这已经解决了“进程崩溃后自动重启”的基础问题。 - 日志:目前日志输出到
/root/workspace/qwen3527.log和/root/workspace/qwen3527.err.log。但所有信息都混在一起,不利于排查。
2.2 我们需要增强什么?
当前的架构像一辆能跑的车,但缺少仪表盘和故障报警灯。我们的目标是给它装上:
- 结构化日志系统:把“发动机转速”、“油耗”、“故障码”(对应INFO、WARNING、ERROR日志)分门别类地记录和展示。
- 端口健康监听:确保“车门”(服务端口)是正常打开的,并且没有被其他东西堵住。
- 全面的健康检查:不仅能看“车门”,还要能检查“发动机”(模型推理进程)、“油箱”(GPU内存)是否都工作正常。
接下来,我们分三步,逐一实现这些能力。
3. 第一步:实现日志分级与管理
把所有日志都往一个文件里写,就像把所有的衣服都塞进一个衣柜,找起来会非常痛苦。日志分级就是给日志贴上标签(INFO, DEBUG, ERROR等),并分到不同的“抽屉”里。
3.1 修改服务启动脚本,集成结构化日志
首先,我们需要修改模型的启动方式,使其支持Python标准的logging模块,并将日志输出到文件的同时,也按级别区分。
找到你的服务启动脚本。根据手册,服务目录在/opt/qwen3527-27b,我们假设主启动文件是app.py。我们需要修改或创建一个新的启动文件,例如app_with_logging.py。
# /opt/qwen3527-27b/app_with_logging.py import sys import os import logging from datetime import datetime from pathlib import Path # 1. 创建独立的日志目录 LOG_DIR = Path("/root/workspace/logs/qwen3527") LOG_DIR.mkdir(parents=True, exist_ok=True) # 2. 生成按日期分割的日志文件名 current_date = datetime.now().strftime("%Y-%m-%d") log_file_info = LOG_DIR / f"qwen3527_info_{current_date}.log" log_file_error = LOG_DIR / f"qwen3527_error_{current_date}.log" log_file_debug = LOG_DIR / f"qwen3527_debug_{current_date}.log" # 3. 配置根日志记录器 logging.basicConfig(level=logging.INFO) logger = logging.getLogger("QwenService") # 清除可能已有的处理器 logger.handlers.clear() # 4. 创建不同级别的处理器 # INFO及以上级别 -> info文件 info_handler = logging.FileHandler(log_file_info) info_handler.setLevel(logging.INFO) info_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') info_handler.setFormatter(info_formatter) # ERROR及以上级别 -> error文件 (同时也会被info_handler记录) error_handler = logging.FileHandler(log_file_error) error_handler.setLevel(logging.ERROR) error_handler.setFormatter(info_formatter) # 使用相同格式 # DEBUG级别 -> debug文件 (按需开启,生产环境可关闭) debug_handler = logging.FileHandler(log_file_debug) debug_handler.setLevel(logging.DEBUG) debug_handler.setFormatter(info_formatter) # 5. 将处理器添加到logger logger.addHandler(info_handler) logger.addHandler(error_handler) # logger.addHandler(debug_handler) # 默认不开启DEBUG日志以节省空间 # 6. 同时输出到控制台,方便supervisor捕获 console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.INFO) console_handler.setFormatter(info_formatter) logger.addHandler(console_handler) logger.info("="*50) logger.info("Qwen 3.5-27B 服务启动 - 结构化日志已初始化") logger.info(f"INFO日志文件: {log_file_info}") logger.info(f"ERROR日志文件: {log_file_error}") logger.info("="*50) # 7. 这里是原有的FastAPI应用启动代码 (假设从原app.py导入) # 例如:from app import app # 为了演示,我们模拟一个简单的启动过程 from fastapi import FastAPI import uvicorn app = FastAPI(title="Qwen3.5-27B API") @app.get("/health") async def health_check(): logger.info("健康检查端点被调用") return {"status": "healthy", "model": "Qwen3.5-27B"} # 模拟模型加载 logger.info("开始加载 Qwen3.5-27B 模型...") # 这里替换为实际的模型加载代码 # from transformers import AutoModelForCausalLM, AutoTokenizer # model = AutoModelForCausalLM.from_pretrained(...) # tokenizer = AutoTokenizer.from_pretrained(...) logger.info("模型加载完成。") if __name__ == "__main__": host = "0.0.0.0" port = 7860 logger.info(f"启动Uvicorn服务器,监听 {host}:{port}") uvicorn.run(app, host=host, port=port, log_config=None) # 禁用uvicorn默认日志,使用我们的关键点解释:
- 按日期分割:每天生成新的日志文件,避免单个文件过大。
- 级别分离:
INFO日志记录常规运行信息(如服务启动、接口调用),ERROR日志只记录错误和异常,方便快速定位问题。 - 格式统一:每条日志都包含时间戳、模块名、级别和信息,清晰可读。
3.2 更新Supervisor配置,指向新的启动脚本
接下来,我们需要修改Supervisor的配置,让它运行我们新的、带日志管理的脚本。
Supervisor的配置文件通常位于/etc/supervisor/conf.d/或/root/workspace/下。根据手册,服务名是qwen3527,我们找到对应的配置文件(例如qwen3527.conf)进行修改。
; /etc/supervisor/conf.d/qwen3527.conf (或类似路径) [program:qwen3527] command=/root/miniconda3/envs/qwen3527/bin/python /opt/qwen3527-27b/app_with_logging.py directory=/opt/qwen3527-27b user=root autostart=true autorestart=true startsecs=10 stopwaitsecs=30 ; 重点修改:重定向标准输出和错误到我们的日志系统(或者让Python自己处理) ; 因为我们在Python代码里已经配置了StreamHandler输出到stdout,所以supervisor可以捕获。 ; 我们将supervisor的日志仅作为备份和进程状态记录。 stdout_logfile=/root/workspace/supervisor_qwen3527_out.log stdout_logfile_maxbytes=10MB stdout_logfile_backups=5 stderr_logfile=/root/workspace/supervisor_qwen3527_err.log stderr_logfile_maxbytes=10MB stderr_logfile_backups=5 environment=PYTHONUNBUFFERED="1"修改完成后,让Supervisor重新加载配置并重启服务:
supervisorctl reread supervisorctl update supervisorctl restart qwen3527现在,检查新的日志目录:
ls -la /root/workspace/logs/qwen3527/ tail -f /root/workspace/logs/qwen3527/qwen3527_info_2024-06-15.log你应该能看到按日期和级别分类的、格式清晰的日志了。
4. 第二步:强化端口监听与冲突处理
服务启动失败,很多时候是因为端口被占用。我们需要一个机制,在启动前检查端口,并在启动后确认监听成功。
4.1 创建端口检查与守护脚本
我们可以创建一个独立的脚本,作为服务的“守门员”。
#!/bin/bash # /opt/qwen3527-27b/check_port.sh PORT=7860 SERVICE_NAME="qwen3527" LOG_FILE="/root/workspace/logs/qwen3527/port_guard.log" # 记录日志函数 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE } # 检查端口是否被占用 check_port() { # 使用ss命令检查7860端口是否已被监听 if ss -ltnp | grep -q ":${PORT} "; then PID=$(ss -ltnp | grep ":${PORT} " | awk '{print $6}' | cut -d= -f2 | cut -d, -f1) log "ERROR: 端口 ${PORT} 已被进程 PID ${PID} 占用。" echo "端口 ${PORT} 已被占用,PID: ${PID}。请先停止该进程。" return 1 else log "INFO: 端口 ${PORT} 空闲。" echo "端口 ${PORT} 空闲。" return 0 fi } # 等待端口被成功监听(超时30秒) wait_for_port() { local timeout=30 local count=0 log "INFO: 等待服务在端口 ${PORT} 上启动..." echo "等待服务启动..." while [ $count -lt $timeout ]; do if ss -ltnp | grep -q ":${PORT} "; then PID=$(ss -ltnp | grep ":${PORT} " | awk '{print $6}' | cut -d= -f2 | cut -d, -f1) log "INFO: 服务已成功在端口 ${PORT} 上启动,PID: ${PID}。" echo "服务启动成功,PID: ${PID}。" return 0 fi sleep 1 ((count++)) done log "ERROR: 等待 ${timeout} 秒后,端口 ${PORT} 仍未监听,服务可能启动失败。" echo "服务启动超时,请检查日志。" return 1 } case "$1" in "check") check_port ;; "wait") wait_for_port ;; *) echo "用法: $0 {check|wait}" exit 1 ;; esac给脚本执行权限:chmod +x /opt/qwen3527-27b/check_port.sh
4.2 集成到启动流程中
有两种方式集成:
- 在Supervisor启动前手动检查:在重启服务前,先运行
./check_port.sh check。 - 在应用启动脚本中检查:修改
app_with_logging.py,在启动Uvicorn前调用端口检查逻辑(可以通过调用上述shell脚本或使用Python的socket库实现)。
这里演示第二种,更自动化。在app_with_logging.py的__main__部分加入检查:
# 在 app_with_logging.py 的 if __name__ == "__main__": 部分加入 import socket import sys def is_port_in_use(port: int) -> bool: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: s.bind(('0.0.0.0', port)) return False except OSError: logger.error(f"端口 {port} 已被占用,无法启动服务。") return True if __name__ == "__main__": port = 7860 if is_port_in_use(port): logger.error("服务启动失败,端口被占用。") sys.exit(1) # 非正常退出,Supervisor会检测到并尝试重启 host = "0.0.0.0" logger.info(f"启动Uvicorn服务器,监听 {host}:{port}") uvicorn.run(app, host=host, port=port, log_config=None)这样,如果端口被占,服务会直接启动失败并记录错误日志,由Supervisor接管后续的重启策略(如果配置了的话)。更完善的方案是在启动后,调用wait_for_port逻辑来确认服务确实成功监听了端口。
5. 第三步:构建多层次服务健康检查
健康检查是运维的“眼睛”。一个/health端点是最基础的,但还不够。我们需要一个综合的健康检查脚本,定期运行,并检查多个维度。
5.1 创建综合健康检查脚本
#!/bin/bash # /opt/qwen3527-27b/health_check.sh SERVICE_NAME="qwen3527" PORT=7860 HEALTH_URL="http://127.0.0.1:${PORT}/health" LOG_FILE="/root/workspace/logs/qwen3527/health_check.log" STATUS_FILE="/root/workspace/logs/qwen3527/status.json" # 日志函数 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE } # 初始化状态 overall_status="healthy" message="所有检查通过" details=() # 1. 检查Supervisor进程状态 log "开始健康检查..." if supervisorctl status $SERVICE_NAME | grep -q "RUNNING"; then details+=("进程状态: RUNNING") else overall_status="unhealthy" message="Supervisor报告服务未运行" details+=("进程状态: STOPPED 或 ERROR") log "ERROR: 服务进程状态异常。" fi # 2. 检查端口监听 if ss -ltnp | grep -q ":${PORT} "; then details+=("端口监听: 正常") else overall_status="unhealthy" message="服务端口未监听" details+=("端口监听: 失败") log "ERROR: 端口 ${PORT} 未监听。" fi # 3. 检查HTTP健康端点 http_code=$(curl -s -o /dev/null -w "%{http_code}" -m 5 $HEALTH_URL) if [ "$http_code" = "200" ]; then details+=("HTTP端点: 正常 (${http_code})") else overall_status="unhealthy" message="健康端点响应异常" details+=("HTTP端点: 异常 (${http_code})") log "ERROR: 健康端点返回 HTTP ${http_code}。" fi # 4. 检查GPU内存使用(可选,需要nvidia-smi) if command -v nvidia-smi &> /dev/null; then gpu_util=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits | head -n 1) gpu_mem=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits | head -n 1) details+=("GPU利用率: ${gpu_util}%") details+=("GPU显存: ${gpu_mem} MiB") # 可以设置阈值告警 if [ "${gpu_util%.*}" -gt 95 ]; then log "WARNING: GPU利用率过高: ${gpu_util}%" fi fi # 5. 检查系统负载(可选) load_avg=$(cat /proc/loadavg | awk '{print $1}') details+=("系统负载(1min): ${load_avg}") # 生成状态报告 report=$(cat <<EOF { "status": "${overall_status}", "message": "${message}", "timestamp": "$(date -Iseconds)", "details": $(printf '%s\n' "${details[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))') } EOF ) # 输出到状态文件 echo $report | jq . > $STATUS_FILE log "健康检查完成,状态: ${overall_status}" # 如果状态不健康,可以触发告警(例如发送邮件、Webhook) if [ "$overall_status" != "healthy" ]; then log "触发告警: $message" # 这里可以添加告警逻辑,例如: # curl -X POST -H 'Content-Type: application/json' -d "$report" YOUR_ALERT_WEBHOOK_URL fi # 打印最终状态 echo $report | jq .给脚本执行权限:chmod +x /opt/qwen3527-27b/health_check.sh安装jq(用于JSON处理):apt-get update && apt-get install -y jq或yum install -y jq
这个脚本做了以下几件事:
- 检查进程:通过Supervisor确认服务进程是否在运行。
- 检查端口:确认服务是否成功绑定了端口。
- 检查应用:通过调用
/healthAPI,确认应用内部逻辑是否正常。 - 检查资源:(可选)监控GPU和系统负载,预防资源瓶颈。
- 输出报告:将检查结果生成一个结构化的JSON文件 (
status.json)。 - 触发告警:当状态异常时,可以扩展脚本发送告警通知。
5.2 配置定时健康检查
我们可以使用Linux自带的cron服务,让这个健康检查脚本每分钟运行一次。
编辑crontab:crontab -e添加一行:
* * * * * /bin/bash /opt/qwen3527-27b/health_check.sh >> /root/workspace/logs/qwen3527/cron.log 2>&1现在,每分钟都会执行一次健康检查。你可以随时查看最新的状态:
cat /root/workspace/logs/qwen3527/status.json | jq .5.3 增强健康检查API
我们之前只在app_with_logging.py里写了一个简单的/health端点。现在可以把它增强,让它也返回更丰富的内部状态。
# 在 app_with_logging.py 的 FastAPI app 中添加 import psutil import torch @app.get("/health") async def health_check(): """增强的健康检查端点""" status = {"status": "healthy"} details = {} # 1. 基础状态 details["service"] = "Qwen3.5-27B API" details["timestamp"] = datetime.now().isoformat() # 2. 检查模型是否加载(这里需要根据实际代码调整) # 假设有一个全局变量 `model` 和 `tokenizer` try: # 这是一个示例检查,实际可能是检查model是否不为None # if model is not None and tokenizer is not None: # details["model_loaded"] = True # else: # details["model_loaded"] = False # status["status"] = "degraded" details["model_loaded"] = True # 假设正常 except Exception as e: details["model_loaded"] = False details["model_error"] = str(e) status["status"] = "unhealthy" # 3. 检查GPU(如果可用) if torch.cuda.is_available(): details["gpu_available"] = True details["gpu_device_count"] = torch.cuda.device_count() try: gpu_mem_alloc = torch.cuda.memory_allocated(0) / 1024**3 # 转成GB gpu_mem_cached = torch.cuda.memory_reserved(0) / 1024**3 details["gpu_memory_allocated_gb"] = round(gpu_mem_alloc, 2) details["gpu_memory_cached_gb"] = round(gpu_mem_cached, 2) except Exception as e: details["gpu_memory_error"] = str(e) else: details["gpu_available"] = False # 4. 检查进程内存 process = psutil.Process() details["process_memory_rss_gb"] = round(process.memory_info().rss / 1024**3, 2) details["process_cpu_percent"] = process.cpu_percent(interval=0.1) status["details"] = details logger.info(f"健康检查被调用,状态: {status['status']}") return status现在,访问https://你的域名:7860/health,你会得到一个包含模型状态、GPU内存、进程内存等详细信息的JSON响应。外部监控系统(如Prometheus, Zabbix)可以轻松地抓取这个端点进行监控。
6. 总结:打造可靠的生产级服务
通过以上三步,我们为千问3.5-27B模型服务搭建了一个初步的、面向生产环境的运维框架:
- 日志分级管理:我们告别了混乱的单一日志文件,拥有了按日期和级别(INFO, ERROR)清晰归档的日志系统。排查问题时,你可以快速定位到错误日志文件,而不是在海量信息中挣扎。
- 端口监听保障:通过启动前的端口冲突检查和应用内的双重确认,服务因端口问题而启动失败的概率大大降低。即使失败,也会有明确的错误日志告诉我们原因。
- 多层次健康检查:
- 内部自检:增强的
/healthAPI 提供了服务深度的健康状态。 - 外部巡检:独立的
health_check.sh脚本作为外部看门狗,定时从进程、端口、HTTP、资源等多个维度检查服务,并生成状态报告和触发告警。
- 内部自检:增强的
把这些组合起来,你的模型服务就具备了基本的“可观测性”和“自愈能力”。当服务出现异常时,你能通过日志和健康状态报告快速定位问题;甚至可以通过配置Supervisor的自动重启和健康检查脚本的告警,在无人值守时也能得到通知并尝试恢复。
当然,这只是一个起点。在生产环境中,你还可以进一步考虑:
- 将日志接入
ELK(Elasticsearch, Logstash, Kibana) 或Loki等日志聚合系统。 - 使用
Prometheus和Grafana来收集和可视化/health端点以及系统的各项指标。 - 将健康检查脚本的告警集成到钉钉、企业微信、Slack或PagerDuty等告警平台。
希望这篇指南能帮助你,让强大的千问3.5-27B模型,在一个稳定、可靠的环境中,为你和你的团队持续创造价值。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
