
1. 先盘点现状vLLM 镜像里的端口值和一把“简易钥匙”1.1 镜像启动后API 默认在 8000 端口裸奔先说实话很多人在本地把 vLLM 镜像跑起来一条命令下去模型能出字了就以为“上线了”。但这时候服务的 API 是默认监听在0.0.0.0:8000的也就是说只要主机有公网 IP 或者防火墙没拦住 8000 端口任何能访问到这个地址的人都能往你的推理服务里丢请求。我在实际部署里见过不止一次的情况是一个团队的 GPU 服务器刚配好公网 IP还没来得及套任何防护第二天一看账单令牌被刷了几万次模型响应慢到没法用。原因无非是公网扫描器会把常见端口翻个遍找到 8000 端口返回的是 OpenAI 兼容的响应格式那基本等于告诉全世界“这里有个免费的 LLM 接口”。之前有人问我vLLM 默认端口到底是多少答案是 8000。用--port可以改但我通常不建议改成一个冷门端口来“隐藏”因为这只挡得住不会扫端口的小白挡不住真正想薅羊毛的人。更靠谱的思路是默认裸奔状态必须做点什么哪怕是最简单的一层认证。1.2 用 --api-key 或 VLLM_API_KEY 开启最基础的 Bearer 校验如果你只是想让服务不被陌生人随便调用vLLM 自己提供了一个很轻量的开关--api-key。新版镜像也支持用环境变量VLLM_API_KEY来设置同等效果。从命令行启动时可以这样写docker run --runtime nvidia --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -p 8000:8000 \ -e VLLM_API_KEYsk-prod-xxxxx \ --ipchost \ vllm/vllm-openai:latest \ --model /models/your-model \ --served-model-name your-model启动之后你在本地用 curl 测试curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-prod-xxxxx \ -d {model:your-model,messages:[{role:user,content:你好}]}不带Authorization头或者带一个错误 key 的请求会直接返回 401。虽然这个校验逻辑本身不复杂但对个人项目、内部工具的防护来说已经能拦住绝大多数“随手扫端口”的流量。1.3 只靠一把钥匙还有哪些不放心很多人以为加上VLLM_API_KEY就万事大吉等到真的开始多人协作、多业务共用一个服务时痛点就出现了。首先是一把钥匙所有人共用你根本不知道某个请求到底来自哪个同事、哪个业务线。其次是钥匙一旦泄露没法单独吊销只能改全局 key 然后通知所有人更新配置。更麻烦的是vLLM 原生的 key 校验是“存在性校验”级别的也就是说它只会判断这个请求带上的 Bearer token 是不是和你设的一致并不支持给不同人分配不同 key、也做不了按用户维度的限流和配额统计。所以我的建议是如果你只是在本地调试、或者服务只跑在内网且调用方都是可信的那VLLM_API_KEY够用但只要这个服务要上公网、有多个调用方、或者流量有一定规模就得往上一层考虑。提示--api-key和VLLM_API_KEY本质上是同一层防护别同时配两个不一样的 key容易把自己绕晕。2. 进阶方案JWT 鉴权插件给多个调用方发不同“工牌”2.1 --auth-plugin 的工作方式vLLM 的 OpenAI 兼容服务入口留了一个自定义鉴权接口叫--auth-plugin。通过这个参数你可以挂一个 Python 类进去vLLM 在处理每个请求前会调用这个类的方法做校验。具体到代码层面你需要写一个类继承 vLLM 提供的鉴权基类并实现authenticate方法。这个方法的入参是 FastAPI 的 Request 对象返回值需要包含用户 ID、权限范围等信息。它和--api-key最大的区别在于--api-key是“对暗号”所有请求同一个 key而--auth-plugin可以让你在鉴权逻辑里自由发挥比如解析 JWT、查数据库、判断该用户的配额还剩多少。2.2 一个 JWT 校验插件长什么样下面是一个很常见的实现思路我没有把完整业务逻辑贴出来但骨架大概是这样import time import jwt from vllm.entrypoints.openai.serving_auth import AuthResult, UserAuth class MyJWTPlugin(UserAuth): def __init__(self, config): super().__init__(config) self.jwt_secret 你的签名密钥 async def authenticate(self, request) - AuthResult: token request.headers.get(Authorization, ).replace(Bearer , ).strip() try: payload jwt.decode(token, self.jwt_secret, algorithms[HS256]) except jwt.ExpiredSignatureError: # 过期了 return AuthResult(user_idNone, scopesNone) except jwt.InvalidTokenError: # 伪造的令牌 return AuthResult(user_idNone, scopesNone) # 校验通过返回用户身份 return AuthResult( user_idpayload.get(sub), scopes{chat: True} )启动时加上--auth-plugin my_auth_module.MyJWTPlugin需要注意vLLM 的鉴权插件是在服务端进程内跑的所以这个类必须能被服务进程 import 到。如果你用的是官方镜像最简单的办法是做一个自己的镜像把写好的鉴权文件拷进去并把工作目录加进 PYTHONPATH。2.3 用令牌换接口权限的常见设计JWT 方案的好处不只是“多一把钥匙”而是你可以把权限信息塞进 token 里。比如scope字段表示这个调用方能用哪些模型quota字段记录每分钟最多请求多少次exp字段直接让 token 到期自动失效aud字段可以区分这个 token 是给内部同学还是给外部客户用的这样做的实际意义是当你把 key 发给业务方时不用给一个“永不过期、权限不明”的字符串而是给一个有时效、有范围的令牌。如果某个业务方不再续费或合作停止只要等 token 过期或者把你这边签发的密钥轮换掉就能批量失效。不过 JWT 方案也有代价你需要自己维护签发端和校验端的逻辑而且 token 本身没法在签发后单独吊销除非你引入黑名单机制。所以它更适合已经有一定工程能力的团队而不是只想“加个认证”的个人项目。3. 生产级方案把鉴权从 vLLM 里挪到反向代理层3.1 为什么最终都会走到“代理层鉴权”我见过不少团队一开始用--api-key后来换成 JWT 插件再往后发现要做的越来越多限流、审计、按用户统计、按业务线隔离、灰度发布、多模型路由……这些如果全塞进 vLLM 的进程里一个是维护成本高另一个是每次升级 vLLM 版本还要重新适配插件。所以成熟一点的部署几乎都会在 vLLM 前面加一层反向代理。这层代理可以是一个简单的 Nginx可以是一个 API 网关也可以用 Kubernetes Ingress 自带的认证插件。vLLM 本身只管推理认证、限流、审计全都放在代理层解决。这样做的核心好处是鉴权和推理拆开了。你要升级鉴权策略不用重启 GPU 服务你要换推理引擎不影响调用方的接入方式你甚至可以同时在后面挂多个 vLLM 实例由代理做负载均衡。3.2 Nginx 配置Bearer Key 校验 限流如果你不想引入太重的组件Nginx 是最快能落地的方案。先定义一个 map把合法的 key 判断出来map $http_authorization $is_authorized { default 0; ~^Bearer sk-prod-[A-Za-z0-9]{16,}$ 1; }然后在 server 里做拦截server { listen 443 ssl; server_name llm.internal.example.com; ssl_certificate /etc/nginx/certs/tls.crt; ssl_certificate_key /etc/nginx/certs/tls.key; # 定义限流区域 limit_req_zone $binary_remote_addr zonellm_api:10m rate10r/s; location / { if ($is_authorized 0) { return 401; } # 按 IP 限流超过 20 个突发请求直接拒绝 limit_req zonellm_api burst20 nodelay; proxy_pass http://127.0.0.1:8000; 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 Authorization $http_authorization; } }关键点在于proxy_set_header Authorization $http_authorization一定要把原始请求的 Authorization 头透传给后端的 vLLM。这样后端也可以校验一次避免有人绕过 Nginx 直连 8000 端口。如果你的 key 机制需要更复杂的校验比如支持多个 key、或者要根据 key 找对应的用户信息建议在 Nginx 里配合 Lua 脚本或者干脆换一个真正的 API 网关。3.3 网关方案Token 生命周期、审计和按用户限流当调用方多到“按 key 分类”都不够用的时候就该请 API 网关进场了。我用的比较多的是基于 OpenResty 或各类云原生网关的方案。它们通常提供这些能力认证插件直接支持 Basic Auth、JWT、OAuth2、自定义 Auth 服务限流插件可以按 IP、按 API Key、按用户维度分别限流审计日志每个请求的调用方身份、模型、token 数、耗时都记录动态配置改限流阈值、加黑名单不用重启网关网关层面的鉴权流程通常是客户端拿到一个服务端签发的 token 或 API Key请求先到网关网关校验 token解析出用户身份网关把解析出的用户信息通过请求头传给后端 vLLMvLLM 专心处理推理不再关心认证这种模式下vLLM 甚至可以不启用自己的--api-key因为外部流量根本无法直接触达它。网关背后可以是内网 IP只开放必要的端口给网关所在网段。3.4 别忘了给代理配 TLS否则认证等于白做这一节我必须单独拎出来说因为踩坑的人太多了。很多人配了 Bearer token、配了 JWT却直接用 HTTP 对外提供服务。要知道Bearer token 是明文放在请求头里的如果你不用 HTTPS那 token 在网络传输过程中就等于明文。只要有人在你和服务器之间的链路上抓包就能直接拿到你的 key。在 Nginx 里启用 TLS 只需要配好证书路径并且把 80 端口的流量全转发到 443。用自签名证书虽然会引发客户端提示但至少比明文强。生产环境还是建议用合法的受信任证书省得调用方被安全策略拦得怀疑人生。注意vLLM 镜像本身不会帮你做 TLS它默认就是 HTTP。要把 HTTPS 终止在代理层再由 Nginx 转成 HTTP 回源到 vLLM这是最常见的架构。4. 多调用方接入Key 怎么发、SDK 怎么连、日志怎么盯4.1 设计一套简单的 Key 发放和管理流程如果你只需要应付几个内部系统Key 发放不用搞得很复杂但至少要有一个记录表。我比较习惯的做法是给每个 key 增加几位“身份码”sk-prod-运营后台-001 sk-prod-数据分析-002 sk-test-张三-003这样做的好处是当审计日志里出现某个 key 在疯狂调用时你一眼就能看出是哪个业务线、哪个负责人在用不需要去翻数据库才能定位。哪怕 vLLM 原生 key 校验不支持这种格式限制只要代理层的正则规则够聪明你甚至可以直接按前缀区分不同调用方的权限。发放流程上我建议至少包括这几个动作申请谁要用为什么用大概什么量级签发生成 key记下签发时间、关联负责人、允许访问的模型通知直接把 key 给调用方的开发负责人不要全群转发审计定期过一遍 key 列表把不活跃的和疑似泄露的都清掉4.2 OpenAI SDK 与 curl 两种接入姿势对大多数开发团队来说他们对接 LLM 服务第一反应就是直接用 OpenAI SDK。只要 vLLM 跑的是 OpenAI 兼容接口设置一个base_url指向你的代理地址就完事from openai import OpenAI client OpenAI( base_urlhttps://llm.internal.example.com/v1, api_keysk-prod-运营后台-001, ) resp client.chat.completions.create( modelyour-model, messages[{role: user, content: 今天天气怎么样}], ) print(resp.choices[0].message.content)命令行里临时验证一个 key 是否生效可以直接用 curlcurl https://llm.internal.example.com/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-prod-运营后台-001 \ -d { model: your-model, messages: [{role: user, content: hi}] }很多时候我们用 Python requests 也能直接对接。需要注意的一点是请求体内的model字段必须是 vLLM 启动时--served-model-name设置的那个名字不一定是模型原始的文件名。这个字段对不上vLLM 会直接报 model not found跟鉴权失败是两回事。4.3 审计日志和异常调用追踪认证只是门槛真正能让你睡得着觉的是日志。我强烈建议你至少记录以下字段时间戳调用方 IPAuthorization 头里的 key可以截断比如只保留前几位和后几位请求的模型名输入 token 数和输出 token 数响应状态码耗时有了这些日志你可以轻松回答几个高频问题哪个 key 在凌晨三点疯狂调用哪个接口的响应时间突然飙高某个模型是不是在被大量测试类 prompt 刷vLLM 自带了一些访问日志但如果你想按 key、按用户维度统计建议还是走代理层记录或者把 vLLM 的日志采集进统一的日志平台里再分析。否则每次排查都要登服务器翻日志效率太低。4.4 按用户维度限流与配额鉴权的下一步是配额管理。只认证不限流等于放一辆没刹车的车上了高速。个人项目可以用 Nginx 的limit_req按 IP 限流但这在多人共用出口 IP 时容易误伤。更精细一点的做法是在网关层解析出 key 对应的用户或业务线再按这个维度限流。比如你给 A 部门分配了每分钟 20 次请求的额度给 B 部门分配了每分钟 100 次请求的额度。当某个 key 的请求频率超过你设定的阈值时直接返回 429 Too Many Requests。这样即使有调用方代码写了个死循环也只会拖垮它自己的额度不会影响其他业务方调用同一个模型服务。5. 镜像部署避坑指南环境变量、端口映射和配置热更新5.1 环境变量与启动参数的优先级我在帮别人排查问题时经常看到一种情况命令里写了--api-key sk-A但服务实际用的却是sk-B。最后发现是 Docker 启动命令里同时挂了-e VLLM_API_KEYsk-B。虽然大部分情况二者是等价的但当你混用时容易产生预期外的覆盖关系。我的习惯是统一用一个入口如果是 docker run 临时起服务就只写-e VLLM_API_KEY如果是在 docker compose 或者 K8s 里管理就用环境变量统一管理不要把命令行的参数和环境变量混着写。否则排查起来你都不知道要从哪里找当前生效的配置。5.2 端口映射与镜像重启的坑vLLM 镜像启动的时候容器内的服务端口默认 8000映射到宿主机可以随便指定。但有些团队为了让调用方少改配置一直复用某个固定端口。有一天需要换机器迁移、或者升级镜像版本时如果新容器起不来老容器被停掉端口被占用就会造成短暂的服务中断。更好的做法是在代理层把上游地址配置成一个内网域名而不是直接写 IP:端口。这样 vLLM 实例需要滚动升级时代理层指向的域名可以逐步切换流量避免“端口起不来、业务全挂掉”这种情况。另外我踩过的坑是容器里面写临时文件默认可能写到容器层一旦容器删掉数据也没了。如果鉴权插件依赖一些本地缓存建议把对应目录挂载成持久化卷区。5.3 日志里别把 Token 打出来这是个很低级但很容易犯的错。有时候为了方便调试会在代码里把请求头打出来看一眼打完忘了删。如果这时候日志系统被采集走所有带着Authorization: Bearer sk-xxx的请求头就全被记录在案了。这不只是安全问题还可能导致日志平台变成新的泄露源。我的做法是写一个日志过滤器把请求头里的 Authorization、Cookie 之类的敏感字段统一替换成***。同时给日志平台设置访问权限不是所有人都有权限查生产日志。5.4 常见问题速查表现象可能原因排查方法请求返回 401没带 Authorization 头或 key 不正确用 curl 手动带 key 试一次确认 key 没被环境变量覆盖请求返回 404模型名和 --served-model-name 不一致请求体里换成服务注册时的模型名代理层报 502上游 vLLM 地址写错或容器未就绪看 Nginx error log确认能访问到 127.0.0.1:8000请求返回 429触发了限流策略查看是 IP 维度还是 key 维度限流适当调整阈值加了 TLS 后客户端报证书错误证书链不完整或使用了自签名证书检查证书配置或把自签名证书导入客户端信任区模型能访问但响应很慢可能是鉴权插件里做了同步网络请求检查插件逻辑尽量避免在请求路径上做阻塞式调用5.5 新手最容易犯的 5 个错最后分享五个我在新手项目里反复看到的问题只给 vLLM 加--api-key但代理层完全没配 TLSkey 在网络里裸奔。把 vLLM 的 8000 端口直接映射到公网外部流量先于代理层触达真实服务认证等于形同虚设。用同一个 key 给所有调用方出问题后无法定位只能全局重置。配了限流但阈值拍脑袋写得很小导致正常的批量任务被误杀。修改鉴权配置后没有验证健康检查接口。负载均衡器和监控系统的探针可能是从内网发起的如果不放行/health或/metrics服务会被误判为宕机造成告警轰炸。提示vLLM 的/health路径会返回 200 表示服务就绪但注意它同样不应该对公网完全开放。更好的做法是让代理层只放行内部网段的探针请求其他来源一律走完整鉴权流程。我个人在实际部署里的体会是vLLM 的鉴权从来不是“开一个开关”就能永远高枕无忧的事而是一次次跟着调用方数量、业务复杂度升级的。先让服务不再裸奔再逐步引入代理层、TLS、限流和审计日志才是比较稳妥的路径。如果你现在的 vLLM 服务还只是停在“跑得通”的阶段那我的建议很简单今天就把VLLM_API_KEY配上把 8000 端口从公网防火墙里摘掉剩下的再慢慢打磨。