ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型服务安全上云:DeepSeek Harness公网部署与鉴权指南

大模型服务安全上云:DeepSeek Harness公网部署与鉴权指南

在云服务器上把大模型服务推到公网,最难的不是让接口跑起来,而是让它安全地对外开放。最近帮团队做了一整套 DeepSeek Harness 的公网部署,过程里踩了不少坑,其中最典型的就是“裸奔”问题:模型推理服务监听公网端口,外面谁拿到地址都能发 HTTP 请求,GPU 账单和 Token 消耗都在替陌生流量买单。

这次部署的核心诉求可以拆成三个:一是让 DeepSeek Harness 在云服务器上稳定运行;二是通过公网 HTTPS 对外提供服务;三是每个请求都要过鉴权,未授权流量在入口就被拦下。这套流程跑通后,团队内部多个服务可以直接调用,外部合作方也能按授权访问,不需要担心接口被扫描器枚举或滥用。

写这篇文章,我把完整过程按“设计思路→环境准备→服务搭建→公网接入→鉴权落地→安全运维→问题排查”这条主线讲一遍,重点放在公网鉴权访问的实现上,不讲虚的,每一步都带实际配置和当时的取舍理由。适合正在做自部署模型服务、或者想把内部 AI 能力开放给外部团队的开发者阅读。

1. 部署前必须想清楚的三个问题

1.1 请求链路长什么样

动手之前,我先在脑子里把请求链路完整过了一遍:客户端发起 HTTPS 请求 → DNS 解析到云服务器公网 IP → 云平台安全组放行 443 端口 → 服务器上 Nginx 终止 TLS → 检查 Authorization 头 → 转发给监听在内网回环地址的 Harness 容器 → 容器内完成模型推理或 API 代理 → 返回响应。

这条链路里每个环节都有不可替代的作用。安全组负责物理隔离,Nginx 负责 TLS 和转发,Harness 容器负责业务逻辑,鉴权校验横跨网关层和应用层两道关卡。我习惯把这个拓扑画在纸上再动手,因为后面所有配置项都是围绕这链路展开的,少画一个环节,就容易少配一段防护。

这里有个很容易被忽略的细节:Harness 容器在服务器内部监听时,最好绑到127.0.0.1:8080,而不是0.0.0.0:8080。这样即使某天 Nginx 挂了、防火墙规则被误改,外部也无法直接绕过网关摸到容器端口。我的经验是,部署时把容器的公网暴露能力默认设为零,需要暴露时再通过网关显式放行——宁可多绕一段,也别裸着。

1.2 公网访问到底在防护什么

很多第一次部署的同学会问:“我 API Key 已经在代码里了,为什么还要单独做鉴权?”实际情况是,公网扫描器和爬虫不会因为你接口没公布就不来。只要 443 或高位端口开着,自动化工具就会持续探测路径和参数,普通 HTTP 服务分分钟被枚举。

不设防的 LLM 服务遭遇的典型情况是:有人拿着扫描到端点地址不断发请求,把你们团队的对话记录和模型调用配额全消耗掉。更麻烦的是,如果服务本身没有做用户维度隔离,别人还能通过精心构造的输入套出你内部配置信息。

所以说,公网鉴权不是给服务“加个壳”,而是给每个请求建立身份标识。没有身份的流量,在 Nginx 第一层就被打回去;有身份的流量,到了 Harness 应用层还要经过二次校验,校验维度包括令牌有效性、过期时间、调用频率、来源 IP 黑名单等。两层防护的目的不是为了应付安全审计,而是实打实地保护 GPU 资源和数据资产。

1.3 多少人用,决定了鉴权复杂度

鉴权方案的复杂度没有标准答案,取决于使用人数和使用场景。我这次部署前先统计了需求:团队内部有四个业务服务要调用,外部有两个合作方需要接入,未来可能再加三到五个。这个规模下,每个调用方分配独立的 API Key,配合简单的速率限制就够了,没必要上一套完整的 OAuth2 授权服务器。

