Langfuse 加 DCGM:会话追踪和 GPU 显存、温度、利用率
目录
服务器内网:10.0.1.47 目录:/usr/cluster/observability Dify 的安装和工具追踪仍在 dify/用 Dify 做合同知识库:Qwen 对话、bge 重排和 Langfuse 工具追踪.md。New-API 那份文件没有改。
本文对应的程序:
| 文件 | 作用 |
|---|---|
scripts/01-部署Langfuse.sh | 拉取 Langfuse v4 并启动 |
scripts/02-部署GPU监控.sh | 启动 Prometheus、Grafana、DCGM |
langfuse/docker-compose.yml | Langfuse 编排,页面改到 3100,MinIO 改到 9092 |
langfuse/env-要点.txt | 现场核对过的环境项,不含密钥 |
metrics/docker-compose.yml | Prometheus、Grafana、DCGM 编排 |
metrics/prometheus.yml | 抓取本机、DCGM、vLLM |
metrics/grafana/provisioning/datasources/prometheus.yml | Grafana 数据源 |
metrics/grafana/provisioning/dashboards/dashboards.yml | 仪表盘目录 |
metrics/grafana/dashboards/gpu-vllm.json | 「GPU 与 vLLM」仪表盘 |
脚本已经在现网执行过。01 发现 langfuse-langfuse-web-1 已在会退出,避免重新生成 .env。02 发现 prometheus 已在会退出。Windows 上传后如果报 $'\r',先执行 sed -i 's/\r$//' 脚本名。不要把本文发到公网。密钥只在服务器 /usr/cluster/observability/langfuse/.env,权限 600。
一、安装配置
1. 目标
在已经运行的 vLLM、New-API、Dify 旁边加上两套观测,互不占用对方的端口:
浏览器
→ Langfuse :3100 会话追踪、token、费用
→ Grafana :3001 显存、温度、GPU 利用率、vLLM 请求
→ Prometheus :9090
→ DCGM Exporter :9400 读 GPU
→ vLLM :8000/metrics 读推理队列动手前已经在跑的服务:
| 服务 | 端口 | 处理 |
|---|---|---|
| New-API | 3000 | 不动。Langfuse 页面改到 3100 |
| Prometheus 预定 | 9090 | MinIO 改到 9092,把 9090 留给 Prometheus |
| 主机 Nginx | 80 | 不动 |
| Kubernetes | kubelet、calico、coredns | 不动 |
| vLLM | 8000 | 不重启。这版 vllm serve 没有 OTLP 参数,推理跨度不进 Langfuse |
Docker 数据盘在 /usr/cluster。根分区空间小,镜像和数据卷都放这里。DCGM 只读显卡,和机器上已有的 nvidia device plugin 共存。
2. 部署 Langfuse
版本是镜像标签 langfuse:4,健康检查返回 4.56.0。官方编排从 jsDelivr 取的 v4 compose。docker.langfuse.com 直连会超时,现场先从 docker.m.daocloud.io 拉取 langfuse、worker、ClickHouse、Postgres、Redis,再打回编排里的名字。MinIO 的镜像站被拒绝,用的是 docker.io/minio/minio:latest。
改过的地方:
- Web 端口
3100:3000 - MinIO 端口
9092:9000,健康检查改为curl http://127.0.0.1:9000/minio/health/live - ClickHouse、Redis、Postgres 不映射到宿主机
- ClickHouse 健康检查
start_period40 秒、重试 30 次 - Redis 健康检查使用
redis-cli -a "$$REDIS_AUTH" LANGFUSE_MIGRATION_V4_WRITE_MODE默认dual。不设这项时,v4 只收新事件,Dify 用的旧版 ingestion 会被拒绝
文件:scripts/01-部署Langfuse.sh
#!/bin/bash
# 已在 10.0.1.47 执行。Langfuse 页面在 3100,因为 3000 是 New-API。
# .env 已存在时不要重新生成。再跑本脚本发现容器已在会直接退出。
# 不启动、不重启 vLLM、New-API、Dify。
set -eu
if docker ps --format '{{.Names}}' | grep -qx 'langfuse-langfuse-web-1'; then
echo "Langfuse 已在运行,退出。"
exit 0
fi
if [ ! -f /usr/cluster/observability/langfuse/.env ]; then
echo "缺少 /usr/cluster/observability/langfuse/.env。不要另造一套密钥。"
exit 1
fi
# docker.langfuse.com 直连会超时。现场先从镜像站拉取,再打回编排里的名字。
# minio 的镜像站被拒绝,用的是 docker.io/minio/minio:latest。
docker pull docker.m.daocloud.io/langfuse/langfuse:4
docker tag docker.m.daocloud.io/langfuse/langfuse:4 docker.langfuse.com/langfuse/langfuse:4
docker pull docker.m.daocloud.io/langfuse/langfuse-worker:4
docker tag docker.m.daocloud.io/langfuse/langfuse-worker:4 docker.langfuse.com/langfuse/langfuse-worker:4
docker pull docker.m.daocloud.io/clickhouse/clickhouse-server:25.12
docker tag docker.m.daocloud.io/clickhouse/clickhouse-server:25.12 docker.io/clickhouse/clickhouse-server:25.12
docker pull docker.m.daocloud.io/library/postgres:17
docker tag docker.m.daocloud.io/library/postgres:17 docker.io/postgres:17
docker pull docker.m.daocloud.io/library/redis:7
docker tag docker.m.daocloud.io/library/redis:7 docker.io/redis:7
docker pull docker.io/minio/minio:latest
cd /usr/cluster/observability/langfuse
docker compose up -d
docker compose ps文件:langfuse/docker-compose.yml。服务器路径 /usr/cluster/observability/langfuse/docker-compose.yml。
# Make sure to update the credential placeholders with your own secrets.
# We mark them with # CHANGEME in the file below.
# In addition, we recommend to restrict inbound traffic on the host to langfuse-web (port 3000) and minio (port 9090) only.
# All other components are bound to localhost (127.0.0.1) to only accept connections from the local machine.
# External connections from other machines will not be able to reach these services directly.
services:
langfuse-worker:
image: docker.langfuse.com/langfuse/langfuse-worker:4
restart: always
depends_on: &langfuse-depends-on
postgres:
condition: service_healthy
minio:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
ports:
- 127.0.0.1:3030:3030
environment: &langfuse-worker-env
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/postgres} # CHANGEME
SALT: ${SALT:-mysalt} # CHANGEME
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} # CHANGEME: generate via `openssl rand -hex 32`
TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true}
LANGFUSE_MIGRATION_V4_WRITE_MODE: ${LANGFUSE_MIGRATION_V4_WRITE_MODE:-dual}
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
# Lets the worker reach the web service internally for in-app agent MCP
# calls without redirecting NEXTAUTH_URL, which also builds links for
# users. Pair it with LANGFUSE_MCP_ALLOWED_HOSTS on langfuse-web.
LANGFUSE_MCP_BASE_URL: ${LANGFUSE_MCP_BASE_URL:-}
LANGFUSE_LLM_CONNECTION_WHITELISTED_HOST: ${LANGFUSE_LLM_CONNECTION_WHITELISTED_HOST:-}
LANGFUSE_LLM_CONNECTION_WHITELISTED_IPS: ${LANGFUSE_LLM_CONNECTION_WHITELISTED_IPS:-}
LANGFUSE_LLM_CONNECTION_WHITELISTED_IP_SEGMENTS: ${LANGFUSE_LLM_CONNECTION_WHITELISTED_IP_SEGMENTS:-}
CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000}
CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123}
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME
CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false}
CLICKHOUSE_CLUSTER_NAME: ${CLICKHOUSE_CLUSTER_NAME:-default}
LANGFUSE_JSON_BAD_UNICODE_ESCAPE: ${LANGFUSE_JSON_BAD_UNICODE_ESCAPE:-}
LANGFUSE_USE_AZURE_BLOB: ${LANGFUSE_USE_AZURE_BLOB:-false}
LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE: ${LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE:-false}
LANGFUSE_OCI_AUTH_TYPE: ${LANGFUSE_OCI_AUTH_TYPE:-workload_identity}
LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto}
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true}
LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/}
LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: ${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse}
LANGFUSE_S3_MEDIA_UPLOAD_REGION: ${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto}
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true}
LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: ${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/}
LANGFUSE_OBSERVATION_FIELD_OVERFLOW_ENABLED: ${LANGFUSE_OBSERVATION_FIELD_OVERFLOW_ENABLED:-false}
LANGFUSE_OBSERVATION_FIELD_SIZE_LIMIT_BYTES: ${LANGFUSE_OBSERVATION_FIELD_SIZE_LIMIT_BYTES:-2097152}
LANGFUSE_S3_BATCH_EXPORT_ENABLED: ${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false}
LANGFUSE_S3_BATCH_EXPORT_BUCKET: ${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse}
LANGFUSE_S3_BATCH_EXPORT_PREFIX: ${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/}
LANGFUSE_S3_BATCH_EXPORT_REGION: ${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto}
LANGFUSE_S3_BATCH_EXPORT_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT:-http://minio:9000}
LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT:-http://localhost:9090}
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID: ${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID:-minio}
LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY: ${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME
LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE: ${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true}
LANGFUSE_INGESTION_QUEUE_DELAY_MS: ${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-}
LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS: ${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_AUTH: ${REDIS_AUTH:-myredissecret} # CHANGEME
LANGFUSE_BULLMQ_SKIP_REDIS_VERSION_CHECK: ${LANGFUSE_BULLMQ_SKIP_REDIS_VERSION_CHECK:-false}
REDIS_TLS_ENABLED: ${REDIS_TLS_ENABLED:-false}
REDIS_TLS_CA: ${REDIS_TLS_CA:-/certs/ca.crt}
REDIS_TLS_CERT: ${REDIS_TLS_CERT:-/certs/redis.crt}
REDIS_TLS_KEY: ${REDIS_TLS_KEY:-/certs/redis.key}
EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-}
SMTP_CONNECTION_URL: ${SMTP_CONNECTION_URL:-}
# Shared AI features configuration. These apply to AI features that run
# in both web and worker.
LANGFUSE_IN_APP_AGENT_ENABLED: ${LANGFUSE_IN_APP_AGENT_ENABLED:-}
LANGFUSE_EVALUATOR_MEDIA_TRANSPORT: ${LANGFUSE_EVALUATOR_MEDIA_TRANSPORT:-}
LANGFUSE_EVALUATOR_MEDIA_INLINE_MAX_BYTES: ${LANGFUSE_EVALUATOR_MEDIA_INLINE_MAX_BYTES:-}
LANGFUSE_AI_PROVIDER: ${LANGFUSE_AI_PROVIDER:-}
LANGFUSE_AI_MODEL: ${LANGFUSE_AI_MODEL:-}
LANGFUSE_AI_SMALL_MODEL: ${LANGFUSE_AI_SMALL_MODEL:-}
LANGFUSE_AI_API_KEY: ${LANGFUSE_AI_API_KEY:-}
LANGFUSE_AI_BASE_URL: ${LANGFUSE_AI_BASE_URL:-}
LANGFUSE_AI_USE_RESPONSES_API: ${LANGFUSE_AI_USE_RESPONSES_API:-}
LANGFUSE_AI_EXTRA_HEADERS: ${LANGFUSE_AI_EXTRA_HEADERS:-}
LANGFUSE_AI_AWS_BEDROCK_REGION: ${LANGFUSE_AI_AWS_BEDROCK_REGION:-}
LANGFUSE_AI_VERTEX_LOCATION: ${LANGFUSE_AI_VERTEX_LOCATION:-}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN:-}
AWS_PROFILE: ${AWS_PROFILE:-}
LANGFUSE_AI_FEATURES_AWS_PROFILE: ${LANGFUSE_AI_FEATURES_AWS_PROFILE:-}
LANGFUSE_IN_APP_AGENT_AWS_PROFILE: ${LANGFUSE_IN_APP_AGENT_AWS_PROFILE:-}
# In-app agent worker settings (ignored by the web service).
LANGFUSE_IN_APP_AGENT_RUN_QUEUE_PROCESSING_CONCURRENCY: ${LANGFUSE_IN_APP_AGENT_RUN_QUEUE_PROCESSING_CONCURRENCY:-}
LANGFUSE_IN_APP_AGENT_SANDBOX_PROVIDER: ${LANGFUSE_IN_APP_AGENT_SANDBOX_PROVIDER:-}
LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_IMAGE_IDENTIFIER: ${LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_IMAGE_IDENTIFIER:-}
LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_EXECUTION_ROLE_ARN: ${LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_EXECUTION_ROLE_ARN:-}
LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_EGRESS_NETWORK_CONNECTOR_ARN: ${LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_EGRESS_NETWORK_CONNECTOR_ARN:-}
LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_REGION: ${LANGFUSE_IN_APP_AGENT_SANDBOX_AWS_LAMBDA_MICROVM_REGION:-}
langfuse-web:
image: docker.langfuse.com/langfuse/langfuse:4
restart: always
depends_on: *langfuse-depends-on
ports:
- "3100:3000"
environment:
<<: *langfuse-worker-env
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-mysecret} # CHANGEME
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://localhost:9090}
LANGFUSE_S3_MEDIA_UPLOAD_INTERNAL_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_INTERNAL_ENDPOINT:-http://minio:9000}
LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-}
LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-}
LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-}
LANGFUSE_INIT_PROJECT_NAME: ${LANGFUSE_INIT_PROJECT_NAME:-}
LANGFUSE_INIT_PROJECT_PUBLIC_KEY: ${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-}
LANGFUSE_INIT_PROJECT_SECRET_KEY: ${LANGFUSE_INIT_PROJECT_SECRET_KEY:-}
LANGFUSE_INIT_USER_EMAIL: ${LANGFUSE_INIT_USER_EMAIL:-}
LANGFUSE_INIT_USER_NAME: ${LANGFUSE_INIT_USER_NAME:-}
LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-}
LANGFUSE_MARKDOWN_RENDER_CHARACTER_LIMIT: ${LANGFUSE_MARKDOWN_RENDER_CHARACTER_LIMIT:-}
# Hostnames allowed to reach the MCP endpoint, beyond NEXTAUTH_URL's own
# host. Required when the worker uses LANGFUSE_MCP_BASE_URL.
LANGFUSE_MCP_ALLOWED_HOSTS: ${LANGFUSE_MCP_ALLOWED_HOSTS:-}
# In-app agent settings.
LANGFUSE_IN_APP_AGENT_MAX_ACTIVE_RUNS_PER_USER: ${LANGFUSE_IN_APP_AGENT_MAX_ACTIVE_RUNS_PER_USER:-}
LANGFUSE_IN_APP_AGENT_MAX_ACTIVE_RUNS_PER_ORG: ${LANGFUSE_IN_APP_AGENT_MAX_ACTIVE_RUNS_PER_ORG:-}
clickhouse:
image: docker.io/clickhouse/clickhouse-server:25.12
restart: always
user: "101:101"
environment:
CLICKHOUSE_DB: default
CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse}
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME
volumes:
- langfuse_clickhouse_data:/var/lib/clickhouse
- langfuse_clickhouse_logs:/var/log/clickhouse-server
healthcheck:
test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
interval: 5s
timeout: 5s
retries: 30
start_period: 40s
minio:
image: docker.io/minio/minio:latest
restart: always
entrypoint: sh
# create the 'langfuse' bucket before starting the service
command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data'
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret} # CHANGEME
ports:
- "9092:9000"
volumes:
- langfuse_minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 20
start_period: 15s
redis:
image: docker.io/redis:7
restart: always
# CHANGEME: row below to secure redis password
command: >
--requirepass ${REDIS_AUTH:-myredissecret}
--maxmemory-policy noeviction
environment:
REDIS_AUTH: ${REDIS_AUTH:-myredissecret}
volumes:
- langfuse_redis_data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_AUTH\" ping | grep PONG"]
interval: 3s
timeout: 10s
retries: 10
postgres:
image: docker.io/postgres:${POSTGRES_VERSION:-17}
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 3s
timeout: 3s
retries: 10
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} # CHANGEME
POSTGRES_DB: ${POSTGRES_DB:-postgres}
TZ: UTC
PGTZ: UTC
volumes:
- langfuse_postgres_data:/var/lib/postgresql/data
volumes:
langfuse_postgres_data:
driver: local
langfuse_clickhouse_data:
driver: local
langfuse_clickhouse_logs:
driver: local
langfuse_minio_data:
driver: local
langfuse_redis_data:
driver: local文件:langfuse/env-要点.txt
# 现场核对过的 Langfuse 环境项。完整文件在服务器 /usr/cluster/observability/langfuse/.env,权限 600。
# 密钥、数据库口令、项目私钥只留在那份文件里。下面不抄具体值。已存在时不要重新生成,否则旧追踪解不开。
COMPOSE_PROJECT_NAME=langfuse
NEXTAUTH_URL=http://10.0.1.47:3100
TELEMETRY_ENABLED=false
POSTGRES_USER=postgres
POSTGRES_DB=postgres
CLICKHOUSE_USER=clickhouse
MINIO_ROOT_USER=minio
LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio
LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://10.0.1.47:9092
LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio
LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://10.0.1.47:9092
LANGFUSE_INIT_ORG_ID=e1e3900e-7a13-4952-955c-ac8223343c04
LANGFUSE_INIT_ORG_NAME=内网大模型
LANGFUSE_INIT_PROJECT_ID=955cd00a-2949-4d51-8dbb-7b824bbbb420
LANGFUSE_INIT_PROJECT_NAME=会话追踪
LANGFUSE_INIT_PROJECT_PUBLIC_KEY=pk-lf-d7a2e5c771eb542a
LANGFUSE_INIT_USER_EMAIL=admin@example.com
LANGFUSE_INIT_USER_NAME=admin
LANGFUSE_MIGRATION_V4_WRITE_MODE=dual
# 下列项已在服务器 .env 中生成,本文只列名字:
# NEXTAUTH_SECRET SALT ENCRYPTION_KEY
# POSTGRES_PASSWORD DATABASE_URL
# CLICKHOUSE_PASSWORD CLICKHOUSE_MIGRATION_URL CLICKHOUSE_URL
# REDIS_AUTH MINIO_ROOT_PASSWORD
# LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY
# LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY
# LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY
# LANGFUSE_INIT_PROJECT_SECRET_KEY LANGFUSE_INIT_USER_PASSWORD页面:http://10.0.1.47:3100 登录:admin@example.com / Lf-trace-2026 组织:内网大模型 项目:会话追踪,编号 955cd00a-2949-4d51-8dbb-7b824bbbb420 公钥:pk-lf-d7a2e5c771eb542a 项目私钥在 .env 的 LANGFUSE_INIT_PROJECT_SECRET_KEY。Dify 容器里的上报地址是 http://172.17.0.1:3100,不能写页面那个不带端口的地址。
健康检查 GET /api/public/health 返回 {"status":"OK","version":"4.56.0"}。
3. 部署 Prometheus、Grafana、DCGM
Prometheus v3.2.1 保留 15 天。Grafana 11.5.2 关掉自助注册。DCGM 镜像 nvidia/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04,采集间隔 5 秒,需要 SYS_ADMIN 才能读 NVML。
vLLM 指标走宿主机网关:Prometheus 容器里的 host.docker.internal:8000。三个抓取目标都是 up:prometheus、dcgm、vllm。
文件:scripts/02-部署GPU监控.sh
#!/bin/bash
# 已在 10.0.1.47 执行。Grafana 在 3001,Prometheus 在 9090,DCGM 在 9400。
# 再跑本脚本发现 prometheus 容器已在会直接退出。
# 不重启 vLLM。DCGM 只读显卡,不占用算力配额。
set -eu
if docker ps --format '{{.Names}}' | grep -qx 'prometheus'; then
echo "Prometheus 已在运行,退出。"
exit 0
fi
docker pull docker.m.daocloud.io/prom/prometheus:v3.2.1
docker tag docker.m.daocloud.io/prom/prometheus:v3.2.1 prom/prometheus:v3.2.1
docker pull docker.m.daocloud.io/grafana/grafana:11.5.2
docker tag docker.m.daocloud.io/grafana/grafana:11.5.2 grafana/grafana:11.5.2
docker pull docker.m.daocloud.io/nvidia/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04
docker tag docker.m.daocloud.io/nvidia/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04 nvidia/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04
cd /usr/cluster/observability/metrics
docker compose up -d
docker compose ps文件:metrics/docker-compose.yml
name: observability
services:
prometheus:
image: prom/prometheus:v3.2.1
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=15d
grafana:
image: grafana/grafana:11.5.2
container_name: grafana
restart: unless-stopped
ports:
- "3001:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: Gf-gpu-2026
GF_USERS_ALLOW_SIGN_UP: "false"
GF_SERVER_ROOT_URL: http://10.0.1.47:3001
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
dcgm-exporter:
image: nvidia/dcgm-exporter:3.3.8-3.6.0-ubuntu22.04
container_name: dcgm-exporter
restart: unless-stopped
cap_add:
- SYS_ADMIN
ports:
- "9400:9400"
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,utility
DCGM_EXPORTER_INTERVAL: "5000"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
prometheus_data:
grafana_data:文件:metrics/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: dcgm
static_configs:
- targets: ["dcgm-exporter:9400"]
- job_name: vllm
metrics_path: /metrics
static_configs:
- targets: ["host.docker.internal:8000"]文件:metrics/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false文件:metrics/grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
- name: local
orgId: 1
folder: 监控
type: file
disableDeletion: false
updateIntervalSeconds: 30
options:
path: /var/lib/grafana/dashboards文件:metrics/grafana/dashboards/gpu-vllm.json
{
"uid": "gpu-vllm",
"title": "GPU 与 vLLM",
"timezone": "browser",
"schemaVersion": 39,
"refresh": "15s",
"time": { "from": "now-1h", "to": "now" },
"panels": [
{
"id": 1,
"type": "stat",
"title": "GPU 利用率",
"gridPos": { "h": 4, "w": 6, "x": 0, "y": 0 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100 } },
"targets": [{ "expr": "DCGM_FI_DEV_GPU_UTIL", "refId": "A" }]
},
{
"id": 2,
"type": "stat",
"title": "显存已用",
"gridPos": { "h": 4, "w": 6, "x": 6, "y": 0 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "decmbytes" } },
"targets": [{ "expr": "DCGM_FI_DEV_FB_USED", "refId": "A" }]
},
{
"id": 3,
"type": "stat",
"title": "显存空闲",
"gridPos": { "h": 4, "w": 6, "x": 12, "y": 0 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "decmbytes" } },
"targets": [{ "expr": "DCGM_FI_DEV_FB_FREE", "refId": "A" }]
},
{
"id": 4,
"type": "stat",
"title": "GPU 温度",
"gridPos": { "h": 4, "w": 6, "x": 18, "y": 0 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "celsius" } },
"targets": [{ "expr": "DCGM_FI_DEV_GPU_TEMP", "refId": "A" }]
},
{
"id": 5,
"type": "timeseries",
"title": "GPU 利用率",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 4 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "percent", "min": 0, "max": 100 } },
"targets": [{ "expr": "DCGM_FI_DEV_GPU_UTIL", "legendFormat": "GPU {{gpu}}", "refId": "A" }]
},
{
"id": 6,
"type": "timeseries",
"title": "显存占用",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 4 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "decmbytes" } },
"targets": [
{ "expr": "DCGM_FI_DEV_FB_USED", "legendFormat": "已用 {{gpu}}", "refId": "A" },
{ "expr": "DCGM_FI_DEV_FB_FREE", "legendFormat": "空闲 {{gpu}}", "refId": "B" }
]
},
{
"id": 7,
"type": "timeseries",
"title": "温度与显存带宽利用率",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 12 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "short" } },
"targets": [
{ "expr": "DCGM_FI_DEV_GPU_TEMP", "legendFormat": "温度 {{gpu}}", "refId": "A" },
{ "expr": "DCGM_FI_DEV_MEM_COPY_UTIL", "legendFormat": "显存带宽 {{gpu}}", "refId": "B" }
]
},
{
"id": 8,
"type": "timeseries",
"title": "vLLM 请求与 KV 占用",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 12 },
"datasource": { "type": "prometheus", "uid": "prometheus" },
"fieldConfig": { "defaults": { "unit": "short" } },
"targets": [
{ "expr": "vllm:num_requests_running", "legendFormat": "运行中", "refId": "A" },
{ "expr": "vllm:num_requests_waiting", "legendFormat": "排队", "refId": "B" },
{ "expr": "vllm:kv_cache_usage_perc", "legendFormat": "KV 占用", "refId": "C" }
]
}
]
}Grafana:http://10.0.1.47:3001 登录:admin / Gf-gpu-2026 仪表盘:http://10.0.1.47:3001/d/gpu-vllm/gpu-e4b88e-vllm 文件夹:监控。每 15 秒刷新。显存单位是 MiB(decmbytes)。
Prometheus:http://10.0.1.47:9090 ,没有登录。目标页是 /targets。
二、功能操作案例
案例 1:把追踪和 GPU 监控装上,不要影响现有服务
提问:部署 Langfuse、Prometheus、Grafana、DCGM,追踪会话链路,同时监控 GPU 的显存、温度、利用率。
新起的容器都在 /usr/cluster/observability。vLLM、New-API、Dify 的 web 容器没有为这次部署重建。主机 80 端口和 Kubernetes 没有动。
写文档时核对到的状态:
| 检查 | 结果 |
|---|---|
Langfuse /api/public/health | OK,版本 4.56.0 |
Prometheus /-/ready | Prometheus Server is Ready |
Grafana /api/health | database ok,版本 11.5.2 |
| 抓取目标 | dcgm up,prometheus up,vllm up |
DCGM 读到的卡是 NVIDIA H20,驱动 550.120。部署时温度约 37°C,GPU 利用率为 0,显存已用约 86432 MiB。
案例 2:在页面上看会话,并看显卡
提问:追踪要能看见一次问答;显存、温度、利用率要在 Grafana 里。
Langfuse 用管理员账号打开项目「会话追踪」。Tracing 里看一次问答的 message 和下面的 llm。工具调用出现后,到 Observations 筛选 Name = getServerStatus。Dify 怎么把追踪送进来,写在 dify/用 Dify 做合同知识库:Qwen 对话、bge 重排和 Langfuse 工具追踪.md 第 7 节和案例 8、案例 9。
Grafana 打开「GPU 与 vLLM」。上面四个数是当前利用率、显存已用、显存空闲、温度。下面四条曲线是利用率、显存占用、温度和显存带宽、vLLM 正在跑的请求、排队请求、KV 占用。
vLLM 的 /metrics 已经进 Prometheus。这版 vLLM 没有 OTLP 参数,所以推理引擎的跨度不在 Langfuse 里。显卡和请求队列看 Grafana。
写文档时核对到的数据
- Langfuse web
0.0.0.0:3100,worker 只绑127.0.0.1:3030。MinIO0.0.0.0:9092。ClickHouse、Redis、Postgres 没有宿主机端口。 LANGFUSE_MIGRATION_V4_WRITE_MODE=dual,TELEMETRY_ENABLED=false,NEXTAUTH_URL=http://10.0.1.47:3100。- Prometheus 15 天保留。三个目标 dcgm、prometheus、vllm 都是 up。
- Grafana 管理员
admin/Gf-gpu-2026,禁止自助注册。仪表盘 uidgpu-vllm。 - DCGM 容器
dcgm-exporter,端口 9400,间隔 5000 毫秒。