1. 本地 K8s 集群部署 FastGPT 到底难在哪
FastGPT 是一个能上传文档、做知识库问答、还能编排工作流的开源项目,适合想在内网跑一套私有 AI 应用的人。官方给的 docker-compose 在单机上很顺,但一旦搬到 K8s,问题就冒出来了:MongoDB 要副本集、PostgreSQL 要带 pgvector 扩展、One-API 要单独暴露、FastGPT 主进程还得知道 sandbox 的集群内地址。我第一次照着 compose 文件手搓 YAML 时,Pod 一直 CrashLoopBackOff,日志里全是连不上 mongo 的报错。
这篇就按我实际踩过的路径来:先规划命名空间和存储,再把 MongoDB、PostgreSQL、One-API、FastGPT 四个组件拆成独立清单,用 ConfigMap 挂载 config.json,最后把模型调用的 Base URL 指向 TaoToken,用一次真实对话验证端到端连通。整套清单可以直接复制,改掉存储路径和密钥就能跑。
适合谁看:手上有本地 K8s 集群(1.20 以上都行)、想在内网搭一套知识库问答、又不想被各家模型 API 的接入细节卡住的人。核心检索词就三个:K8s 集群部署、FastGPT 本地化、TaoToken 统一 API 接入。下面从命名空间开始,一步步来。
2. 部署前的命名空间与存储规划
命名空间我习惯按项目隔离,这里统一用fastgpt。先建命名空间,再准备 PV/PVC。存储这块有个坑:hostPath 类型的 PV 不会自动创建目录,你得先在节点上把目录建好,否则 Pod 挂载会失败。
kubectl create namespace fastgpt然后在每个可能调度到的节点上建目录。我用的是单节点测试集群,多节点的话建议用 local PV 加 nodeAffinity,或者直接上 NFS。
sudo mkdir -p /mnt/data/{pg,mongo,mysql,oneapi} sudo chmod -R 777 /mnt/data接下来是 PV 清单。注意persistentVolumeReclaimPolicy: Retain,这样删 PVC 时数据不会跟着没,调试阶段很关键。
# pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pg-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/mnt/data/pg" --- apiVersion: v1 kind: PersistentVolume metadata: name: mongo-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/mnt/data/mongo" --- apiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/mnt/data/mysql" --- apiVersion: v1 kind: PersistentVolume metadata: name: oneapi-pv spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/mnt/data/oneapi"PVC 部分要显式指定volumeName,不然动态供给没配的话会一直 Pending。
# pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pg-pvc namespace: fastgpt spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: pg-pv --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mongo-pvc namespace: fastgpt spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: mongo-pv --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-pvc namespace: fastgpt spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: mysql-pv --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: oneapi-pvc namespace: fastgpt spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: oneapi-pv应用顺序很重要:先 PV 再 PVC,最后才是工作负载。我试过把 PV 和 Deployment 写在一个文件里 apply,结果 PVC 还没绑定 Pod 就起来了,直接挂载失败。
kubectl apply -f pv.yaml kubectl apply -f pvc.yaml kubectl get pvc -n fastgpt看到四个 PVC 都是Bound状态,存储这关就算过了。如果卡在 Pending,先kubectl describe pvc看事件,多半是 volumeName 写错或者节点上目录权限不对。
3. 依赖组件编排:MongoDB、PostgreSQL 与 One-API 的 YAML 清单
FastGPT 依赖三个外部组件:MongoDB 存业务数据、PostgreSQL 带 pgvector 存向量、One-API 做模型网关。这三个必须先起来,FastGPT 主进程才能正常启动。
3.1 MongoDB 副本集配置
FastGPT 要求 MongoDB 以副本集模式运行,单节点也要rs.initiate。官方镜像里带了初始化脚本,但 K8s 下得自己用 command 覆盖启动逻辑。
# mongo.yaml apiVersion: v1 kind: Service metadata: name: mongo namespace: fastgpt spec: ports: - port: 27017 name: mongo clusterIP: None selector: app: mongo --- apiVersion: apps/v1 kind: Deployment metadata: name: mongo namespace: fastgpt spec: replicas: 1 selector: matchLabels: app: mongo template: metadata: labels: app: mongo spec: containers: - name: mongo image: registry.cn-hangzhou.aliyuncs.com/fastgpt/mongo:5.0.18 ports: - containerPort: 27017 env: - name: MONGO_INITDB_ROOT_USERNAME value: "root" - name: MONGO_INITDB_ROOT_PASSWORD value: "123456" volumeMounts: - name: mongo-pvc mountPath: /data/db resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 250m memory: 250Mi command: - bash - -c - | if [ ! -f /data/mongodb.key ]; then openssl rand -base64 128 > /data/mongodb.key chmod 400 /data/mongodb.key chown 999:999 /data/mongodb.key fi if [ ! -f /data/initReplicaSet.js ]; then echo 'const isInited = rs.status().ok === 1 if(!isInited){ rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "mongo:27017" } ] }) }' > /data/initReplicaSet.js fi exec docker-entrypoint.sh mongod --keyFile /data/mongodb.key --replSet rs0 & until mongo -u root -p 123456 --authenticationDatabase admin --eval "print('waited for connection')" > /dev/null 2>&1; do echo "Waiting for MongoDB to start..." sleep 2 done mongo -u root -p 123456 --authenticationDatabase admin /data/initReplicaSet.js wait $! volumes: - name: mongo-pvc persistentVolumeClaim: claimName: mongo-pvc这里有个细节:host: "mongo:27017"用的是 Service 名,因为 headless Service 的 DNS 能解析到 Pod IP。副本集初始化后,FastGPT 连接串里要带replicaSet=rs0,否则读写会报NotPrimaryNoSecondaryOk。
3.2 PostgreSQL + pgvector
FastGPT 的向量检索依赖 pgvector 扩展,普通 postgres 镜像不行,得用官方打包好的pgvector:v0.7.0。
# pg.yaml apiVersion: apps/v1 kind: Deployment metadata: name: pg namespace: fastgpt spec: replicas: 1 selector: matchLabels: app: pg template: metadata: labels: app: pg spec: containers: - name: pg image: registry.cn-hangzhou.aliyuncs.com/fastgpt/pgvector:v0.7.0 ports: - containerPort: 5432 env: - name: POSTGRES_USER value: "username" - name: POSTGRES_PASSWORD value: "password" - name: POSTGRES_DB value: "postgres" volumeMounts: - mountPath: /var/lib/postgresql/data name: pg-pvc resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 250m memory: 250Mi volumes: - name: pg-pvc persistentVolumeClaim: claimName: pg-pvc --- apiVersion: v1 kind: Service metadata: name: pg namespace: fastgpt spec: ports: - port: 5432 selector: app: pg3.3 One-API 网关
One-API 在这里的角色是模型代理层。FastGPT 不直接调各家模型,而是把请求发给 One-API,由 One-API 统一转发。这样换模型、加渠道都只改一处。
# oneapi.yaml apiVersion: apps/v1 kind: Deployment metadata: name: oneapi namespace: fastgpt spec: selector: matchLabels: app: oneapi replicas: 1 template: metadata: labels: app: oneapi spec: containers: - name: oneapi image: ghcr.io/songquanpeng/one-api:latest ports: - containerPort: 3000 env: - name: SQL_DSN value: "root:oneapimmysql@tcp(mysql:3306)/oneapi" - name: SESSION_SECRET value: "oneapikey" - name: MEMORY_CACHE_ENABLED value: "true" - name: BATCH_UPDATE_ENABLED value: "true" - name: BATCH_UPDATE_INTERVAL value: "10" - name: INITIAL_ROOT_TOKEN value: "fastgpt" volumeMounts: - mountPath: /var/lib/oneapi/data name: oneapi-pvc resources: limits: cpu: 500m memory: 512Mi requests: cpu: 50m memory: 51Mi volumes: - name: oneapi-pvc persistentVolumeClaim: claimName: oneapi-pvc --- apiVersion: v1 kind: Service metadata: name: oneapi namespace: fastgpt spec: type: NodePort ports: - port: 3001 targetPort: 3000 selector: app: oneapiOne-API 自己需要 MySQL 存渠道和令牌,所以还得补一个 MySQL 清单。这部分和 excerpt 里一致,直接复用即可。
# mysql.yaml apiVersion: apps/v1 kind: Deployment metadata: name: mysql namespace: fastgpt spec: selector: matchLabels: app: mysql replicas: 1 template: metadata: labels: app: mysql spec: containers: - name: mysql image: docker.m.daocloud.io/mysql:8.0.36 ports: - containerPort: 3306 env: - name: MYSQL_ROOT_PASSWORD value: "oneapimmysql" - name: MYSQL_DATABASE value: "oneapi" volumeMounts: - mountPath: /var/lib/mysql name: mysql-pvc resources: limits: cpu: 500m memory: 512Mi requests: cpu: 250m memory: 512Mi volumes: - name: mysql-pvc persistentVolumeClaim: claimName: mysql-pvc --- apiVersion: v1 kind: Service metadata: name: mysql namespace: fastgpt spec: ports: - port: 3306 selector: app: mysql三个组件 apply 完之后,用kubectl get pod -n fastgpt确认都是 Running。MongoDB 首次启动会慢一点,因为要生成 keyfile 和初始化副本集,等 30 秒左右正常。
4. FastGPT 主进程与 sandbox 的 ConfigMap 挂载
FastGPT 分两个 Deployment:主进程和 sandbox。sandbox 负责执行代码块,主进程负责 API 和前端。主进程需要挂一个 config.json,里面定义可用模型列表。
# fastgpt.yaml apiVersion: apps/v1 kind: Deployment metadata: name: sandbox namespace: fastgpt spec: selector: matchLabels: app: sandbox replicas: 1 template: metadata: labels: app: sandbox spec: containers: - name: sandbox image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.3 ports: - containerPort: 3000 resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 250m memory: 512Mi --- apiVersion: v1 kind: Service metadata: name: sandbox namespace: fastgpt spec: ports: - port: 3000 selector: app: sandbox --- apiVersion: apps/v1 kind: Deployment metadata: name: fastgpt namespace: fastgpt spec: selector: matchLabels: app: fastgpt replicas: 1 template: metadata: labels: app: fastgpt spec: containers: - name: fastgpt image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.3 ports: - containerPort: 3000 env: - name: DEFAULT_ROOT_PSW value: "1234" - name: OPENAI_BASE_URL value: "http://oneapi.fastgpt.svc.cluster.local:3001/v1" - name: CHAT_API_KEY value: "sk-你的OneAPI令牌" - name: DB_MAX_LINK value: "30" - name: TOKEN_KEY value: "any" - name: ROOT_KEY value: "root_key" - name: FILE_TOKEN_KEY value: "filetoken" - name: MONGODB_URI value: "mongodb://root:123456@mongo:27017/fastgpt?authSource=admin&replicaSet=rs0" - name: PG_URL value: "postgresql://username:password@pg:5432/postgres" - name: SANDBOX_URL value: "http://sandbox.fastgpt.svc.cluster.local:3000" volumeMounts: - mountPath: /app/data/config.json name: config-volume subPath: config.json resources: limits: cpu: 1000m memory: 1024Mi requests: cpu: 250m memory: 512Mi volumes: - name: config-volume configMap: name: fastgpt-config --- apiVersion: v1 kind: Service metadata: name: fastgpt namespace: fastgpt spec: type: NodePort ports: - port: 3000 selector: app: fastgpt --- apiVersion: v1 kind: ConfigMap metadata: name: fastgpt-config namespace: fastgpt data: config.json: | { "feConfigs": { "lafEnv": "https://laf.dev" }, "systemEnv": { "openapiPrefix": "fastgpt", "vectorMaxProcess": 15, "qaMaxProcess": 15, "pgHNSWEfSearch": 100 }, "llmModels": [ { "model": "qwen-max", "name": "qwen", "maxContext": 16000, "avatar": "/imgs/model/openai.svg", "maxResponse": 4000, "quoteMaxToken": 13000, "maxTemperature": 1.2, "charsPointsPrice": 0, "censor": false, "vision": false, "datasetProcess": true, "usedInClassify": true, "usedInExtractFields": true, "usedInToolCall": true, "usedInQueryExtension": true, "toolChoice": true, "functionCall": true, "defaultConfig": {} }, { "model": "gpt-4-0125-preview", "name": "gpt-4-turbo", "avatar": "/imgs/model/openai.svg", "maxContext": 125000, "maxResponse": 4000, "quoteMaxToken": 100000, "maxTemperature": 1.2, "charsPointsPrice": 0, "censor": false, "vision": false, "datasetProcess": false, "usedInClassify": true, "usedInExtractFields": true, "usedInToolCall": true, "usedInQueryExtension": true, "toolChoice": true, "functionCall": false, "defaultConfig": {} } ], "vectorModels": [ { "model": "text-embedding-ada-002", "name": "Embedding-2", "avatar": "/imgs/model/openai.svg", "charsPointsPrice": 0, "defaultToken": 512, "maxToken": 3000, "weight": 100, "dbConfig": {}, "queryConfig": {} } ], "reRankModels": [], "audioSpeechModels": [], "whisperModel": {} }这里的关键改动是把OPENAI_BASE_URL指向 One-API 的集群内地址。注意 Service 名和命名空间要对上,oneapi.fastgpt.svc.cluster.local:3001里的 3001 是 Service 端口,不是容器端口。CHAT_API_KEY填 One-API 里生成的令牌,后面接 TaoToken 时会重新生成。
apply 之后等 Pod 起来,用kubectl logs -n fastgpt deploy/fastgpt看日志。如果看到connect to mongo相关报错,检查 MONGODB_URI 里有没有带replicaSet=rs0。
5. 把模型 Base URL 切到 TaoToken 并验证端到端连通
前面 One-API 是空壳,还没配渠道。现在把模型调用指向 TaoToken,让 One-API 转发过去。TaoToken 提供统一的 OpenAI 兼容接口,Base URL 是https://taotoken.net/api,模型 ID 按需选。
5.1 在 One-API 里加渠道
浏览器打开http://<节点IP>:<NodePort>,用root/123456登录(One-API 默认账号)。进「渠道」→「添加渠道」:
| 字段 | 值 |
|---|---|
| 类型 | OpenAI |
| 名称 | taotoken |
| 分组 | default |
| 模型 | qwen-max,gpt-4-0125-preview,text-embedding-ada-002 |
| 代理 | 留空 |
| 密钥 | 你的 TaoToken API Key |
| Base URL | https://taotoken.net/api |
保存后点「测试」,返回绿色即通。然后去「令牌」页新建一个令牌,复制出来,这就是 FastGPT 要用的CHAT_API_KEY。
5.2 更新 FastGPT 环境变量
把fastgpt.yaml里的CHAT_API_KEY换成新令牌,重新 apply:
kubectl apply -f fastgpt.yaml kubectl rollout restart deploy/fastgpt -n fastgpt5.3 用 curl 验证端到端
最直接的验证方式是绕过前端,直接打 FastGPT 的 OpenAI 兼容接口。先拿 NodePort:
kubectl get svc fastgpt -n fastgpt假设 NodePort 是 31234,节点 IP 是 192.168.1.100,发一个对话请求:
curl -X POST http://192.168.1.100:31234/api/v1/chat/completions \ -H "Authorization: Bearer 你的FastGPT令牌" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-max", "messages": [{"role": "user", "content": "用一句话说明K8s是什么"}], "stream": false }'返回里如果有choices[0].message.content,说明 FastGPT → One-API → TaoToken → 模型 这条链路全通了。如果返回 401,检查 FastGPT 令牌;如果返回local proxy failed,多半是 One-API 的 Base URL 写错或网络不通。
5.4 前端对话验证
浏览器打开 FastGPT 前端,用root/1234登录,新建一个应用,选qwen-max模型,直接对话。能正常返回内容,整个部署就完成了。
6. 部署过程中最容易踩的五个坑
坑一:MongoDB 副本集没初始化。表现是 FastGPT 日志报NotPrimaryNoSecondaryOk。原因是 command 里的rs.initiate没执行成功。排查方法:kubectl exec -it deploy/mongo -n fastgpt -- mongo -u root -p 123456 --authenticationDatabase admin --eval "rs.status()",看 state 是不是 PRIMARY。
坑二:PVC 一直 Pending。多半是 volumeName 和 PV 名字对不上,或者节点上 hostPath 目录不存在。kubectl describe pvc -n fastgpt看 Events 里有没有no volume plugin matched。
坑三:One-API 报reading choices错误。这是上游返回格式不对,通常是 Base URL 少了/v1或者模型 ID 写错。TaoToken 的 Base URL 是https://taotoken.net/api,One-API 会自动补/v1,所以填的时候不要重复加。
坑四:FastGPT 连不上 sandbox。表现是代码块执行超时。检查SANDBOX_URL是不是http://sandbox.fastgpt.svc.cluster.local:3000,命名空间和 Service 名都要对。
坑五:401 Unauthorized。分两种:FastGPT 前端登录失败是密码不对,默认1234;API 调用 401 是令牌不对,去 One-API 重新生成一个,确保 FastGPT 的CHAT_API_KEY和 One-API 令牌一致。
排障时优先看 Pod 日志,kubectl logs -n fastgpt deploy/xxx --tail=100,大部分问题日志里都有线索。如果涉及 One-API 渠道配置,去 TaoToken 控制台确认 Key 状态和余额,再回来对 Base URL。
整套跑下来,本地 K8s 集群里就有一套完整的 FastGPT 知识库问答系统了。模型调用统一走 TaoToken,换模型只改 One-API 渠道,FastGPT 本身不用动。需要看模型列表或调试接口,可以去 TaoToken 模型对话 直接试;要生成和管理 API Key,在 API Keys 页面 操作;接入细节查 接入文档。如果后面要长期跑编码类 Agent,可以看看 Coding Plan,按用量走更省心。