如果只是你一个人自用,那在 Nginx 写死一个 Token 就能跑;如果是要做成 SaaS 对外开放、面向大量第三方开发者,那就要考虑动态发放令牌、按用户做计量计费、支持密钥轮换和吊销。方案选型的核心原则是“够用就行,复杂适可而止”——部署模型服务是为了解决业务问题,不是为了炫技。接下来我写到的这套方案,是以中小团队规模为基准的实践,既能保证安全,又不过度设计。

2. 环境准备与资源选型

2.1 云服务器配置怎么选

云服务器配置是整个部署的地基,选错硬件后面全是坑。这里不是一个简单“越大越好”的问题,要分场景来看:

场景CPU内存系统盘数据盘GPU
纯代理转发(本地不跑模型)2核4G40G50G不需要
小规模模型推理(7B量化)8核32G60G200G+单卡24G显存
中大规模并发(13B/34B)16核+64G+60G500G+双卡或更高

我这次选的是中间那一档:8 核、32G 内存、单卡 24G 显存。原因是团队需要尝试在本地加载小尺寸模型做离线推理测试,同时也要跑一个代理转发模式对接官方接口,两个模式并存,资源上就不能太省。

磁盘这里我要多说一句。很多人只盯着系统盘 40G 够不够,忽略了模型权重文件占空间的问题。现在随便一个 7B 模型的量化权重就是 4 到 8G,13B 模型能到 16G 以上,再加 Docker 镜像、日志和临时缓存,数据盘建议直接给到 200G 起步。我吃过一次亏:部署到一半发现磁盘满,镜像拉不下来,进程反复 crash,排查半天才意识到是预留空间不够。

2.2 操作系统与基础软件

操作系统我用的是 Ubuntu Server 22.04 LTS,选择理由是兼容性好、社区资料多、几个主流 GPU 驱动的安装包都直接支持。用某云厂商的 Ubuntu 22.04 镜像,初始化后先做两件事:更新系统包、加固 SSH 登录。

基础软件安装命令很简单:

sudo apt update && sudo apt upgrade -y sudo apt install -y docker.io docker-compose-v2 nginx sudo systemctl enable --now docker sudo systemctl enable --now nginx

这里有个细节值得单独提一下:Docker 官方源在国内环境下可能拉镜像很慢,我当时直接将 Docker 的 registry-mirror 配置到了可用的镜像源,然后重启 Docker 服务,后续拉取速度快了几个量级。这个操作在/etc/docker/daemon.json里加一行"registry-mirrors": ["https://xxx"]即可。

SSH 加固方面,我在云平台控制台生成了密钥对,登录服务器后立刻把/etc/ssh/sshd_config里的PasswordAuthentication改为no,同时把 SSH 端口从 22 改到一个高位端口。这样做的目的很单纯:密码登录在公网环境下被暴力破解的概率太高了,改用密钥对后,安全组里再把新 SSH 端口限定为可信 IP 段放行,基本能挡住绝大部分扫描器。

2.3 数据目录与挂载规划

需要规划的不只是“目录建在哪”,还有“哪块磁盘放什么”。我的建议是单独挂一块数据盘用于模型权重、日志和持久化数据,系统盘只放操作系统和基础软件。这样即使以后系统盘崩了要重装,业务数据还在数据盘上,恢复成本低很多。

在云控制台购买数据盘并挂载后,执行分区和格式化:

sudo mkfs.ext4 /dev/vdb sudo mkdir -p /data sudo mount /dev/vdb /data echo '/dev/vdb /data ext4 defaults 0 0' | sudo tee -a /etc/fstab

然后把 Harness 的工作目录安排在/data/deepseek-harness下,里面再分models和data两个子目录,分别放模型文件和运行时产生的数据。结构清晰的好处是,后续做备份和回滚时一眼就知道哪些目录要打包哪些不用。

