← 返回资讯

OneAPI 部署与配置实战:从零搭建 AI 聚合网关

2026-07-29

OneAPI 是国内使用最广泛的开源 AI 聚合网关,Go 语言编写,单二进制部署,支持数十家服务商渠道。本文覆盖从零开始的完整部署配置流程,适合有基础 Linux 运维能力的开发者。

如果你是团队里第一个把 OneAPI 搭起来的人,大概率是这样的场景:手头有好几家上游的 Key(可能是 OpenAI 官方、可能是 Azure、也可能是国内某家模型商),团队里每个人各自拿一份 Key 直连太乱,账也对不上,密钥一旦泄露还得挨个撤换。OneAPI 干的事情很简单:把这些上游账号统一收进一个网关,对内发放你自己签发的 sk- 令牌,谁用了多少、限了哪些模型、什么时候过期,全在一个后台里管。看着简单,但部署这一步真正踩坑的地方不在”装起来”,而在”装完之后跑得稳不稳、丢没丢数据、出问题能不能定位到”。下面按实际部署顺序,把每一步该注意的东西讲透。

环境准备

最低配置:1 核 CPU、1 GB 内存、10 GB 磁盘,任意 Linux 发行版(Ubuntu 22.04 推荐)。这个配置是给”个人或小团队日请求量几千次以内”打的底,OneAPI 本身是 Go 写的单二进制,空载内存占用也就几十 MB,真正吃资源的是并发请求转发时的连接数和日志写入。如果你规划的是给几十人团队用、每天几万次调用,建议直接上 2 核 4 GB,把 Redis 也配上,不要等出问题了再加。

依赖

  • Docker 20.10+(推荐方式)
  • 或 Go 1.20+ 编译源码

