最近老有人问我:"能不能在自己服务器上跑一个 Agent,数据完全不出内网?"这个问题我上个月帮客户做内部知识库问答机器人时也反复琢磨过。云端的大模型服务和现成的 Agent 平台确实省事,可企业内部的产品文档、客户信息、业务流程一旦要过外部接口,合规和保密就变成了绕不过去的硬门槛。评估了一圈之后,我把讯飞 Astron Agent 掘金版拖下来做了一轮完整的落地测试,用 Docker Compose 方式部署到了 Ubuntu 22.04 服务器上。
Astron Agent 掘金版简单来说,是讯飞给开发者放出来的一个可私有化部署的智能体运行平台,底层对话能力基于星火大模型,同时支持知识库、插件、任务编排和会话管理。用 Docker Compose 把整套环境拉起来之后,所有服务都在你自己的主机上跑,业务数据不会离开内网,属于完全可控的私有化 Agent 环境。我实际跑了一周,从机器选型到 compose 文件编写,再到最后用 Python 调通星火 API 做业务验证,中间的坑和排错思路全部记录在下面。文章里所有命令和配置都可以直接复制,适合自己搭 Agent 平台的开发者、运维,以及想在企业内网做 AI 落地的技术负责人参考。
1. Astron Agent 掘金版到底是什么,为什么值得私有化部署
1.1 定位:把 Agent 运行环境搬回自己家
先说说 Astron Agent 掘金版和普通 API 调用模式的区别。很多人用大模型的方式就是"调接口":把文本丢给星火或者其他模型服务,拿回一段回复就完事。但一个真正能落地的 Agent 不只是能聊天,它要能理解任务、拆解步骤、调用外部工具、检索知识库、记住上下文,甚至串联起一组自动化业务流程。Astron Agent 做的事情,就是把这一整套运行和编排环境打包起来,你部署好之后,它负责管理 Agent 的生命周期、任务调度、知识库索引、会话记录,而不是单纯给你当个模型转发代理。
"掘金版"这个命名容易让人误解成在线商业版的阉割版。从我拿到的版本看,它的核心能力和在线版同源,区别主要在于授权范围和打包方式。掘金版更适合三类人:第一,企业内部做 AI 原型的快速验证,因为数据不用出网,法务和合规基本不会卡你;第二,独立开发者在自己的服务器上做 Agent 实验,自己掌控服务运行状态;第三,想深度定制界面和流程的团队,私有化之后所有配置都在你手里,改起来没有平台限制。
1.2 部署架构:Docker Compose 需要协调哪几个角色
在动手写配置之前,先要搞清楚整套环境会拉起哪些容器。我自己实际验证下来的容器结构大致如下:
| 容器 | 职责定位 |
|---|---|
| astron-server | 主服务,负责 Agent 任务接收、编排、状态管理,是整套系统的大脑 |
| astron-web | 管理后台和用户界面,浏览器里配置 Agent 和知识库入口 |
| astron-worker | 异步任务执行模块,处理文档解析、批量检索、模型流式响应等耗时任务 |
| astron-redis | 缓存和消息队列,协调 server 和 worker 之间的任务分发 |
| astron-postgres | 元数据和会话记录存储,Agent 配置、历史记录、用户信息都在这里 |
除了这几个核心容器,如果要用到知识库的向量检索,通常还要在数据卷里挂一个向量索引服务,比如 pgvector 或者单独部署一套向量数据库,这个按需加装。理解了这个架构,再看下面 docker-compose.yml 就不会一头雾水,也不会出现"某个容器挂了不知道该看谁日志"的情况。
2. 环境准备阶段的三个关键选型
2.1 服务器规格怎么定才不浪费
第一坑往往不是配置问题,而是机器选小了。很多人觉得一个 Agent 平台能有多吃资源,拿 2C4G 的机器就想跑。实测下来,Astron Agent 的业务逻辑本身不算太吃 CPU,但 Docker 环境下同时跑服务端、Web、Redis、PostgreSQL,再加上知识库索引进程,内存一下子就紧张了。我建议的最低配置是 4 核 CPU、8GB 内存、40GB 可用磁盘,如果后续要挂比较大的知识库,磁盘至少留 100GB。操作系统方面,Ubuntu 22.04 LTS 和 Debian 12 都是实测下来比较稳的,老内核系统跑 Docker 经常在容器网络和存储驱动上出兼容问题,能用新系统一定换新系统。
提示:部署前先去服务器上执行
free -h和df -h,确认内存和磁盘真实余量,别只看云厂商控制台上标的数字。
2.2 Ubuntu 22.04 安装 Docker 的正确姿势
这一步看着基础,但坑很多。Ubuntu 22.04 自带 apt 源里的 docker.io 版本比较老,Compose 插件版本也跟不上,强烈建议用 Docker 官方源安装。我用的命令序列如下:
sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装完先别急着部署,执行docker compose version,看到版本号输出就说明 Compose 插件没问题。这里有个必须说清楚的点:新版 Docker 的命令是docker compose,空格连接,不是老版本的docker-compose(中间是横杠)。如果你在博客或者教程里看到横杠版本,先确认自己的 compose 插件版本,否则会一路报command not found。
2.3 镜像拉不下来的现实问题怎么解
用 Docker 部署最容易遇到的就是镜像拉取速度慢或者直接超时。这个问题有几条常规路径可以处理。
第一种是配置 Registry Mirror,也就是镜像加速器。在/etc/docker/daemon.json里加registry-mirrors配置,然后重启 Docker:sudo systemctl restart docker。要注意,不同网络环境对不同加速地址的连通性不一样,别盲从网上某一条配置,自己多试几个地址,看哪个实际速度能接受。
第二种是分步拉取。别一上来就docker compose up -d,那样一旦某个大镜像超时,整个组合全部失败,排查起来也不知道卡在哪。建议先手动docker pull小的基础镜像,确认网络和仓库连通性,再拉项目业务镜像。
第三种面向内网环境:把镜像在能联网的机器上docker pull下来,然后docker save打成 tar 包,传输到内网服务器后用docker load导入。这个方式在企业内网离线部署时特别实用,我这次部分镜像就是用的这个方案,稳定可控,不用赌外网连接质量。
3. 手写 docker-compose.yml 的完整过程
3.1 先说整体服务划分
不同来源的镜像标签可能不一样,这里我提供一个经过验证的配置骨架,重点在于讲清楚每个服务的职责和依赖关系,照抄时把镜像地址换成你实际使用的版本即可。compose 文件放在项目目录docker-astron/下,所有配置我习惯用缩进方式写,避免 YAML 解析出错。
3.2 核心配置逐段拆解
version: "3.8" services: postgres: image: postgres:15-alpine container_name: astron-postgres restart: unless-stopped environment: POSTGRES_USER: astron POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: astron volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U astron"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: astron-redis restart: unless-stopped command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru", "--appendonly", "yes"] volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 server: image: astron-agent/server:latest container_name: astron-server restart: unless-stopped env_file: .env depends_on: postgres: condition: service_healthy redis: condition: service_healthy ports: - "8080:8080" volumes: - ./data:/app/data worker: image: astron-agent/worker:latest container_name: astron-worker restart: unless-stopped env_file: .env depends_on: postgres: condition: service_healthy redis: condition: service_healthy web: image: astron-agent/web:latest container_name: astron-web restart: unless-stopped ports: - "3000:3000" depends_on: - server volumes: pgdata: redisdata:逐个解释几个关键点:
为什么用restart: unless-stopped而不是always。unless-stopped表示容器异常退出自动重启,但如果你手动停了它,Docker 不会在开机时强行拉起来。这个语义对运维更友好,服务器重启之后服务会自动恢复,但管理员手动停掉容器做维护时不会陷入"怎么停都停不掉"的尴尬。
为什么在 Redis 的 command 里直接写密码参数。这里很多人会踩坑,以为在environment里配了密码就不会生效。Redis 官方镜像读取密码的方式就是可以在启动命令里加--requirepass,也可以挂在配置文件中。直接用 command 参数把${REDIS_PASSWORD}传给 redis-server,简单直接,还能配合 healthcheck 里的redis-cli -a做健康检查。
为什么depends_on要配合condition: service_healthy。这是 compose 服务编排里最容易忽略的逻辑。光写depends_on只能保证容器启动顺序,不能保证依赖服务已经就绪,比如 PostgreSQL 容器起来了不代表数据库已经接受连接。加上 healthcheck 和condition: service_healthy,server 和 worker 就会等数据库和缓存真正健康后才开始启动,第一次跑起来基本不会报数据库连接失败。
3.3 环境变量和密钥的安全处理
.env文件需要手动创建,放在 compose 文件同目录下。内容如下:
SPARK_APP_ID=你的AppID SPARK_API_KEY=你的APIKey SPARK_API_SECRET=你的APISecret SPARK_API_URL=wss://spark-api.xf-yun.com/v3.5/chat SPARK_LLM_DOMAIN=generalv3.5 POSTGRES_PASSWORD=请换成强密码 REDIS_PASSWORD=请换成强密码 ASTRON_SERVER_PORT=8080 ASTRON_WEB_PORT=3000.env文件里存的是明文密钥,两个安全细节必须做到位:第一,文件权限改成 600,执行chmod 600 .env,避免其他系统用户直接读走密钥;第二,.env绝不能提交到 Git 仓库,项目里如果还没有.gitignore,赶紧加上一行.env。我看到过很多真实事故,就是密钥跟着仓库一起被推到公开代码库,后果非常麻烦。
env_file: .env和environment:的区别也要搞清楚。environment:是硬编码写进 compose 文件,适合放非敏感的固定参数;env_file则是把变量从外部文件读进来,密钥这种东西放外部.env更灵活,还可以在不同环境切换不同的配置,不用改 compose 文件本身。
4. 从启动到跑通的完整过程
4.1 启动前的检查清单
配置写完后不要急着 up,先把下面几项检查做完,能少折腾一小时。
- 端口占用:执行
ss -lntp | grep -E '8080|3000',确认这两个端口没被其他服务占用。如果被占了,要么改 compose 里的端口映射,要么先停掉占用进程。 - 磁盘空间:
df -h确认可用空间,至少大于 20GB,Docker 镜像和数据卷会快速占磁盘。 - 防火墙规则:执行
sudo ufw status,如果开启了防火墙,记得放行 3000 和 8080 端口,否则浏览器访问不到管理界面。 - 密钥是否填对:重新打开
.env,逐项核对 AppID、APIKey、APISecret 是不是真实有效的,不要用占位符。
4.2 第一次启动的完整命令流
cd docker-astron docker compose config这条命令会把 compose 文件结合.env展开成最终的配置并校验,如果有语法错误,会在这里直接暴露,不用等到容器启动后再排查。配置文件校验通过后,再执行:
docker compose up -d添加-d参数表示后台运行。第一次执行时会拉镜像,耗时取决于网络情况,可以加个--pull always强制拉取最新镜像。启动完成后用docker compose ps查看容器状态,正常情况所有容器应该是Up状态,并且有 healthcheck 的容器会显示(healthy)。
4.3 健康检查和初始化怎么确认
容器起来之后,先用docker compose logs -f server观察主服务日志,确认没有数据库连接错误和模型 API 鉴权报错。然后浏览器访问http://服务器IP:3000,第一次打开会进入管理员初始化页面,需要设置管理员账号密码,这一步做完之后才能真正创建 Agent。
Web 界面能打开只是第一步。我建议在初始化完成后创建一个小测试 Agent,不做知识库,先发一句"你好"看它能不能回复。这个测试能快速验证三条链路是否通畅:Web 到 server 的连通性、server 到星火大模型的 API 链路、以及整个编排流程是否正常。如果这一步就有问题,别急着往下走,先看第 5 章的错误排查。
5. 部署实测中绕不开的坑
5.1 "Cannot connect to the Docker daemon"完整排查
这个报错几乎是所有 Docker 部署新手都会撞上的墙,我在部署过程中也遇到过两次。报错信息长这样:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?完整排查链路如下:
第一步,确认 Docker 服务状态。执行systemctl status docker,如果显示inactive (dead),说明服务根本没起来。刚安装完 Docker 之后服务一般会自启,但有些瘦身系统镜像会关掉自动启动,需要手动执行:
sudo systemctl enable --now docker第二步,排查用户权限。如果服务是 active 状态,但普通用户执行 docker 命令还是报连接失败,那基本是 socket 权限问题。Docker 默认只允许 root 用户和 docker 组的成员访问/var/run/docker.sock。把当前用户加进 docker 组:
sudo usermod -aG docker $USER注意,加完组之后必须重新登录会话,光执行命令不重登不会生效,很多人卡在这一步。
第三步,看守护进程日志。如果服务启动失败,执行journalctl -u docker -n 50,最常见的故障点是 iptables 规则相关报错,一般可以通过sudo systemctl restart docker解决;如果系统里装了多个容器网络插件,可能需要清理残留的网桥或 iptables 规则。
5.2 Redis 容器化生产环境配置
Redis 这个容器看起来人畜无害,但如果你直接docker run redis裸奔上线,迟早出事。我在 compose 里专门给它加了几个生产参数,原因如下:
--requirepass是必须的。容器部署的 Redis 默认监听 0.0.0.0,在内网环境相当于对局域网所有人开放,不设密码等于把缓存数据(可能包含会话 token、检索结果)挂在门口。--maxmemory 512mb是为了防止内存失控。Redis 容器被打死最常见的场景就是某个任务触发超大检索,内存被占满,容器直接被内核 OOM kill。--maxmemory-policy allkeys-lru指定内存满了之后按 LRU 策略淘汰冷数据,保证服务不会因为缓存膨胀而崩溃。--appendonly yes开启 AOF 持久化,数据写到磁盘,容器重启后缓存不会全部丢光。
5.3 星火 API 超时和鉴权失败的定位
我在接入星火大模型时遇到两个典型问题,分别是超时和鉴权失败。超时的问题一般只出现在首次请求,前端界面发消息后转圈很久才出错。这时候先做连通性测试,看服务器能不能访问星火接口域名,执行:
curl -I https://spark-api.xf-yun.com如果请求卡住或者返回连接超时,说明服务器和星火接口之间的网络通道有问题。如果内网出口需要走代理,务必确认代理配置被正确注入到容器环境变量里,否则容器内的请求不会认宿主机的全局代理。
鉴权失败报错的代码通常是 404 或者 auth 相关错误。排错顺序是:先确认.env里的三把钥匙拼写和值完全正确,注意 AppID 和 APIKey 是两回事,别填反;然后确认SPARK_API_URL是不是对应版本的最新地址,星火不同版本(v1.1、v2.1、v3.5)的接口地址不一样,我把 v3.5 的地址写在了示例.env里;最后,如果你不是用官方 SDK 而是自己拼 WebSocket 请求,鉴权 URL 是基于 HMAC-SHA256 动态生成的,任何签名参数不一致都会导致 401 或 403,这种情况强烈建议直接用官方 SDK,别重复造轮子。
6. Python 调用星火 API 验证 Agent 效果
6.1 获取三把钥匙
星火 API 需要三样东西:AppID、APIKey、APISecret。登录讯飞开放平台,进入控制台,创建一个应用就能看到这三样。创建完成后把值填到.env文件里。这里再啰嗦一句:三把钥匙是敏感凭证,不要贴到代码仓库、别发到聊天群里,泄露了立刻去控制台重置。
6.2 Python 调用示例
Agent 后端调通星火大模型是整套系统正常工作的前提。用官方提供的 Python SDK,最简单的调用方式如下:
from sparkai.llm.llm import ChatSparkLLM from sparkai.core.messages import ChatMessage spark = ChatSparkLLM( spark_api_url="wss://spark-api.xf-yun.com/v3.5/chat", spark_app_id="你的AppID", spark_api_key="你的APIKey", spark_api_secret="你的APISecret", spark_llm_domain="generalv3.5", ) messages = [ChatMessage(role="user", content="请用一句话介绍你自己")] resp = spark.generate([messages]) print(resp.generations[0].text)依赖安装一般就是pip install sparkai,具体包名和类名以官方文档最新版本为准,接口签名不同版本可能有细微差异。这个示例的目的是帮你验证鉴权和连通性,如果 Python 能正常返回内容,说明星火链路通了,Astron Agent 收到用户消息后也能正常拿到模型响应。
6.3 把 Agent 接入自己的业务
Python 验证通过后,就要回到 Agent 平台本身去测它的编排能力了。在 Web 管理后台创建一个"内部知识库问答"Agent,上传几个内部文档建索引,然后通过 HTTP 接口调用 Astron Agent:
curl -X POST http://localhost:8080/api/v1/agent/chat \ -H "Content-Type: application/json" \ -d '{"agent_id":"你的AgentID","message":"公司产品线里退货率最高的是哪一类?"}'这个请求会先触发 Agent 检索知识库,再把检索结果和用户问题一起交给星火模型生成回答。如果之前没建知识库,模型只能靠自己的训练知识回答;建了知识库之后,回答会明显带上文档里的具体内容,那才是 Agent 真正在业务流程里起作用的状态。
7. 跑起来之后的维护建议
7.1 日志和磁盘占用控制
Docker 默认的日志驱动不限制文件大小,每个容器的 JSON 日志会无上限增长。我见过有人部署了一个容器,三个月后/var/lib/docker/containers下日志文件占了几十个 GB,直接把磁盘写满。要提前用日志限制配置,在/etc/docker/daemon.json里加上:
{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }改完执行sudo systemctl restart docker。注意,这个配置只对新创建容器生效,已存在的容器需要用docker compose up -d --force-recreate重建后才生效。
7.2 数据备份和恢复
Astron Agent 最重要的数据源是 PostgreSQL 和 Redis 两个数据卷。备份 PostgreSQL 数据卷可以用如下命令:
docker run --rm -v astron_pgdata:/data -v $(pwd):/backup alpine tar czf /backup/pgdata.tar.gz -C /data .这条命令的意思是用 alpine 容器挂载两个目录:一个是 PostgreSQL 的数据卷,一个是当前目录,然后把数据卷打成压缩包备份到当前目录。恢复就反过来解包:
docker run --rm -v astron_pgdata:/data -v $(pwd):/backup alpine tar xzf /backup/pgdata.tar.gz -C /data建议用 cron 定期执行备份任务,比如每天凌晨两点把 pgdata 和 redisdata 都打一份包,保留最近 7 天,成本低,真出问题的时候能救命。
7.3 版本升级注意事项
升级流程我建议严格按下面这个顺序走:先备份数据和配置文件,再执行docker compose pull拉取新镜像,然后docker compose up -d重建变化的容器,最后观察日志和回归测试。千万别跳步骤。特别是数据库结构变更的版本,官方升级说明里通常会标注"先备份再迁移",这句话不是客套话,我见过太多人因为跳过了备份,升级到一半数据结构不兼容,只能回滚到旧版镜像重新折腾。
升级后至少验证三件事:Web 界面能正常打开、已有 Agent 能被正常创建和查询、星火 API 链路能返回新消息。这三条过了,基本就可以放心使用了。注意我的升级顺序是先 pull 表现有服务,通过docker compose up只重建镜像发生变化的服务,而不是先手动docker stop全部容器再启动,那样会无端扩大故障窗口。
跑了一周之后,我最直观的体会是:私有化部署的价值不在于"把服务装进去"这个动作本身,而在于后续的数据归属、流程编排、权限控制全都握在自己手里。我也建议第一次部署的人别追求一次成功,把docker compose logs当成日常排错的入口,每做一步都记录下当时的环境和结果。这个项目其实还留了不少可玩的空间,比如把知识库换成更专业的企业文档管理工具,或者对接内部统一登录协议,如果你也正在部署 Astron Agent,欢迎在实际落地后多交流部署细节和踩坑经验。