3. 服务搭建:镜像、编排与启动

3.1 两种运行模式选哪种

DeepSeek Harness 在我理解里其实承担了两层不同角色。第一层是“本地推理模式”,模型权重直接落到服务器上,Harness 容器加载模型并负责文本生成的预填和解码过程;第二层是“代理转发模式”,服务器上不部署模型,Harness 只做请求封装、会话管理和密钥托管,真正的推理发生在 DeepSeek 官方接口那边。

我这次选择两种模式并存:默认走代理转发模式保证响应速度和质量,同时把小型量化模型下载到本地,用于突发断网或官方接口限流时的降级方案。这里的取舍点在于——本地推理受服务器显存限制,模型一大就放不下,强行量化又会影响回答质量;代理转发模式质量稳定但受网络和官方配额约束。两者互补才是比较稳妥的方案。

3.2 可复用的容器编排配置

为了不把环境变量写死在镜像里,我用 Docker Compose 做编排,敏感配置全部走环境变量文件。这是实际部署的主配置:

version: "3.8" services: harness: image: registry.example.org/deepseek-harness:20250214 container_name: deepseek-harness restart: unless-stopped ports: - "127.0.0.1:8080:8080" env_file: - .env volumes: - /data/deepseek-harness/data:/app/data - /data/deepseek-harness/models:/app/models healthcheck: test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/healthz"] interval: 30s timeout: 5s retries: 3 deploy: resources: limits: memory: 8G

注意端口映射那里写的是127.0.0.1:8080:8080,这一步非常关键。如果直接写成8080:8080,Docker 会把端口绑到0.0.0.0,等于给公网开了个 8080 入口,前面费劲做的安全组规则就白搭了。绑到回环地址后,外部访问只能通过 Nginx 转发进来,所有入站流量都在网关层被过滤一次。

对应的.env文件内容这样组织:

# DeepSeek 官方接口信息 DEEPSEEK_API_BASE_URL=https://api.example.com/v1 DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx # Harness 服务参数 HARNESS_BIND_ADDR=127.0.0.1:8080 AUTH_ENABLED=true API_KEY_FILE=/app/data/api_keys.json JWT_SECRET=change-me-to-a-long-random-string JWT_EXPIRE=24h RATE_LIMIT_PER_KEY=30 LOG_LEVEL=info

.env文件默认不进版本库,且权限要收紧到 600,避免其他系统用户读到 API Key。每次修改.env之后,重启容器才会生效,所以我把这个动作写进了部署检查清单,防止漏重启导致配置不生效的情况。

3.3 启动容器并验证健康状态

配置写好后,首次启动按顺序执行:

cd /data/deepseek-harness docker compose up -d docker ps curl -s http://127.0.0.1:8080/healthz

docker compose up -d会拉镜像并启动容器,docker ps看容器状态是否Up,/healthz探活确认服务进程正常响应。如果返回 JSON 里包含"ok": true之类的标识,说明服务起来了。

启动完成后还要看两样东西,一是不带鉴权的请求会不会被拒,二是普通请求能否正常拿到模型回复。我会先跑一个带错误 Token 的请求确认它返回 401,再跑一个带正确 Token 的请求确认它能通过,两个结果都符合预期才放行到下一步。这一步看似多余,实际能省掉后面排查时的很多自我怀疑。

4. 公网接入:反向代理与 TLS

4.1 云平台安全组:只放开该放的端口

服务本机跑通后,接下来要考虑公网接入。第一道关卡不是 Nginx 配置,而是云平台安全组。安全组的规则配置原则就一条:最小放行。

我在某云控制台给这台服务器加了两条规则:

方向协议端口来源备注
入方向TCP4430.0.0.0/0HTTPS 流量
入方向TCP22022指定内网 IP 段SSH 管理

