3.8 部署结构:从 Docker Compose 到 K8s
学习目标
完成本节后,你将能够:
- 从部署视角复述 Langfuse 的运行组件:
web、worker、Postgres、ClickHouse、Redis/Valkey、S3/blob。 - 说清 Docker Compose 本地栈和 Kubernetes Helm 栈之间的对象映射。
- 理解为什么生产部署不能只把 compose 翻译成几个 Pod,还要补迁移、扩缩容、备份和状态组件治理。
3.8.1 先给结论:部署结构不是代码目录结构
从源码目录看,Langfuse 主要是:
web/ + worker/ + packages/shared/从部署视角看,它是:
Langfuse Web + Langfuse Worker
+ Postgres
+ ClickHouse
+ Redis / Valkey
+ S3 / Blob Storage也就是说,部署时真正要回答的不是“哪些目录跑起来”,而是:
| 问题 | 对应部署组件 |
|---|---|
| 谁接浏览器、SDK、Public API 和 ingestion 请求? | langfuse-web |
| 谁消费异步任务、批量写入、跑后台作业? | langfuse-worker |
| 组织、项目、用户、dataset、prompt、配置放哪里? | Postgres |
| trace、observation、score、events 的分析查询放哪里? | ClickHouse |
| ingestion job、缓存、锁、限流、queue metrics 放哪里? | Redis / Valkey |
| raw event、大文件、media、export 放哪里? | S3 / Blob Storage,local 用 MinIO |
本地 Docker Compose 把这些组件压到一台机器和一个 Docker network 里。Kubernetes 则把它们拆成 Deployment、Service、Ingress、Stateful dependency、Secret、HPA/PDB/KEDA 等对象。
3.8.2 Local Docker Compose 是什么结构
完整本地自托管入口是 docker-compose.yml。它起六类服务:
| compose service | 镜像或角色 | 部署职责 |
|---|---|---|
langfuse-web | langfuse/langfuse:3 | UI、tRPC、Public API、ingestion HTTP 入口。 |
langfuse-worker | langfuse/langfuse-worker:3 | BullMQ consumer、batch writer、eval/export/delete 等后台任务。 |
postgres | PostgreSQL | 事务型产品状态。 |
clickhouse | ClickHouse Server | tracing/eval/score/events 分析状态。 |
redis | Redis | 队列、缓存、锁、限流。 |
minio | S3-compatible object storage | raw event、media、batch export。 |
本地 compose 拓扑可以这样读:
读这张图时注意两点:
web和worker都需要连接 Postgres、ClickHouse、Redis、S3,但职责不同。web是同步入口,worker是异步执行器。- ingestion 高吞吐路径不是
web -> ClickHouse直写,而是web -> S3 raw event + Redis job -> worker -> ClickHouse。
开发测试时还有一个更轻的 docker-compose.dev.yml。它通常只起 Postgres、ClickHouse、Redis、MinIO,web 和 worker 由源码命令启动。这更适合本地改代码,因为你可以用 pnpm run dev:web、pnpm run dev:worker 看日志和热更新。
3.8.3 Compose 里的连接契约
Compose 的核心不是 depends_on,而是环境变量连接契约。
| 契约 | 示例变量 | 谁使用 |
|---|---|---|
| Postgres | DATABASE_URL | web、worker |
| ClickHouse HTTP 查询 | CLICKHOUSE_URL | web、worker |
| ClickHouse migration | CLICKHOUSE_MIGRATION_URL | 主要由 web entrypoint 跑迁移时使用 |
| Redis | REDIS_HOST、REDIS_PORT、REDIS_AUTH | web、worker |
| S3 raw event | LANGFUSE_S3_EVENT_UPLOAD_* | web 写,worker 读 |
| S3 media | LANGFUSE_S3_MEDIA_UPLOAD_* | web 生成上传/下载 URL,产品功能读取 |
| S3 export | LANGFUSE_S3_BATCH_EXPORT_* | worker 写 export,web 提供下载入口 |
这些变量在 compose 和 K8s 之间保持同一套语义。K8s Helm chart 做的事情,本质上是把这些连接参数从 docker-compose.yml 的 env block,搬到 values.yaml、Secret 和模板 helper 里。
3.8.4 K8s Helm 是什么结构
langfuse-k8s chart 的应用层是:
Deployment/langfuse-web
Service/langfuse-web
Ingress/langfuse-web 可选
Deployment/langfuse-worker
Secret / ServiceAccount / HPA / KEDA ScaledObject / PDB / VPA 可选数据层默认由 chart dependency 拉起:
PostgreSQL subchart
ClickHouse subchart
Valkey subchart,作为 Redis 兼容层
MinIO subchart,作为 S3 兼容层生产环境也可以把这些 dependency 关掉,改接外部托管服务:
postgresql:
deploy: false
host: "<managed-postgres-host>"
clickhouse:
deploy: false
host: "<managed-clickhouse-host>"
redis:
deploy: false
host: "<managed-redis-host>"
s3:
deploy: false
bucket: "<bucket-name>"从 K8s 资源视角看:
这张图和 compose 图看起来很像,但语义不一样:
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 应用进程 | 一个 service 通常一个容器实例 | Deployment 管理多个 Pod 副本 |
| 服务发现 | Docker network service name | Kubernetes Service DNS |
| 对外入口 | host port,例如 3000:3000 | Ingress / LoadBalancer / port-forward |
| 配置 | env block / .env | values.yaml、Secret、ConfigMap、envFrom |
| 健康检查 | compose healthcheck | livenessProbe / readinessProbe |
| 扩缩容 | 手动改 replicas 或另起容器 | HPA / KEDA / VPA |
| 状态持久化 | local volume | PVC、托管数据库、对象存储 |
| 发布 | docker compose pull && up | Helm upgrade / rollout |
3.8.5 Helm values 如何映射到应用环境变量
Chart 的 values.yaml 并不是应用代码的新配置系统。它只是把部署层参数翻译成应用容器认识的环境变量。
对应关系是:
| Helm values | 生成的应用配置 | 说明 |
|---|---|---|
postgresql.host、postgresql.auth.* | DATABASE_HOST、DATABASE_USERNAME、DATABASE_PASSWORD、DATABASE_NAME | entrypoint 会拼出 DATABASE_URL。 |
postgresql.directUrl | DIRECT_URL | 用于 migration,可绕开 pooler。 |
postgresql.migration.autoMigrate | LANGFUSE_AUTO_POSTGRES_MIGRATION_DISABLED | 控制是否自动跑 Prisma migration。 |
clickhouse.host、clickhouse.httpPort | CLICKHOUSE_URL | 应用读写 ClickHouse HTTP endpoint。 |
clickhouse.migration.url | CLICKHOUSE_MIGRATION_URL | ClickHouse migration 使用 native/TCP endpoint。 |
clickhouse.clusterEnabled | CLICKHOUSE_CLUSTER_ENABLED | 控制 migration 是否 ON CLUSTER。 |
redis.host、redis.auth.* | REDIS_HOST / REDIS_CONNECTION_STRING 相关 env | 连接 BullMQ 和缓存。 |
s3.eventUpload.* | LANGFUSE_S3_EVENT_UPLOAD_* | ingestion raw event bucket。 |
s3.mediaUpload.* | LANGFUSE_S3_MEDIA_UPLOAD_* | media upload bucket。 |
s3.batchExport.* | LANGFUSE_S3_BATCH_EXPORT_* | batch export bucket。 |
所以读 Helm chart 时,不要把 values.yaml 当成业务抽象。真正的业务抽象仍在 web、worker、packages/shared;values.yaml 是部署层把外部基础设施接入这些抽象。
3.8.6 迁移:Compose 方便,K8s 要小心
web 镜像的 entrypoint 会做两类迁移:
- Postgres:执行 cleanup SQL,再跑 Prisma migration。
- ClickHouse:执行
packages/shared/clickhouse/scripts/up.sh。
这在 compose 里很方便,因为通常只有一个 langfuse-web 实例。
在 K8s 里,如果 langfuse-web 有多个副本,滚动发布时就可能出现多个新 Pod 同时启动、同时尝试迁移。迁移脚本一般会尽量幂等,但生产上仍然应该把“应用启动”和“schema 变更”分开治理。
推荐心智模型:
对初学者来说,这里最重要的不是记住某个 Helm hook 写法,而是理解边界:
| 阶段 | 应该做什么 | 不应该混在一起的原因 |
|---|---|---|
| migration | 改 Postgres / ClickHouse schema | 需要单次、有序、可回滚或可人工介入。 |
| web rollout | 替换同步入口实例 | 应该快速、可并行、可健康检查。 |
| worker rollout | 替换后台 consumer | 还要考虑旧 job payload、新旧版本兼容和重试。 |
如果要做更稳的生产部署,可以关闭自动迁移,把迁移放到单独 Job 或发布流水线里,再启动多副本 web/worker。
3.8.7 从能跑到生产,还差什么
K8s 不是“换个容器编排器”就自动生产可用。差距主要在下面几类治理能力。
1. 副本和高可用
最低限度:
| 组件 | POC | 更接近生产 |
|---|---|---|
web | 1 replica | 至少 2 replicas,配 readinessProbe 和 PDB。 |
worker | 1 replica | 按 CPU 或 queue length 扩容。 |
| Postgres | chart standalone 或外部单实例 | 托管 Postgres 或有备份/故障恢复的 HA。 |
| ClickHouse | 单节点也能跑 | 单 shard 多 replica,或 ClickHouse Cloud/BYOC。 |
| Redis/Valkey | standalone | 高负载时用 cluster/sentinel/托管实例。 |
| S3/blob | MinIO | 托管对象存储或有备份和生命周期策略的对象存储。 |
2. 扩缩容指标要按链路拆
web 和 worker 的瓶颈不一样:
| 部署对象 | 主要压力 | 常见扩容信号 |
|---|---|---|
web UI/API | HTTP 并发、ingestion payload upload、API auth/cache | CPU、内存、请求延迟、5xx、S3 socket backlog。 |
worker | BullMQ job 消费、S3 下载、ClickHouse batch insert、eval/export | CPU、queue length、job wait time、failed/stalled count。 |
| ClickHouse | insert、merge、filter/query、dashboard/API reads | query latency、merge backlog、disk、CPU、memory。 |
| Redis | queue operations、cache lookup、locks、sharded queue | CPU、memory、queue length、command latency。 |
高 ingestion 负载时,可以把 langfuse-web 拆成两个 Deployment:
这不是源码层的新服务,而是部署层把同一个 langfuse-web 镜像按流量类型复制一份,用 Ingress path routing 隔离 UI 和 ingestion 压力。
3. 状态组件要有备份和生命周期策略
Compose 的 local volume 只适合本地实验。生产要分别回答:
| 状态组件 | 必须回答的问题 |
|---|---|
| Postgres | 备份频率、恢复演练、连接池、migration 用户权限、UTC 时区。 |
| ClickHouse | replica 数、PVC 大小、磁盘扩容、TTL、系统日志表、读写隔离。 |
| Redis/Valkey | noeviction、CPU、memory、queue shard、持久化需求。 |
| S3/blob | bucket 权限、生命周期、media 不应随意过期、跨 VPC endpoint、presigned URL 可达性。 |
Langfuse 的“事实数据”不在一个地方。丢 Postgres 会丢产品控制面,丢 ClickHouse 会丢 tracing 分析事实,丢 S3 raw event 会影响重放、media 和 export,丢 Redis 会影响队列和短期缓存。
4. Secret 和配置要脱离 values 明文
教程里可以为了演示把密码写进 values.yaml,但生产不要这样做。
更稳的方式:
SALT、ENCRYPTION_KEY、NEXTAUTH_SECRET使用 Kubernetes Secret 或 External Secrets。- 数据库、Redis、S3 密码使用 secret reference。
- 应用公共配置和敏感配置分开管理。
- 不把真实 secret commit 到 GitOps 仓库。
5. 网络边界要明确
Compose 里靠 127.0.0.1 限制端口暴露。K8s 里要换成:
| 边界 | K8s 对应做法 |
|---|---|
| 用户只访问 web | Ingress 只暴露 langfuse-web。 |
| 数据库不暴露公网 | ClusterIP / private endpoint / security group。 |
| Pod 间访问受控 | NetworkPolicy。 |
| S3 endpoint 可达 | VPC endpoint、DNS、presigned URL 外部 endpoint。 |
| 管理控制面 | 限制 Helm/Kubectl 权限和 namespace 范围。 |
3.8.8 最小 K8s 学习路径
如果你只是为了理解架构,不要一上来陷入所有 Helm values。建议按这个顺序读:
charts/langfuse/Chart.yaml:看 dependency,确认哪些状态组件被 chart 拉起来。charts/langfuse/values.yaml:看langfuse.web、langfuse.worker、postgresql、clickhouse、redis、s3六块。templates/web/deployment.yaml:看 web container env、probe、service port。templates/worker/deployment.yaml:看 worker container env、probe、port。templates/_helpers.tpl:看 values 如何变成DATABASE_*、CLICKHOUSE_*、REDIS_*、LANGFUSE_S3_*。- 回到
docker-compose.yml对照同名环境变量,确认 K8s 不是新系统,而是同一套运行契约的生产编排版本。
源码锚点:
| 目标 | 文件 |
|---|---|
| 本地完整部署 | docker-compose.yml |
| 本地依赖栈 | docker-compose.dev.yml |
| web 镜像和迁移入口 | web/Dockerfile、web/entrypoint.sh |
| worker 镜像入口 | worker/Dockerfile、worker/entrypoint.sh |
| Helm chart 依赖 | ../langfuse-k8s/charts/langfuse/Chart.yaml |
| Helm values | ../langfuse-k8s/charts/langfuse/values.yaml |
| web Deployment | ../langfuse-k8s/charts/langfuse/templates/web/deployment.yaml |
| worker Deployment | ../langfuse-k8s/charts/langfuse/templates/worker/deployment.yaml |
| env helper | ../langfuse-k8s/charts/langfuse/templates/_helpers.tpl |
| 官方部署文档 | ../langfuse-docs/content/self-hosting/deployment/kubernetes-helm.mdx |
| 官方 scaling 文档 | ../langfuse-docs/content/self-hosting/configuration/scaling.mdx |
3.8.9 读完后应该形成的判断
看到一个 Langfuse 部署问题时,先把它放进这张表:
| 问题 | 优先检查 |
|---|---|
| UI 慢、API 慢 | web CPU/内存、ClickHouse 查询、Postgres 连接、是否缺时间/project filter。 |
| ingestion 延迟大 | Redis queue length、worker CPU、S3 写入、ClickHouse insert/merge。 |
| worker 重试多 | S3/ClickHouse/Postgres 错误、job payload 兼容、lock/stalled 设置。 |
| 发布后启动失败 | migration、Secret、数据库连接、ClickHouse migration URL。 |
| 数据查不到 | ClickHouse 写入、worker 是否消费、project/time filter、events_core/events_full 投影。 |
| media/export 失败 | S3 bucket、endpoint、presigned external endpoint、权限。 |
| 升级有风险 | schema migration、旧 queue job、ClickHouse migration、feature flag。 |
部署架构的本质是把第 3.5 节的数据设施、第 4.4 节 ingestion 链路、第 4.5 节 worker 队列和第 5 篇契约放到真实运行环境里。K8s 只是承载形式;真正需要学会的是:哪些组件是无状态、哪些组件是事实来源、哪些路径可以重试、哪些变更必须单独迁移。