ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Langfuse离线部署实录:内网环境下的大模型可观测平台实践

2026/10/6 4:02:54 拓冰建站 浏览量
Langfuse离线部署实录:内网环境下的大模型可观测平台实践 Langfuse离线部署实录没有外网的服务器上我是怎么把大模型可观测平台跑起来的接到这个需求是在一个保密要求极高的内网项目上。客户那边的大模型应用已经上线了小半年但一直缺一个能看推理链路、统计Token消耗、做Prompt版本管理的平台。选型阶段对比了LangSmith、MLflow、Helmholtz最后敲定Langfuse——开源、功能贴合、社区活跃。问题随之而来部署环境是物理隔离的网络连Docker Hub都访问不了官方文档里那句docker compose up -d根本用不上。整个部署过程断断续续折腾了三天踩了不少坑也积累了一套完整的离线部署流程。这篇文章就把整个思路和操作步骤完整记录下来。如果你也在做大模型应用的运维或交付需要在隔离网络、内网机房或者专有云环境里部署Langfuse这篇文章应该能帮你少走很多弯路。1. 先想清楚离线部署到底要准备什么很多人在离线部署上栽跟头不是因为操作复杂而是因为第一步就没想清楚到底要搬哪些东西。Langfuse不是一个单体应用它是一组互相依赖的服务集合离线部署的本质就是把这一整套依赖完整地搬进内网。1.1 Langfuse的组件架构与离线部署的关键依赖Langfuse的官方部署方案基于Docker Compose最新版本的核心组件包括Langfuse Web应用基于Next.js的前端和API服务提供控制台、数据查询、评测等能力。Langfuse Worker异步任务消费者负责处理日志写入、数据聚合、导出任务等。PostgreSQL主数据库存储用户、项目、Prompt、观测事件等结构化数据。ClickHouse分析型数据库专门存放海量的trace和observation数据支撑高性能检索和聚合。Redis作为缓存和消息队列连接Web应用和Worker。MinIO可选但强烈建议S3兼容的对象存储用于存放导出文件、附件、数据集等。这六个组件构成了Langfuse的完整运行时。离线部署的核心难点就是把对应的Docker镜像和依赖包全部准备好。官方还在持续推进的版本可能会引入新的依赖组件但截至目前上述六件套就是全部。注意ClickHouse和PostgreSQL都各有版本要求Langfuse对数据库的版本比较敏感尤其是PostgreSQL建议直接使用官方docker-compose.yml中lock定的版本不要随意升级。1.2 为什么不能只打包应用镜像我第一次尝试离线部署时犯过一个典型错误只把langfuse/langfuse和langfuse/langfuse-worker两个镜像导出带进内网。结果启动后API服务一直报错日志里是数据库连接失败。这才意识到整个系统的运行依赖远不止应用本身。Langfuse的Web应用启动时需要连接PostgreSQL完成Schema校验和数据访问Worker则需要从Redis拉取任务并写入ClickHouse。任何一个基础组件缺失应用都无法正常工作。更麻烦的是Web应用和Worker间通过Redis的队列机制协作没有Redis整个异步处理链路直接瘫痪。所以离线部署的第一个原则是要把完整的运行时依赖一起打包而不是只搬应用本体。这也是很多开源系统离线部署的共同特点——依赖链是一个整体缺一环就全盘皆输。1.3 网络环境的判断你需要什么样的准备机离线部署通常需要一个准备机——一台能访问外网的机器用来拉取镜像和依赖包然后把产物通过移动介质、跳板机或审批通道传入内网。准备机可以是你的办公电脑、一台云上的跳板机或者客户的临时演示环境。关键要求只有三个能访问Docker Hub或已配置的镜像仓库有足够的磁盘空间建议至少预留20GB镜像和临时文件比较多装了Docker和docker compose插件传输方式上我见过有人用U盘拷贝tar包也见过通过审批后的FTP通道上传还有人用内网自建的Harbor作为中转。无论哪种方式核心都是两个动作docker save导出和docker load导入。后面我会给出完整命令。2. 镜像的批量获取与运输唯一硬卡点在这里离线部署真正有技术含量的环节就是镜像获取。这步处理得好后续基本就是顺水推舟处理不好可能在传输环节就卡死。2.1 获取完整的镜像清单我建议直接在联网机器上拉取官方docker-compose.yml然后从中提取镜像列表。这样做的好处是版本完全对齐不会出现自己随便配的版本和官方不兼容的情况。实际操作如下# 克隆或下载官方部署仓库任选其一 git clone https://github.com/langfuse/langfuse.git # 或者直接下载 docker-compose.yml wget https://raw.githubusercontent.com/langfuse/langfuse/main/docker-compose.yml # 查看文件中所有镜像的定义 grep -E ^\simage: docker-compose.yml以当前较新的版本为例镜像清单大致是组件镜像名用途Weblangfuse/langfuse:2.x主应用前后端Workerlangfuse/langfuse-worker:2.x异步任务处理PostgreSQLpostgres:15.x主数据库ClickHouseclickhouse/clickhouse-server:24.x分析数据库Redisredis:7.x缓存与队列MinIOminio/minio:latest对象存储有个细节需要注意langfuse/langfuse和langfuse/langfuse-worker的版本号必须一致否则可能出现API和Worker之间数据格式不兼容的问题。2.2 拉取、打包、导出的完整操作流程在准备机上执行# 1. 先拉取所有镜像逐条执行 docker pull langfuse/langfuse:2.29.0 docker pull langfuse/langfuse-worker:2.29.0 docker pull postgres:15.6 docker pull clickhouse/clickhouse-server:24.3.3.102 docker pull redis:7.2.4 docker pull minio/minio:RELEASE.2024-06-13T22-53-52Z # 2. 使用 docker save 批量导出 # 建议用tar归档再压缩体积能小不少 docker save \ langfuse/langfuse:2.29.0 \ langfuse/langfuse-worker:2.29.0 \ postgres:15.6 \ clickhouse/clickhouse-server:24.3.3.102 \ redis:7.2.4 \ minio/minio:RELEASE.2024-06-13T22-53-52Z \ -o langfuse-images.tar # 3. 压缩传输包 gzip langfuse-images.tar # 得到 langfuse-images.tar.gz此时可以传入内网如果单个压缩包太大超出了传输介质的限制可以用split命令分卷# 按500MB分卷 split -b 500M -d langfuse-images.tar.gz langfuse-part- # 内网端合并 cat langfuse-part-* langfuse-images.tar.gz这个过程中我踩过一个比较典型的坑docker save不加-o参数时会把镜像流输出到stdout如果终端环境有编码转换很可能导致tar包损坏。所以务必使用-o指定输出文件。2.3 内网机器导入镜像到内网目标机器后执行# 解压 gunzip langfuse-images.tar.gz # 导入镜像耐心等待这一步会有点久取决于服务器磁盘性能 docker load -i langfuse-images.tar # 验证导入结果 docker imagesdocker load完成后会逐条输出Loaded image信息。建议核对一下镜像名和tag是否齐全尤其是ClickHouse的镜像名带官方命名空间clickhouse/clickhouse-server漏了或拼错了后面起容器时会报image not found。2.4 拉取镜像失败的两个替代方案内网环境除了完全隔离还有一种常见情况是半隔离——容器运行时可以访问内网自建的镜像仓库。如果是这种情况可以先把镜像推送到内网Harbor或Nexus然后修改目标机器的Docker配置指向内网仓库。# 在准备机上打tag并推送 docker tag langfuse/langfuse:2.29.0 harbor.internal.com/library/langfuse:2.29.0 docker push harbor.internal.com/library/langfuse:2.29.0 # 在目标机上直接pull docker pull harbor.internal.com/library/langfuse:2.29.0还有一种情况更特殊那台准备机连Docker Hub也访问不了只能通过HTTP代理访问有限白名单域名。此时可以在/etc/docker/daemon.json里配置代理然后重启Docker{ proxies: { http-proxy: http://proxy.internal.com:8080, https-proxy: http://proxy.internal.com:8080 } }不过坦白说如果网络限制到这个程度通常走审批流程让人工传递tar包反而更快。离线部署本质上是绕过网络依赖而不是对抗网络策略选择最省力的路径才是正确的工程判断。3. 内网编排那些必须手工调整的配置镜像导入后工作重点就转移到编排文件上。直接拿官方docker-compose.yml用在内网环境大概率起不来。原因集中在几个方面环境变量缺失、组件间域名解析、外网遥测请求超时、以及数据库持久化配置。这里逐个说明。3.1 环境变量Langfuse跑起来的七个关键配置Langfuse的配置项非常多完整列表可以查官方文档。但内网部署只需要关注下面这几个它们直接决定服务能否启动配置项作用内网部署建议ENCRYPTION_KEY加密数据库中的敏感字段API密钥等必须设置长度32字节用openssl rand -base64 32生成SALT密码哈希的盐值必须设置同样用openssl rand -base64 32生成NEXTAUTH_URLNextAuth回调地址必须设置为内网访问地址如http://192.168.1.100:3000否则登录跳转异常NEXTAUTH_SECRETNextAuth会话签名密钥必须设置openssl rand -base64 32生成DATABASE_URLPostgreSQL连接串格式postgresql://postgres:passworddb:5432/postgresREDIS_URLRedis连接串格式redis://redis:6379CLICKHOUSE_URLClickHouse连接串格式http://clickhouse:8123TELEMETRY_ENABLED是否发送遥测数据内网必须设为false否则启动时会尝试外连生成密钥的具体命令openssl rand -base64 32 # 返回一串类似 xyz123abc456... 的字符串 openssl rand -hex 16 # 返回32位十六进制字符串用作SALT这里特别强调一下NEXTAUTH_URL。我遇到过最诡异的现象是所有容器都healthy但访问登录页面提交后一直报NEXTAUTH_URL相关的redirect错误。原因就是我在内网用IP访问但环境变量里写的是容器名或旧域名。在内网环境NEXTAUTH_URL必须填用户实际访问的地址——如果通过域名访问就填域名如果是IP访问就填IP。这是一条容易忽略但影响极大的配置。3.2 内网DNS与容器间通信Docker Compose默认会创建内部网络容器之间通过服务名互相解析。所以docker-compose.yml中依赖项连接串里的host名必须和Compose中的服务名一致——比如DATABASE_URL里的host写成db服务名就是dbREDIS_URL里写redis服务名就必须是redis。很多内网环境的路由和DNS策略比较严如果目标机器本身还跑着其他容器要注意端口冲突。官方配置默认映射3000端口Web应用8123端口ClickHouse HTTP接口9000端口MinIO API9001端口MinIO控制台如果这些端口已经被占用的需要改成宿主机的其他端口比如13000:3000。修改后NEXTAUTH_URL也要跟着改为http://内网IP:13000。3.3 持久化存储离线环境最怕重启丢数据官方docker-compose.yml里MinIO和PostgreSQL、ClickHouse都定义了volume。离线环境因为镜像无法轻易重新获取持久化更要谨慎。有三种选择使用volume推荐跟随Docker管理备份时用docker run --volumes-from导出。绑定挂载到宿主机目录最直观适合后续对接客户的备份机制目录肉眼可见。内网存储服务将数据目录挂载到NFS或其他共享存储上。我倾向于绑定挂载到宿主机目录尤其是在交付后需要移交给客户运维的场景。看着/data/langfuse/{postgres,clickhouse,redis,minio}这样清晰的目录结构接手的人心里踏实。绑定挂载只需把volume声明改为volumes: - /data/langfuse/postgres:/var/lib/postgresql/data - /data/langfuse/clickhouse:/var/lib/clickhouse - /data/langfuse/redis:/data - /data/langfuse/minio:/data提示ClickHouse的权限要求比较严格绑定挂载时需要确保宿主机目录属主是101:101ClickHouse容器内用户否则容器启动时可能报权限错误。最简单的办法先创建目录并chown为101:101。4. 第一次启动从迁移到健康检查的完整链路配置就绪后第一次启动是最容易出现问题的环节。Langfuse官方部署手册里有一句Run database migrations这一步很多人会漏掉或者不知道什么时候该执行。我梳理了一条完整的启动链路照这个顺序做基本不会翻车。4.1 数据库迁移必须先于应用启动Langfuse使用Prisma ORM管理PostgreSQL的Schema。新部署必须执行迁移命令否则Web应用启动后会一直报database is not in sync with the schema之类的错误。进入项目目录执行# 确保镜像已导入然后执行迁移 docker compose run --rm api db-migrate迁移命令会读取.env或compose配置中的DATABASE_URL自动创建表结构、索引和外键。注意这个命令执行完毕后PostgreSQL里已经有了完整的Schema后续再启动所有服务就不会报Schema问题了。如果在内网的数据库服务器上做了额外的安全策略比如限制了源IP迁移时会遇到连接超时。这种情况可以临时把网络策略加上Docker网段或者用--network host模式跑迁移命令docker run --rm --network host \ -e DATABASE_URLpostgresql://postgres:password宿主机IP:5432/postgres \ langfuse/langfuse:2.29.0 db-migrate4.2 组件启动顺序与实际依赖关系官方用depends_on定义了依赖顺序但depends_on默认只保证容器启动了并不保证容器内的服务就绪了。比如PostgreSQL容器已经进入运行状态但不代表5432端口已经能接受连接。稳妥的做法是启动后主动等待健康检查通过# 后台启动所有服务 docker compose up -d # 查看容器状态 docker compose ps # 等待片刻后检查健康状态STATUS列会显示healthy watch -n 2 docker compose ps官方镜像通常内置了HEALTHCHECK指令容器状态会从starting变为healthy。如果长时间停在starting或直接unhealthy优先看日志docker compose logs api docker compose logs worker4.3 ClickHouse的内存与日志问题ClickHouse是整组容器中资源占用最大的角色。默认配置下它可能会占用宿主机大量的内存对于只有8GB内存的服务器这可能造成其他容器被OOM杀掉。建议在compose文件中给ClickHouse加上内存限制clickhouse: image: clickhouse/clickhouse-server:24.3.3.102 ulimits: nofile: soft: 262144 hard: 262144 mem_limit: 4g同样Langfuse的Web应用也可以加mem_limit: 2g避免内存竞争导致整个Docker守护进程异常。ClickHouse的日志默认滚动策略比较保守长时间运行后会占据较多磁盘空间。建议在挂载的ClickHouse配置目录里放一个config.d/logger.xmlclickhouse logger levelwarning/level size100M/size count5/count /logger /clickhouse这个文件需要在启动前就放到挂载目录中否则容器内的配置不会自动生成。4.4 首次登录与功能验证清单所有容器healthy后在内网浏览器里访问http://内网IP:3000。首次访问会要求注册管理员账号注意这一步涉及发邮件确认——内网环境没有配置SMTP的话会出现邮箱验证失败之类的提示。Langfuse支持跳过邮件验证的配置AUTH_DISABLE_SIGNUP和AUTH_DISABLE_EMAIL_VALIDATION的组合。推荐在.env里设置AUTH_DISABLE_SIGNUPfalse AUTH_DISABLE_EMAIL_VALIDATIONtrue这样注册账号后不需要邮件验证就能直接登录适合内网交付初期快速验证。功能验证的核心检查项如下能正常创建Project并生成API密钥能通过SDKPython或JS向/api/public/traces写入一条测试数据控制台能看到写入的trace且Token消耗统计正确创建一个Prompt版本并发布确认Worker能同步处理# 用Python SDK快速验证内网机器上执行 pip install langfuse python -c from langfuse import Langfuse langfuse Langfuse( public_keypk-xxx, secret_keysk-xxx, hosthttp://内网IP:3000 ) langfuse.trace(nameoffline-test).update(outputok) langfuse.flush() 能在控制台看到这条offline-test的trace记录说明整条链路已经通了。5. 版本升级与日常运维离线环境下的后续管理部署成功只是开始更考验人的是后续的升级和维护。离线环境下没有拉新镜像的便捷路径凡事都得提前规划。5.1 离线升级的完整操作路径Langfuse迭代速度较快新版本经常带来新的观测功能和安全修复。离线升级的基本思路是准备机拉新包、导出、内网导入、重启。# 准备机拉取新版本镜像以2.31.0为例 docker pull langfuse/langfuse:2.31.0 docker pull langfuse/langfuse-worker:2.31.0 # 导出新镜像基础组件没变就不需要重新打包 docker save langfuse/langfuse:2.31.0 langfuse/langfuse-worker:2.31.0 -o langfuse-upgrade.tar # 内网机器导入新镜像 docker load -i langfuse-upgrade.tar # 在docker-compose.yml中修改镜像版本号 vim docker-compose.yml # 把 langfuse/langfuse:2.29.0 改为 langfuse/langfuse:2.31.0 # 把 langfuse/langfuse-worker:2.29.0 改为 langfuse/langfuse-worker:2.31.0 # 执行迁移升级通常伴随Schema变更 docker compose run --rm api db-migrate # 重建并启动 docker compose up -d升级前务必备份PostgreSQL和ClickHouse的数据目录。我通常在备份时直接停掉应用避免数据不一致docker compose stop api worker # PostgreSQL仍在运行此时用pg_dump备份 docker exec -i langfuse-db pg_dump -U postgres postgres backup_$(date %Y%m%d).sqlClickHouse的数据备份没有PostgreSQL那么方便最简单可靠的方式是直接打包数据目录tar czf clickhouse-backup.tar.gz -C /data/langfuse/clickhouse .5.2 日志采集与故障诊断的实用技巧离线环境没有外部的日志聚合服务但Langfuse运行过程中会产生几种关键日志必须知道去哪里看Web应用日志docker compose logs api排查登录、API请求、数据库连接问题Worker日志docker compose logs worker排查异步队列消费、ClickHouse写入问题PostgreSQL慢查询日志如果页面加载慢优先排查数据库一个比较推荐的定位技巧是在.env里把LOG_LEVEL设置为debug默认是info能输出SQL语句和API请求的详细信息。但注意生产环境不要长期开debug日志量会暴涨磁盘很快就满。5.3 备份策略与容灾安排离线环境的数据一旦丢失恢复成本比联网环境高得多。我的建议是至少做双层备份物理备份每天凌晨用tar打包PostgreSQL和ClickHouse的数据目录存到内网备份服务器。逻辑备份每周用pg_dump导出SQL文件用于应对物理文件损坏但数据库服务还能启动的情况。可以在宿主机上写一个简单的定时脚本#!/bin/bash # /opt/scripts/langfuse-backup.sh DATE$(date %Y%m%d_%H%M%S) BACKUP_DIR/data/backups/langfuse mkdir -p $BACKUP_DIR # 备份PostgreSQL docker compose -f /opt/langfuse/docker-compose.yml exec -T db \ pg_dump -U postgres postgres $BACKUP_DIR/postgres_$DATE.sql # 备份ClickHouse数据目录 tar czf $BACKUP_DIR/clickhouse_$DATE.tar.gz \ -C /data/langfuse/clickhouse . # 保留最近30天的备份 find $BACKUP_DIR -name *.sql -mtime 30 -delete find $BACKUP_DIR -name *.tar.gz -mtime 30 -delete然后加到crontab0 2 * * * /opt/scripts/langfuse-backup.sh5.4 内网时间同步的一个隐蔽坑这个问题我差点漏掉。Langfuse的Worker对任务有时间戳校验而ClickHouse内部也强依赖时间排序。如果内网服务器的时间与真实时间偏差过大比如超过几分钟可能出现trace写进去了但控制台查不到或者任务一直堆积不消费的怪象。检查方法timedatectl status # 确认NTP服务是否在运行 timedatectl show -p NTPSynchronized离线环境访问不了公网NTP服务需要在局域网内部搭建时间服务器或者手动校准所有机器的时间。最省心的做法是在内网找一台机器作为NTP服务器其他机器都指向它同步。具体搭建方式这里不展开但如果你的Langfuse出现数据写入无反应而容器都正常的情况先查时间同步这个排查顺序能省不少事。6. 部署完成后的几点经验总结整个流程走通之后回头看离线部署Langfuse的成败其实只取决于几个关键点**第一镜像版本必须统一。**应用和worker不一致、PostgreSQL版本不对、ClickHouse镜像名拼错这些都是最常见的坑。拉取前把完整清单列出来逐项核对再动手。第二环境变量里藏着80%的问题。NEXTAUTH_URL不对导致登录异常、TELEMETRY_ENABLED没关导致启动时外连超时、AUTH_DISABLE_EMAIL_VALIDATION没设导致注册卡壳。这些配置在官方文档里都有但内网环境把它们从可选项变成了必选项。**第三数据库迁移必须在应用启动前执行。**这一步漏掉后面所有容器都会陷入启动失败的重启循环而排查半天可能都想不到是Schema的问题。**第四离线环境的运维要更保守。**升级前必须完整备份改动配置前先导出当前docker compose config留底养成先备份再操作的习惯。我在实际交付中还有一个体会建议交付时随环境附上一份部署说明文档把镜像清单、环境变量清单、备份恢复步骤、常见故障排查都写清楚。这样后续客户运维团队接手时不至于手足无措。毕竟离线环境里出问题查资料都查不了文档就是唯一的救命稻草。如果后续你有机会接触到Langfuse的新版本建议优先关注它的评测功能和数据集管理模块——这两个方向在内部模型迭代场景下特别有用。离线部署的核心流程不会变变的无非是镜像版本和一些新增的环境变量掌握了这套方法论换任何版本都能快速上手。