443 端口对全网放行是必须的,因为公网用户要从任意位置访问;SSH 管理端口我只允许公司内网 IP 段访问,不开放到 0.0.0.0/0。这样即使 SSH 服务有什么漏洞,攻击者也进不到端口层。还有一个细节:我没有放行 80 端口,既然全部走 HTTPS,HTTP 流量直接由 Nginx 跳转处理即可,安全组层面收掉更干净。

4.2 Nginx 反向代理配置

Nginx 配置是整个公网接入的核心环节。下面是我实际落地的配置,建议按照自己的域名和证书路径做适应调整:

server { listen 443 ssl http2; server_name your-domain.example; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; # 请求体最大 10M,防止超大请求把服务拖垮 client_max_body_size 10m; location / { # 无 Authorization 头直接拒绝,省得进业务逻辑 if ($http_authorization = "") { return 401; } proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Authorization $http_authorization; # 关键:LLM 对话可能需要流式输出,关掉缓冲才对 proxy_buffering off; proxy_cache off; proxy_read_timeout 120s; proxy_send_timeout 120s; } } server { listen 80; server_name your-domain.example; return 301 https://$host$request_uri; }

有两点经验要分享。第一,proxy_buffering off必须打开,否则流式输出场景下,Nginx 会先把响应攒一段时间再整体返回,模型一个字一个字吐的结果,客户端收到的是卡顿式的延迟感。第二,Authorization头要显式传给后端 Harness 服务,保证应用层还能再做一次校验;很多转发配置漏了这行,结果网关层鉴权过了,应用层拿不到原始令牌,只能靠层信任,安全隐患不小。

4.3 TLS 证书申请与自动续期

证书这块不需要买付费版,ACME 协议的免费证书已经足够覆盖绝大多数场景。我用acme.sh工具申请证书并写入了自动续期逻辑,流程如下:

apt install -y socat curl curl https://get.acme.sh | sh -s email=admin@your-domain.example acme.sh --issue -d your-domain.example --standalone -k ec-256 acme.sh --install-cert -d your-domain.example \ --key-file /etc/nginx/certs/privkey.pem \ --fullchain-file /etc/nginx/certs/fullchain.pem \ --reloadcmd "systemctl reload nginx"

acme.sh装好后会自动添加 crontab 定时任务,证书到期前会自动续签并触发systemctl reload nginx,中间不需要人工干预。证书申请时用了ec-256椭圆曲线密钥,相比 RSA 相同安全强度下性能更好、握手更快。

这里提醒一个细节:证书文件路径的权限要注意,Nginx 工作进程需要有权限读取私钥文件,否则 reload 时会出现cannot load certificate报错。我习惯把证书放/etc/nginx/certs/目录并设置为 root:root 权限,Nginx 的 master 进程本身以 root 启动,读起来没问题。

4.4 流式响应容易踩的两个坑

配置完公网接入,我顺手验证了一次长文本生成,结果发现两处问题,都跟流式响应相关。

第一个是proxy_read_timeout。默认 60s 的读取超时在高负载推理场景下不够用,如果模型排队时间稍长,Nginx 就会提前断开连接,客户端只收到半截结果。我直接拉到了 120s,并在更长的推理任务场景可以按需调到 300s。要记得:这个值不是越大越好,太大会导致异常慢请求占用连接资源。

第二个是 SSE 类型的流式响应,除了关闭proxy_buffering,还要确认后端响应头里带了X-Accel-Buffering: no。有些场景下 Nginx 会忽略proxy_buffering设置而启用内部缓冲,显式声明这个头后,Nginx 才会对特定响应关闭缓冲。两个配置配合,流式效果才稳定可靠。

5. 鉴权体系落地方案

5.1 Nginx 层快速拦截:没有 Token 直接踢掉

鉴权是整个部署的重点,我把它分成两层来做。第一层放在 Nginx,负责快速过滤明显无效的请求,避免脏流量进入应用层消耗算力。

Nginx 里我用了map指令维护一个有效令牌列表:

# 有效令牌列表,后面每新增一个授权方就加一行 map $http_authorization $authz_passed { default 0; "Bearer key-abc-123" 1; "Bearer key-def-456" 1; } server { # ... location / { if ($authz_passed = 0) { return 401; } # 其余转发配置... } }

这种方式的好处是匹配是精确的,不依赖正则的模糊匹配,可维护性也高。给新授权方发令牌时只改 map 这一段再 reload,不用动业务代码。

不过我必须提醒一点:Nginx 的if指令在 location 中使用有历史遗留的坑,用它做return这类“无害”操作是安全惯例,但不要拿它去改proxy_pass、rewrite之类的行为,否则可能产生不可预期的转发结果。严格遵循这一限制,就不会被 Nginx 的奇怪行为坑到。

5.2 API Key 的生成、托管与轮换

API Key 的生成我直接用系统伪随机源,不用会暴露规律的自增字符串:

openssl rand -base64 32

生成的密钥会记录到 Harness 的数据目录/data/deepseek-harness/data/api_keys.json里,结构大致是这样:

{ "users": [ { "id": "user-001", "name": "内部服务A", "api_key": "key-abc-123", "rate_limit_per_min": 30, "whitelist_ips": [], "enabled": true }, { "id": "user-002", "name": "外部合作方B", "api_key": "key-def-456", "rate_limit_per_min": 10, "whitelist_ips": ["203.0.113.0/24"], "enabled": true } ] }

这个文件是挂载进容器的敏感配置文件,权限设为 600。每次调整授权的启用状态、速率限制或 IP 白名单,改完文件后给 Harness 进程发一个 SIGHUP 信号触发配置重载,不用重启容器。

密钥轮换的流程我固定成三步:先把新 Key 加进列表并把旧 Key 的enabled改为false,然后通知调用方切换,观察一段时间日志确认没有用旧 Key 的异常请求后,再把旧 Key 从文件里删掉。这样做的好处是灰度切换,避免调用方未更新引发线上故障。

5.3 应用层二次校验:JWT 短期令牌

只靠 Nginx 层的静态 API Key 仍然不够,原因是静态 Key 一旦泄露,攻击者可以长期使用。所以我加了第二层校验:应用层签发短期有效的 JWT 令牌。

工作流程是这样:客户端先拿 API Key 请求 Harness 的/auth/token端点换取一个 JWT,默认有效期 24 小时;后续所有业务请求都带Authorization: Bearer <JWT>头。Harness 应用层验签后再放行,过期令牌自动拒绝。这样即使 JWT 泄露,有效期受限,攻击窗口从“永久”缩小到“一天”。

签发令牌的核心代码逻辑也就几十行,手写示范如下:

import os import time import hmac import hashlib import base64 import json SECRET = os.environ["JWT_SECRET"] def encode_jwt(user_id: str, expire_seconds: int = 86400): header = {"alg": "HS256", "typ": "JWT"} payload = { "sub": user_id, "iat": int(time.time()), "exp": int(time.time()) + expire_seconds, } segments = [ base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b"="), base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b"="), ] signing_input = b".".join(segments) sig = hmac.new(SECRET.encode(), signing_input, hashlib.sha256).digest() segments.append(base64.urlsafe_b64encode(sig).rstrip(b"=")) return b".".join(segments).decode()

