TensorZero 部署实战:用 NGINX + Bearer Token 为 TGI 搭建安全持久推理端点
【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero
导读
TensorZero 是一个开源的 LLMOps 平台,将 LLM 网关、可观测性、评估、优化与实验统一于一体。为了让端到端(E2E)测试始终拥有一个持久、安全、可复用的文本生成推理(TGI)端点,TensorZero 在仓库中提供了一个名为tensorzero/tgi-nginx的定制 Docker 镜像:它把 Hugging Face 的 Text Generation Inference 包装在 NGINX 反向代理之后,由 NGINX 负责 Bearer Token 鉴权。读完本文,你将掌握该镜像的设计原理(Dockerfile、NGINX 配置、入口脚本三者的配合方式)、完整的docker run启动命令与参数含义,以及如何把这样一个受保护端点接入 TensorZero 的模型配置与 E2E 测试体系。
本文核心素材来自 tgi-nginx/README.md,并深入其同目录下的 Dockerfile、default.conf、entrypoint.sh 以及 TensorZero 的 TGI Provider 实现与 E2E 测试配置进行佐证。
一、为什么需要"TGI 套 NGINX"的镜像?
在 TensorZero 的 E2E 测试体系中,多个测试用例需要调用真实的大模型推理端点。与一次性拉起测试环境不同,E2E 测试希望有一个**持久(persistent)**的服务端点:它长期在线、地址稳定,从而避免每次测试都重新冷启动模型服务。
但"持久"也意味着"暴露"。如果 TGI 直接裸奔在网络上,任何人都可以调用它,既消耗算力也存在安全风险。因此 TensorZero 的思路很直接:
用 NGINX 作为反向代理挡在 TGI 前面,通过 Bearer Token 做鉴权,只有携带正确令牌的请求才能穿透到后端的 TGI 服务。
这一模式被固化成了一个可复用的 Docker 镜像tensorzero/tgi-nginx,README 中明确说明其用途是 "provide a persistent secure endpoint serving TGI for our E2E tests"。仓库中还有一个同构的 sgl-nginx 镜像(SGLang 版本),两者的 Dockerfile、NGINX 配置与入口脚本几乎完全一致,只是基础镜像和推理进程不同,可见这是 TensorZero 测试基建中反复使用的一套成熟模式。
二、镜像结构拆解:三层组件如何协作
整个镜像由三个文件构成,各自职责单一、组合清晰。下面逐一分析。
2.1 Dockerfile:在 TGI 官方镜像上叠加 NGINX
Dockerfile 的内容如下:
FROM ghcr.io/huggingface/text-generation-inference:latest # Install nginx and remove default site RUN apt-get update && apt-get install -y nginx \ && rm -rf /var/lib/apt/lists/* \ && rm /etc/nginx/sites-enabled/default # Copy the nginx config COPY default.conf /etc/nginx/conf.d/default.conf # Copy our entrypoint script COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh # Nginx listens on port 80; TGI will listen on 8081 EXPOSE 80 # Entrypoint runs TGI and nginx ENTRYPOINT ["/entrypoint.sh"]关键设计点:
- 基础镜像直接采用官方
ghcr.io/huggingface/text-generation-inference:latest,保证 TGI 运行时与官方一致,避免重复维护推理依赖; - 安装
nginx后删除默认站点(/etc/nginx/sites-enabled/default),防止默认配置抢占 80 端口或干扰代理规则; - 自定义的
default.conf被放置到/etc/nginx/conf.d/default.conf,nginx 启动时会自动加载conf.d下的所有配置; - 端口分工:
EXPOSE 80只暴露 NGINX 端口,容器内 TGI 则监听8081(注释写得很清楚:"Nginx listens on port 80; TGI will listen on 8081"); - 入口固定为
entrypoint.sh,由它负责串联起 TGI 与 NGINX 两个进程。
2.2 default.conf:Bearer Token 校验 + 反向代理
default.conf 是鉴权逻辑的核心,全文如下:
server { listen 80; location / { # Default token check fails set $valid_token 0; # Compare provided Authorization header to environment-substituted token if ($http_authorization = "Bearer _MY_SECRET_") { set $valid_token 1; } # If no match, return 401 if ($valid_token = 0) { return 401; } # Otherwise, proxy to TGI proxy_pass http://127.0.0.1:8081; } }实现逻辑非常朴素但有效:
- 通过
set $valid_token 0;默认把令牌校验标记置为"失败"; - 比较请求的
Authorization头是否严格等于Bearer _MY_SECRET_。注意_MY_SECRET_只是一个占位符,真实令牌由入口脚本在容器启动时用sed替换进去(详见下一节); - 若匹配则置
valid_token = 1,否则直接return 401拒绝访问; - 校验通过后,
proxy_pass把请求转发给同容器内127.0.0.1:8081上的 TGI 服务。
这种"占位符 + 启动期注入"的方式有一个好处:镜像本身是公开可构建、可审计的,真正的密钥不写入镜像层,而是在运行时通过环境变量注入,避免令牌被固化进镜像历史。
2.3 entrypoint.sh:令牌注入 + 双进程编排
entrypoint.sh 负责容器启动时的完整编排:
#!/bin/bash # Replace placeholder in nginx config with real token from env if [ -z "$BEARER_TOKEN" ]; then echo "Missing BEARER_TOKEN env var. Exiting." exit 1 fi # Replace placeholder in nginx config with real token from env sed -i "s#_MY_SECRET_#${BEARER_TOKEN}#g" /etc/nginx/conf.d/default.conf ldconfig 2>/dev/null || echo 'Note: Unable to refresh dynamic linker cache. This is expected in some container environments and will not affect functionality.' # Run TGI in background; pass all command-line args directly text-generation-launcher --port 8081 $@ & # Start nginx in the foreground exec nginx -g 'daemon off;'脚本的几个关键行为:
- 强校验令牌:若未设置
BEARER_TOKEN环境变量,立即报错并以非零码退出,杜绝"无鉴权裸奔"的隐患; - 运行时注入密钥:用
sed -i "s#_MY_SECRET_#${BEARER_TOKEN}#g"把 nginx 配置中的占位符替换成真实令牌。这里使用#作为sed的分隔符,可以安全容纳包含/的令牌值; - TGI 后台启动:
text-generation-launcher --port 8081 $@ &—— 强制 TGI 监听8081(与 nginx 的proxy_pass严格对齐),并把docker run后传入的所有命令行参数原样透传给 TGI,这正是 README 中"无需手动指定--port"的原因; - nginx 前台运行:
exec nginx -g 'daemon off;'让 NGINX 以前台进程方式运行并接管容器 PID 1,保证容器生命周期与 NGINX 一致——NGINX 退出即容器退出; ldconfig的调用是为了刷新动态链接器缓存,确保 TGI 二进制在容器环境能正确加载依赖库(失败也不影响功能,脚本会打印提示继续运行)。
三、实战:用 docker run 拉起受保护的 TGI 端点
README 给出的完整用法如下:
docker run \ -p 8080:80 \ # Map port 8080 of the host to port 80 of the container # (can change this, but the container will listen on port 80) -e BEARER_TOKEN=SUPER_SECRET_TOKEN \ # Set the BEARER_TOKEN environment variable to your secret token tensorzero/tgi-nginx:latest \ --model-id microsoft/Phi-3.5-mini-instruct \ # The model to serve --max-input-length 1024 \ --max-total-tokens 2048 \ --max-batch-prefill-tokens 1024 \ --quantize fp8 # Do not pass --port here, it is set by the container逐段解读:
| 部分 | 说明 |
|---|---|
-p 8080:80 | 把宿主机8080端口映射到容器80端口(即 NGINX 监听端口)。宿主端口可以任意调整,但容器侧必须是80;对外访问地址为http://<宿主机>:8080 |
-e BEARER_TOKEN=SUPER_SECRET_TOKEN | 设置鉴权令牌。这是必填环境变量,缺失时入口脚本会直接退出。请务必替换为你的高强度随机令牌 |
tensorzero/tgi-nginx:latest | 镜像名。docker run之后的所有参数(--model-id等)都会原样透传给容器内的text-generation-launcher |
--model-id microsoft/Phi-3.5-mini-instruct | 指定要加载的 Hugging Face 模型(仓库 E2E 测试实际使用phi-3.5-mini-instruct系列模型) |
--max-input-length 1024 | 限制单次请求的最大输入 token 数,防止超长输入拖垮服务 |
--max-total-tokens 2048 | 限制单次请求输入 + 输出的总 token 上限,配合上面的输入限制间接约束输出长度 |
--max-batch-prefill-tokens 1024 | 控制 prefill 阶段单批最多处理的 token 数,是 TGI 吞吐与显存占用的平衡点 |
--quantize fp8 | 以 FP8 精度量化加载模型,显著降低显存占用,适合消费级 GPU 或测试环境 |
--port | 禁止传入。该参数由容器内入口脚本固定为8081,传入自定义值会导致 NGINX 代理与 TGI 端口失配 |
启动成功后,验证方式非常简单:
# 不带令牌访问 → 应返回 401 curl -i http://localhost:8080/v1/chat/completions # 携带正确令牌访问 → 请求被代理到 TGI 并正常处理 curl -i http://localhost:8080/v1/chat/completions \ -H "Authorization: Bearer SUPER_SECRET_TOKEN" \ -H "Content-Type: application/json" \ -d '{"model": "...", "messages": [{"role": "user", "content": "Hello"}]}'四、接入 TensorZero:把受保护端点配成模型
镜像搭建好后,只需在 TensorZero 配置中声明一个tgi类型的模型,并把api_base指向 NGINX 的对外端口即可。E2E 测试的真实配置见 tensorzero.models.toml:
[models."phi-3.5-mini-instruct-tgi"] routing = ["tgi"] [models."phi-3.5-mini-instruct-tgi".providers.tgi] type = "tgi" api_base = "https://zr0gj152lrhnrr-80.proxy.runpod.net/v1/"对应的函数变体配置(见 tensorzero.functions.basic_test.toml)中,测试同时覆盖了正常调用(tgi)、附加请求体(tgi-extra-body)以及携带错误鉴权头(tgi-extra-headers,其extra_headers中设置了Authorization: invalid_tgi_auth)三种场景——后者正是用来验证 NGINX 会拒绝错误令牌、返回 401 的用例。
在 providers/tgi.rs 的测试注册中,TGI 端点的能力边界也被明确标注:supports_batch_inference: false(不支持批量推理)、无工具调用、无嵌入、无缓存输入 token 测试等。这与 tgi.rs 源码 中声明的已知限制一致:TGI 对流式工具调用、多工具请求、工具响应回传支持不完善,因此 TensorZero 对 TGI不显式支持工具调用,仅通过tool方式支持 JSON 模式。如果你的业务依赖工具调用,应优先选择其他 Provider。
五、原理小结与延伸阅读
核心原理一句话:tgi-nginx镜像通过入口脚本在启动期把BEARER_TOKEN注入 NGINX 配置,由 NGINX 在80端口完成 Bearer 鉴权后反向代理到容器内8081端口的 TGI,从而以最小改动得到一个"鉴权前置、推理后置"的安全持久端点。
可继续深入仓库的入口:
- tgi-nginx 完整部署目录(README、Dockerfile、default.conf、entrypoint.sh 四件套);
- 同构的 SGLang 版本 sgl-nginx 部署目录;
- TensorZero 的 TGI Provider 实现 src/providers/tgi.rs,内含完整的能力限制说明与流式/非流式实现细节;
- E2E 测试注册文件 tests/e2e/providers/tgi.rs,展示了该端点在测试矩阵中的全部使用场景。
这套"推理服务 + 反向代理鉴权"的镜像模板,同样适用于企业内部把自建推理端点安全地暴露给网关或测试流水线的场景:只要替换--model-id与BEARER_TOKEN,即可复用到任何 TGI 兼容模型上。
【免费下载链接】tensorzeroTensorZero is an open-source LLMOps platform that unifies an LLM gateway, observability, evaluation, optimization, and experimentation.项目地址: https://gitcode.com/GitHub_Trending/te/tensorzero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考