container/使用OpenResty反向代理.md

168 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 为什么要写这一篇
因为当时 vLLM 部署的 gpt-oss-120b 的模型,总是造成 vLLM 宕机,分析宕机时的崩溃日志是由于 vLLM 根据模型的回复内容调用结构化输出的工具,然后模型回复的内容跟结构后输出函数不兼容所以抛了 ValueError 导致 vLLM 的引擎进程退出APIServer 与引擎的进程有心跳机制,发现引擎宕机了,所以自杀了。但是没有请求参数日志,不知道啥样的请求参数触发了结构化输出的功能。在 Open AI API 层面是有校验 response_format 的参数合法性的,不合法会直接拒绝。所以当务之急是先捕获请求参数,结合 vLLM 的宕机时间,尝试复现宕机的参数。
## 踩了很多坑
- 第一反应是看看能不能调整 vLLM 有没有开启打印请求参数的能力vLLM 的版本是 v0.11.0,查看源码发现是没有的。
- 第二种就是使用抓包工具去实时抓包,选择了 tshark 它是 wireshark 的命令行版本。
- 在服务器后台运行了一晚上,天塌了!这叼东西会一直写临时文件,存储路径:/tmp/*.pcap。
- 它是抓包网卡的流量,在服务器部署了好几个大模型和 OpenAI API 端点。
- 而且有其他同事在压测,一直在并发调用 Open AI API所以一晚上写了 300G+ 的临时文件,服务器直接告警了,运维挨批了。
## 终极方案
不修改 vLLM 的源码,也不用抓包工具,针对 vLLM 部署的 gpt-oss-120b 的 Open AI API 加一层反向代理,把请求参数写到 access_log 并轮转。
## OpenResty
用这玩意儿是因为它可以在 nginx.conf 里面写 Lua 脚本,还提供了一系列的增强能力,比直接用 nginx 更省心。
### nginx.conf
```
worker_processes auto;
error_log stderr warn;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
lua_need_request_body on;
log_escape_non_ascii off;
# 注意:这里不再使用 $time_local而是用自定义变量 $log_time
log_format llm_audit '[$log_time] | $request_uri | $raw_body';
upstream llm_backend {
server 127.0.0.1:8080; # 修改成你的服务地址
keepalive 32;
}
server {
listen 8000;
server_name _;
client_max_body_size 100M;
client_body_buffer_size 128k;
client_body_in_single_buffer on;
location /v1/chat/completions {
set $log_time "";
set $raw_body "";
rewrite_by_lua_block {
local now = ngx.time()
local tm = os.date("*t", now)
ngx.var.log_time = string.format(
"%04d-%02d-%02d %02d:%02d:%02d",
tm.year, tm.month, tm.day,
tm.hour, tm.min, tm.sec
)
local body = ngx.var.request_body or ""
ngx.var.raw_body = body
-- 判断是否为流式请求
if body ~= "" then
local cjson = require "cjson.safe"
local ok, json = pcall(cjson.decode, body)
if ok and type(json) == "table" and json.stream == true then
ngx.exec("@stream")
return
end
end
ngx.exec("@normal")
}
}
# ========== 流式响应 ==========
location @stream {
internal;
access_log /hook/request.log llm_audit;
proxy_pass http://llm_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_buffering off;
proxy_cache off;
send_timeout 600s;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 600s;
proxy_socket_keepalive on;
}
# ========== 非流式响应 ==========
location @normal {
internal;
access_log /hook/request.log llm_audit;
proxy_pass http://llm_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_buffering on;
proxy_cache off;
send_timeout 600s;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 600s;
proxy_socket_keepalive on;
}
}
}
```
### /hook/request.log
```
/hook/request.log {
daily
rotate 5
size 20M
compress
delaycompress
missingok
notifempty
copytruncate
su root root # 如果 nginx 以非 root 用户运行,需匹配权限
}
```
### docker-compose.yml
```yml
version: "3.8"
services:
openresty:
image: docker.1ms.run/openresty/openresty:jammy
container_name: openresty
environment:
- TZ=Asia/Shanghai
- http_proxy=
- https_proxy=
- HTTP_PROXY=
- HTTPS_PROXY=
- no_proxy=
- NO_PROXY=
ports:
- "28000:8000"
volumes:
# 宿主机这个目录可以查看 request.log 也必须包含 nginx.conf
- ./hook:/hook
# 日志轮转配置文件
- ./hook/hook-nginx:/etc/logrotate.d/hook-nginx
command: ["/usr/local/openresty/bin/openresty", "-c", "/hook/nginx.conf", "-g", "daemon off;"]
restart: on-failure:3
```