实际生产环境建议还是使用成熟的 JWT 库,这里手写只是为了说明原理。密钥JWT_SECRET必须是高强度的随机字符串,而且不能和 API Key 存在同一个配置位置,我通常把它放在只有 root 可读的环境文件里。

JWT_EXPIRE我设为 24h 是因为团队内部高频调用,令牌过期太频繁会增加换取令牌的次数。如果是外部合作方接入,建议缩短到 2 到 4 小时,同时配合 refresh token 机制做自动续期。

5.4 更严格场景:客户端证书双向认证

如果业务环境对安全要求极高,例如金融或医疗相关场景,只靠 Token 还不够,可以在 Nginx 层启用 mTLS 双向认证。Nginx 会要求客户端携带一个由自己 CA 签发的证书,验证通过后才继续处理请求,否则在 TLS 握手阶段就断掉连接。

在 Nginx 配置里增加以下内容:

ssl_verify_client on; ssl_client_certificate /etc/nginx/certs/ca.pem;

客户端证书由内部 CA 签发,证书里可以绑定客户 ID 和有效期。这样即便 API Key 或 JWT 泄露,没有合法客户端证书的人也无法建立 TLS 连接,实现物理层和业务层双保险。代价是接入成本变高,外部用户需要安装证书,不适合偏互联网开放型产品。