Docker 方式的好处不只是省事,更重要的是升级和回滚都变成了”换一个镜像 tag”这么简单的操作,源码编译方式一旦遇到 Go 版本不兼容、依赖库拉不下来(国内网络对 golang.org/x/* 系列包不友好),排查成本会明显更高。除非你有魔改源码的需求(比如加自定义鉴权逻辑),否则没必要走编译这条路。

数据库(二选一):

  • SQLite(默认,无需额外安装,适合小规模)
  • MySQL 5.7+ / PostgreSQL 14+(推荐生产环境)

这里有个很多人上来就踩的坑:SQLite 是单文件数据库,写入是串行加锁的。日常测试、单人使用完全没问题,但一旦渠道多、请求并发上来(尤其是日志表频繁写入,OneAPI 默认每次请求都会落一条日志),SQLite 会出现 database is locked 报错,具体表现是网关响应变慢甚至请求超时,但上游 API 本身是正常的。判断依据很简单:如果你的日调用量稳定超过几千次,或者有 3 个以上的人同时通过网关发请求,直接上 MySQL,别等报错了再迁移——SQLite 迁 MySQL 需要手动导数据,中间容易丢日志。

Docker 快速部署

最简启动(SQLite)

# 拉取镜像
docker pull justsong/one-api:latest

# 启动容器
docker run -d \
  --name one-api \
  -p 3000:3000 \
  -v /data/one-api:/data \
  -e TZ=Asia/Shanghai \
  --restart always \
  justsong/one-api:latest

访问 http://服务器IP:3000,默认账号 root / 密码 123456首次登录后立即修改密码

这句提醒不是客套话——OneAPI 的默认账密是公开在源码和文档里的,如果你的服务器有公网 IP 又没配防火墙规则,扫描器几分钟内就能试出这个默认组合。真实发生过的事故是:有人图省事直接把 3000 端口暴露在公网、密码没改,结果后台被人登进去改了渠道密钥、把额度刷爆。所以两件事必须同时做:改密码,以及把 3000 端口通过防火墙或安全组限制成只允许内网/跳板机访问,对外只暴露 Nginx 反代出去的 443 端口。

-v /data/one-api:/data 这行也值得多说一句:OneAPI 的 SQLite 数据库文件、日志、配置都落在容器内的 /data 目录,挂载出来是为了容器重建(比如升级镜像)时数据不丢。如果你漏了这个挂载参数,docker stop && docker rm 之后再重新 docker run,会发现渠道、令牌全没了——这是新手最容易吃的一次亏,务必在第一次启动时就确认挂载生效(docker inspect one-api 看 Mounts 字段)。

生产推荐:接 MySQL

docker run -d \
  --name one-api \
  -p 3000:3000 \
  -v /data/one-api:/data \
  -e TZ=Asia/Shanghai \
  -e SQL_DSN="root:password@tcp(mysql-host:3306)/oneapi?charset=utf8mb4" \
  --restart always \
  justsong/one-api:latest

SQL_DSN 这一行的格式很多人会抄错,尤其是 charset=utf8mb4 这个参数不能省——如果建表时用了默认的 utf8(不是 utf8mb4),存渠道名称或者日志里带 emoji、生僻字的内容时会报 Incorrect string value 错误,直接导致请求写日志失败。建表前确认 MySQL 的数据库字符集,或者干脆让 OneAPI 自己建库(连接串里数据库名如果不存在,部分场景下需要你先手动 CREATE DATABASE oneapi CHARACTER SET utf8mb4)。

另外,MySQL 和 SQLite 之间没法直接热切换:第一次用哪个数据库启动,渠道和令牌数据就落在哪个库里,中途改 SQL_DSN 相当于换了一个全新的空库,之前配置的渠道全部”消失”(其实是在旧库里,没删)。所以数据库选型这一步建议在正式投入使用前就定下来,别等配好几十个渠道之后再想着换库。

关键配置项

环境变量说明

变量说明推荐值
SQL_DSNMySQL 连接串,不设则用 SQLite见上方示例
REDIS_CONN_STRINGRedis 连接串,用于速率限制redis://localhost:6379
SESSION_SECRETSession 加密密钥随机 32 位字符串
INITIAL_ROOT_TOKEN初始 root 令牌(可选,方便 API 初始化)自定义

这几个变量里最容易被忽略、但生产环境必须配的是 REDIS_CONN_STRING。OneAPI 的限流(比如令牌的调用频率限制、每分钟请求数控制)在没有 Redis 的情况下是基于内存计数的,这意味着:如果你部署了多个 OneAPI 实例做负载均衡(前面挂了负载均衡器分流量到 2 台以上机器),每台实例各自计数,限流形同虚设——同一个令牌在两台机器上各打一遍,实际吞吐是设定值的 2 倍。只要你的部署不是单实例,就必须接 Redis,让所有实例共享同一份限流计数。

SESSION_SECRET 不设置的后果也值得说一下:不设置时 OneAPI 会用随机生成的临时密钥,每次容器重启这个密钥都会变,结果是所有已登录用户的 session 全部失效,需要重新登录。如果你的部署是单机长期运行、很少重启,这个问题不明显;但如果配了容器自动重启策略或者用编排工具管理,频繁重启会让管理员体验很差(动不动就被强制登出),建议手动设置一个固定的随机字符串。

Nginx 反代配置(生产必备)

server {
    listen 443 ssl;
    server_name api.yourdomain.com;

    # SSL 证书
    ssl_certificate /etc/ssl/certs/yourdomain.crt;
    ssl_certificate_key /etc/ssl/private/yourdomain.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        # 流式响应必须关闭缓冲
        proxy_buffering off;
        proxy_read_timeout 300s;
        # SSE/streaming 支持
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

proxy_buffering off 是流式响应正常工作的关键,务必配置。

这里稍微展开讲一下原理,不然你可能不理解为什么少了这一行会出问题。流式响应(也就是 ChatGPT 网页版那种一个字一个字往外蹦的效果)底层用的是 SSE(Server-Sent Events),上游模型服务是一边生成 token 一边往下推数据块的。Nginx 默认开启 proxy_buffering,会把上游返回的内容先攒在缓冲区里,攒够一定大小或者等上游连接关闭才一次性转发给客户端——这对普通的 HTTP 请求没问题,但对流式响应就是灾难:客户端会卡在原地等很久,然后一次性收到全部内容,完全丧失”打字机”效果,严重时客户端因为长时间收不到数据触发自己的超时机制,直接报错断连。关掉 proxy_buffering 之后,Nginx 收到多少就转发多少,才能还原真实的流式体验。

proxy_read_timeout 300s 这个值也不是随手写的,它决定了 Nginx 等待上游响应的最长时间。如果你接的上游模型经常有长文本生成(比如生成一篇几千字的文章、或者复杂的推理链),单次响应时间超过 300 秒是很常见的,这时候需要把这个值调得更大(比如 600s),否则会看到 Nginx 主动断开连接、客户端收到 504 Gateway Timeout,但实际上游模型还在正常生成,只是网关层先掐断了。

渠道配置(接入上游)

进入管理后台 → 渠道添加渠道,关键字段:

字段说明
类型选对服务商类型(OpenAI / Anthropic / 自定义 / 等)
名称内部标识,如”OpenAI 主账号”
Base URL上游 API 地址(OpenAI 官方留空或填 https://api.openai.com)
密钥上游 API Key,多个用换行分隔(OneAPI 会随机选用)
模型该渠道支持的模型列表,从下拉中选择
优先级数字越大越优先(同优先级内按权重随机)
权重负载均衡权重(同优先级渠道间按权重分配)

常见渠道配置要点

  • Azure OpenAI:类型选”Azure”,Base URL 填资源端点(https://xxx.openai.azure.com/),模型名需与 Azure 部署名一致
  • Anthropic:类型选”Anthropic”,密钥填 sk-ant-xxx 格式的 key
  • 国内服务商(阿里通义、智谱等):部分有专属类型,否则选”自定义”并填入 OpenAI 兼容端点

优先级和权重这两个字段,很多人配置的时候是混着理解的,其实是两层不同的逻辑:优先级决定”先用谁”,权重决定”同一优先级里怎么分流量”。举个例子说明白:

场景渠道A配置渠道B配置实际效果
主备容灾优先级10,权重1优先级5,权重1只要A能用就一直用A,A挂了才切到B
均匀分流优先级10,权重50优先级10,权重50请求按1:1随机分给A和B
主力+补充优先级10,权重80优先级10,权重20同优先级下,约80%流量给A,20%给B

实际场景里最常用的是第一种:给稳定的官方渠道设高优先级,给临时的、便宜的、或者你不太信任稳定性的渠道设低优先级作为兜底。这样正常情况下都走主渠道,只有主渠道报错或者达到额度上限时才会自动降级到备用渠道,用户几乎无感知。

令牌管理(对外发 Key)

进入 令牌添加令牌

名称:team-a-production
额度:1,000,000(tokens,根据倍率折算)
有效期:2027-01-01(或永不过期)
模型限制:gpt-4o,gpt-4o-mini,deepseek-chat(留空则不限)

生成的令牌以 sk- 开头,格式与 OpenAI key 一致,调用方直接配置即可:

from openai import OpenAI

client = OpenAI(
    api_key="sk-your-oneapi-token",
    base_url="https://api.yourdomain.com/v1"
)

这段代码能直接跑通的前提是:OpenAI SDK 只认 base_url + api_key 两个参数就能切换到任意兼容 OpenAI 协议的服务,这也是 OneAPI(以及几乎所有聚合网关)能存在的技术基础——不管后面接的是 OpenAI 官方、Azure、还是国内某家模型,只要 OneAPI 把协议统一转换成了 OpenAI 格式,调用方这边一行代码都不用改,换的只是 base_url。这一点在你后面想切换供应商、或者从自建网关迁移到托管服务时,成本几乎为零。

“额度”这个字段容易让人误解成”消耗多少 token”,实际上 OneAPI 里的额度是按倍率折算后的虚拟额度计算的,不同模型的倍率不同(贵的模型倍率高,同样调用量消耗的额度更多)。这意味着你设置 100 万额度,如果调用方一直用便宜模型可能能扛很久,一旦切到贵模型(比如 GPT-4 级别),实际能调用的次数会少很多。给团队分配额度时,最好先在后台”渠道-模型倍率”里确认清楚具体的倍率数值,不要直接按 token 数线性估算,避免额度算错导致业务方突然被断量。

模型限制这个字段建议尽量填死,不要留空。留空意味着这个令牌能调用网关下所有已配置的模型,如果这个令牌不小心泄露出去(比如写进了前端代码、或者提交到了公开仓库),泄露方能调用的范围就是你整个网关的全部渠道,损失会被放大。按最小权限原则,给每个业务方发的令牌只开放它实际需要的模型列表。

生产运维要点

监控:OneAPI 暴露 /api/status 健康检查端点和 Prometheus 指标(需开启),建议接入监控系统。实际运维中更实用的做法是:给 /api/status 配一个每分钟一次的外部探活(用 uptime 监控工具或者简单的 crontab + curl 脚本),一旦网关本身挂了或者数据库连接断了能第一时间收到告警,而不是等业务方反馈调用失败了你才发现。渠道级别的可用性(比如某个上游账号被封、余额用尽)OneAPI 后台的渠道列表会标红,建议养成每天看一眼的习惯,或者写个脚本定期调用渠道测试接口自动巡检。

日志:容器日志通过 docker logs one-api -f 查看,或配置 log driver 转发到日志平台。有个容易被忽略的点:Docker 默认的 json-file log driver 不会自动限制日志文件大小,长期运行的网关如果日志量大,/var/lib/docker/containers/.../*.log 文件会无限增长,见过有服务器因为这个把磁盘写满导致容器写日志失败进而请求处理异常。建议在 docker run 时加上 --log-opt max-size=100m --log-opt max-file=3,限制单个日志文件大小和保留份数。

升级

docker pull justsong/one-api:latest
docker stop one-api && docker rm one-api
# 重新执行 docker run 命令(数据持久化在 /data/one-api,不丢失)

升级前有两件事强烈建议做:一是先看一眼 OneAPI 的 GitHub Release Notes,确认有没有破坏性变更(比如数据库表结构调整,虽然一般会自动迁移,但生产环境不能赌”一般”);二是升级前手动做一次数据库备份(见下方备份说明),哪怕只是多花两分钟,也比升级后出问题回滚不了强。升级过程中会有短暂的服务中断(容器停止到新容器就绪之间),如果你的业务方对可用性要求高,建议用两台 OneAPI 做滚动升级,或者选在低峰期操作。

备份:SQLite 直接备份 /data/one-api/one-api.db;MySQL 用 mysqldump 定期备份。备份这件事光”做”还不够,建议自己动手验证一次恢复流程:找个测试环境,把备份文件恢复进去,确认渠道、令牌、日志都完好,能正常发起请求。见过不少团队备份脚本跑了大半年,真出事故要恢复时才发现备份文件是空的或者格式不对,那时候已经来不及了。SQLite 备份可以简单写个每日 crontab:

0 3 * * * cp /data/one-api/one-api.db /backup/one-api-$(date +\%Y\%m\%d).db

再配合定期清理超过 30 天的旧备份,避免磁盘被备份文件占满。

常见问题

渠道测试失败,怎么排查? 在渠道列表点击”测试”,查看错误信息。常见原因:Base URL 末尾多了斜杠、模型名与服务商不匹配、API Key 格式错误。详细日志在 OneAPI 后台”日志”页面查看。

流式响应(streaming)出现乱码或卡住? 90% 是 Nginx 未配置 proxy_buffering off 导致。另检查 proxy_read_timeout 是否足够长(建议 300 秒以上)。

多个渠道同一模型怎么做负载均衡? 添加多个同名模型的渠道,设置相同优先级、不同权重,OneAPI 会按权重随机分配。主备关系则设置不同优先级,主渠道失败后自动降到次优先级渠道。

调用返回 401 Unauthorized,是哪一层的问题? 先分清楚是”客户端调 OneAPI 失败”还是”OneAPI 调上游失败”。OneAPI 后台的日志页面会记录每次请求,如果日志里根本没有这条请求记录,说明请求在到达 OneAPI 之前就被拒绝了,多半是客户端传的 sk- 令牌本身写错了、或者令牌已过期/被禁用,检查令牌管理页面确认状态是”启用”且没过有效期。如果日志里有记录但标记为失败,点开详情看错误信息,通常是渠道配置的上游 API Key 本身失效或者被上游服务商封禁了,需要去渠道页面重新测试并更新密钥。

返回 429 Too Many Requests,怎么判断是谁的限流? 同样先看错误信息里带的具体文案。如果是 OneAPI 自己的限流提示(一般会带类似”请求过于频繁”的中文提示),说明是令牌配置的调用频率超限,去令牌详情调整限速参数或者升级方案;如果错误信息是英文的、格式和上游服务商的错误一致(比如 OpenAI 的 rate_limit_exceeded),说明是上游账号本身触发了限流,这种情况下加钱升级上游账号等级,或者配置多个上游渠道做权重分流分摊压力,都比死等限流解除更实际。

上下文超限(context length exceeded)报错怎么处理? 这个错误是上游模型直接抛出来的,OneAPI 只是原样转发,说明单次请求的 prompt + 历史对话 + 期望输出的总 token 数超过了该模型的上限。解决办法不在网关层,而在业务层:要么换成上下文窗口更大的模型(不同模型上限差异很大,调用前确认清楚),要么在应用侧做历史对话截断或摘要压缩,把发送给模型的内容控制在窗口范围内。

中文或特殊字符出现乱码? 如果是页面展示乱码,大概率是前面提到的 MySQL 字符集问题,确认数据库和连接串都用了 utf8mb4。如果是流式响应里偶尔出现半个乱码字符然后恢复正常,通常是多字节 UTF-8 字符被截断在两个数据块之间导致的临时显示问题,客户端做增量拼接时按完整字节序列处理一般就不会有问题,属于正常现象不用当作故障处理。


延伸阅读:

不想自己部署维护?申请力达云聚合 API 内测,托管聚合服务,零运维开箱即用。