diff --git a/使用OpenResty反向代理.md b/使用OpenResty反向代理.md new file mode 100644 index 0000000..7aa9e65 --- /dev/null +++ b/使用OpenResty反向代理.md @@ -0,0 +1,168 @@ +## 为什么要写这一篇 + +因为当时 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.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 +``` \ No newline at end of file