6. 安全加固与日常运维

6.1 访问日志怎么留才有效

部署上线后,日志是追责、排障和安全审计的第一手材料。默认的 Nginx 日志格式信息太少,我把它改成了 JSON 格式,让每次请求都记录关键字段,包括客户端 IP、请求路径、响应状态码、响应时间、Authorization 头中的用户标识等。

配置一个自定义日志格式:

log_format json_log escape=json '{"time":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"request":"$request",' '"status":"$status",' '"body_bytes_sent":"$body_bytes_sent",' '"request_time":"$request_time",' '"auth_user":"$http_x_user_id",' '"referer":"$http_referer"}'; access_log /var/log/nginx/access_json.log json_log;

加了一个$http_x_user_id字段,这个值由 Harness 在 JWT 校验通过后注入到请求头里,Nginx 只需要原样记录。后续查询“某个用户今天调了多少次”“哪个 IP 触发了 429”这类问题,直接对 JSON 日志做过滤就能快速定位。

日志轮转要同步配置。Nginx 默认的 logrotate 周期是每天切分一次,保留两周的量就够了,不要长期堆着占磁盘。我额外把 Nginx 日志和 Harness 业务日志都挂到了数据盘,避免系统盘被日志撑爆。

6.2 用量限制与异常熔断

LLM 服务的成本模型和普通 Web 服务不一样,单次请求消耗的算力和 Token 数波动很大。我只做访问频率限制还不够,要叠加两个维度:单用户每分钟请求数、单用户每日 Token 消耗上限。

速率限制既可以在 Harness 应用层统计,也可以在 Nginx 用limit_req模块做粗粒度控制。实际使用中我带了两者:Nginx 层挡住明显的突发流量,应用层按用户维度精确计量。Nginx 的临时限流配置如下:

limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=10r/s; location / { limit_req zone=auth_limit burst=20 nodelay; # ... }

异常熔断的逻辑我写在了 Harness 的配置里:如果单个用户在过去一分钟内错误率超过 30%,或者连续五次鉴权失败,自动吊销该用户的临时令牌,并触发告警通知。这个策略在团队内部执行时很有效,但要注意误判风险,所以吊销操作默认只持续 10 分钟,之后自动恢复。

6.3 镜像版本管理与回滚预案

服务迭代是常态,但我把所有镜像都固定了版本号,不用latest标签。Docker Compose 文件里写deepseek-harness:20250214,下次升级时改为deepseek-harness:20250301,然后执行:

docker compose pull harness docker compose up -d --no-deps harness

--no-deps参数非常关键,确保只更新 harness 服务,不会误重启其他依赖服务。升级前我习惯先备份当前容器的数据目录,回滚时只需要把 Compose 里的镜像版本改回旧标签再执行同样的命令,数据不会丢。

数据目录备份我用最简单的tar方式,每天凌晨由 crontab 自动执行:

tar -czf /data/backup/harness-$(date +%F).tar.gz /data/deepseek-harness/data find /data/backup -name "harness-*.tar.gz" -mtime +7 -delete

备份脚本里同时加了过期清理,防止备份文件无限堆积。这个流程不花哨,但对于中小团队场景,一个能恢复、能回滚的备份方案,价值比花里胡哨的运维平台更实在。

7. 常见问题排查实录

7.1 外部死活访问不到服务

这是排在第一位的经典问题,排查顺序很固定:先确认安全组放行,再看服务器本地防火墙,接着看 Nginx 监听状态,最后检查容器映射地址。

# 看端口监听情况 ss -tlnp | grep nginx ss -tlnp | grep 8080 # 看本机防火墙 sudo ufw status # 从外网测试(注意换自己的域名和端口) curl -v https://your-domain.example/healthz

如果是“连接超时”,大概率是安全组或防火墙没放行;如果是“拒绝连接”,大概是 Nginx 没监听或容器没启动;如果是证书错误,那就是证书路径或域名不匹配。我排查过无数次,按这个顺序走下来基本几分钟就能定位。

7.2 明明带了 Token 还是一直 401

鉴权 401 最常见的原因有三个:Token 复制多了空格、大小写不匹配、Nginx map 列表和实际密钥不一致。

Authorization 头要求是Bearer key-abc-123,注意Bearer首字母大写且后面有一个空格。用环境变量注入密钥时,要确认变量末尾没有换行符,否则实际值变成了key-abc-123\n,跟 map 列表永远匹配不上。

遇到这类问题,一个最直接的排查方法是在 Nginx access log 里打开日志格式中的$http_authorization字段,看看实际落入请求头的裸值长什么样子,肉眼对比一下就知道差异在哪了。

7.3 流式响应总是断流

客户端已经配置了流式接收,但响应到一半就停住,并且控制台提示连接重置,多半是 Nginx 的proxy_read_timeout太短。模型推理速度不稳定,有时一个多字长回复处理时间会超过 60 秒,默认超时就会断开。

解决办法是把proxy_read_timeout和proxy_send_timeout都调到 120 秒以上,同时确认proxy_buffering off已设置。后续我又遇到一种情况是客户端和 Nginx 之间有 CDN 或云负载均衡器中转,这些中间层的超时时间也可能把长连接掐断。排查时要从请求链路逐层看,别只盯着 Nginx 排查。

7.4 推理进程无故退出或 GPU 内存不足

GPU 内存不足是自部署模型最让人头疼的问题之一。显存占用不仅来自模型权重,还来自 KV Cache 和推理中间结果。模型加载成功后,多路并发请求会迅速堆高显存使用量,一旦超过上限,进程可能直接被系统杀掉。

实际处理方案主要有几种:一是降低并发数,Harness 配置里设置最大并发请求为 2 到 4;二是使用量化版的模型权重,4-bit 量化能显著减少显存占用;三是给容器配置内存和 swap 的限制,避免单个容器把整机内存吃满。部署完监控显存占用,也可以用nvidia-smi定时采日志:

nvidia-smi --query-gpu=memory.used,utilization.gpu --format=csv -l 5 >> /data/logs/gpu.log

7.5 API Key 泄露了怎么办

处理泄露的第一个动作不是骂人,而是立刻吊销旧 Key。修改api_keys.json把对应enabled改为false,同时更换JWT_SECRET,因为所有已签发的 JWT 都是基于旧密钥签发的,更换后全部失效。执行 reload 让配置生效,然后翻 access log 找出泄露前后的异常请求记录,分析是否有数据外流。

泄露后的复盘很重要,但先把影响面收干净是第一优先级。之后要在团队内部强调密钥保管规范,建议禁止把 Key 提交进代码仓库,而是放到内部密钥管理服务或环境变量里。

最后再分享一个小经验:这套部署跑稳定后,我随手把事情记录成一张带命令和输出格式的运维手册,后续再出现异常,团队任何一个人照着手册就能定位。真正好用的部署方案,是离开某一个人的经验也能独立运行。你在部署过程中如果碰到这里没提到的问题,多留一分当时现场的信息,比事后猜原因要高效得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进