基于OpenObserve实现分布式系统监控(1)

作者参与的一个项目中针对系统监控选择了Openobserve这个比较新的系统搭建,实现了日志+指标+链路的完整监控体系。本文以此项目作为例子,分享一下我们团队针对Openobserve平台接入的一些实现方案和经验。

Openobserve部署

如果想要在单机部署Openobserve,官方文档中提供了两种方法,分别是通过安装脚本直接在裸机安装、及通过docker run命令在docker中启动。在实际部署中,我们发现这两种方案均不太合适。其中第一种方案会污染物理机,而本人团队的监控机器上需要同时部署其他应用,有发生冲突的危险。第二种方案不利于后期变更docker容器相关配置,且教程中使用的是社区版本镜像,其功能受限。

鉴于以上原因,我们团队选择了使用docker compose的方案,部署企业版本的Openobserve系统,以下为示例配置文件:

bash 复制代码
services:
  openobserve:
    image: public.ecr.aws/zinclabs/openobserve-enterprise:latest
    ports:
      - "5080:5080" # web
      - "5081:5081" # grpc
      - "5514:5514" # syslog
    environment:
      - ZO_DATA_DIR=/data
      - ZO_ROOT_USER_EMAIL=******@example.com
      - ZO_ROOT_USER_PASSWORD=******
      - ZO_TELEMETRY=false  # 禁用Openobserve遥测
      - ZO_APP_NAME=observe
      - ZO_COMPACT_DATA_RETENTION_DAYS=14 # 日志滚动周期
    volumes:
      - openobserve_data:/data
    restart: unless-stopped
volumes:
  openobserve_data: 

针对企业版付费问题,官方团队曾在团队博客中给出过澄清,大致内容为:企业版可以对日均日志吞吐低于200GB的团队免费使用,这对于中小团队而言是绰绰有余的额度,可以放心使用。

成功部署后登录即可看到如下界面,证明部署部分已经成功完成:

增强配置

如果想在以上基础上额外补充单点登录功能,Openobserve依赖dexidp实现对接外部身份源,而此依赖的部署方案官方仅在helm镜像包中有提及,但并未提供docker compose版本的部署方案。经过一段时间摸索,我们团队最终将helm包中的部署有关配置移植到了compose中,具体配置如下:

bash 复制代码
# docker-compose.yaml
services:
  openobserve:
    image: public.ecr.aws/zinclabs/openobserve-enterprise:latest
    ports:
      - "5080:5080"
      - "5081:5081" # grpc 
      - "5514:5514" # syslog
      # 其他端口可按需暴露,或直接使用host网络模式
    environment:
      - ZO_DATA_DIR=/data
      - ZO_ROOT_USER_EMAIL=*****@example.com
      - ZO_ROOT_USER_PASSWORD=*******
      - ZO_BASE_URI=/observe  # 网站路径前缀
      - ZO_TELEMETRY=false
      - ZO_APP_NAME=observe
      - ZO_COMPACT_DATA_RETENTION_DAYS=30
      - O2_DEX_ENABLED=true
      - O2_DEX_CLIENT_ID=o2-client
      - O2_DEX_CLIENT_SECRET=******
      - O2_DEX_BASE_URL=https://***.com/dex
      - O2_DEX_REDIRECT_URL=https://***.com/observe/config/redirect
      - O2_CALLBACK_URL=https://***.com/observe/web/cb
    volumes:
      - openobserve_data:/data
    restart: unless-stopped
  dex:
    image: ghcr.io/dexidp/dex:v2.37.0
    ports:
      - "5556:5556" # dex endpoint
    volumes:
      - ./dex-config.yaml:/etc/dex/config.docker.yaml
volumes:
  openobserve_data:
yaml 复制代码
# dex-config.yaml
issuer: https://example.com/internal/dex

storage:
  type: memory  # 分布式环境下不应该使用内存数据库,此处仅做示意

web:
  http: 0.0.0.0:5556

expiry:
  idTokens: 10m
  refreshTokens:
    validIfNotUsedFor: 30m

staticClients:
  - id: o2-client
    redirectURIs:
      - https://example.com/internal/observe/config/redirect
    name: o2-client
    secret: ****** # 与上文O2_DEX_CLIENT_SECRET对应

oauth2:
  responseTypes:
    - code
  skipApprovalScreen: true

connectors:
  - type: oidc  # OIDC认证协议介入,可以根据上游身份源自行选择
    id: sso
    name: SSO
    config:
      issuer: https://sso.***.com/application/o/observe/
      clientID: xxxx
      clientSecret: xxxxx
      redirectURI: https://***.com/dex/callback

注意:需要在外层通过Nginx、Caddy等将对应端口暴露为配置中的域名

成功部署后应看到如下登录页面,点击Login with SSO后即可跳转到身份源完成单点登录:

链路监控

Go系统接入

团队内使用的微服务以fiber作为web服务框架,基于官方的otelfiber插件二次开发,实现链路数据上报。

连接Openobserve系统方式:

go 复制代码
const (
    DefaultTracesPath  = "/v1/traces"
    DefaultMetricsPath = "/v1/metrics"
    DefaultLogsPath    = "/v1/logs"
)

func ConfigureOpentelemetry(ctx context.Context, serviceName string) func() {
    endpoint := os.Getenv("OTEL_HTTP_ENDPOINT")
    authorization := os.Getenv("OTEL_AUTHORIZATION")

    if endpoint == "" || authorization == "" {
       return configureStdout()
    }

    endpointUrl, err := url.Parse(endpoint)
    if err != nil {
       panic(err)
    }

    hostname, err := os.Hostname()
    if err != nil {
       hostname = "unknown"
    }
    res := resource.NewWithAttributes(
       semconv.SchemaURL,
       semconv.ServiceNameKey.String(serviceName),
       semconv.HostNameKey.String(hostname),
    )

    isInsecure := strings.ToLower(endpointUrl.Scheme) == "http"

    authHeader := map[string]string{
       "Authorization": authorization,
       "stream-name":   "default",
    }

    // Tracing
    traceOptions := []otlptracehttp.Option{
       otlptracehttp.WithEndpoint(endpointUrl.Host),
       otlptracehttp.WithURLPath(path.Join(endpointUrl.Path, DefaultTracesPath)),
       otlptracehttp.WithHeaders(authHeader),
    }
    if isInsecure {
       traceOptions = append(traceOptions, otlptracehttp.WithInsecure())
    }

    traceExp, err := otlptracehttp.New(ctx, traceOptions...)

    if err != nil {
       panic(err)
    }

    tracerProvider := sdktrace.NewTracerProvider(
       sdktrace.WithSampler(sdktrace.AlwaysSample()),
       sdktrace.WithResource(res),
       sdktrace.WithBatcher(traceExp),
    )

    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
       propagation.TraceContext{},
       propagation.Baggage{},
    ))
    otel.SetTracerProvider(tracerProvider)

    return func() {
       cxt, cancel := context.WithTimeout(ctx, time.Second)
       defer cancel()
       if err := traceExp.Shutdown(cxt); err != nil {
          otel.Handle(err)
       }
    }
}

由于Golang基于context上下文实现并发控制,为了实现链路收集,需要在所有链接函数中持续传递context对象,并使用opentelemetry为不同中间件驱动实现的instrumentation组件实现上报。对于微服务系统,opentelemetry目前通过请求头设置Traceparent实现链路传播(propagation),其内容格式为version-traceId-parentId-traceFlags,详见W3C规范文档。

一个典型的MySQL接入方案是基于otelsql,实现针对database/sql包的链路数据上报。具体实现如下:

go 复制代码
db, err := otelsql.Open(
   dialect.MySQL,
   fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=True", *****),
   otelsql.WithAttributes(semconv.DBSystemMySQL),
   otelsql.WithSpanOptions(otelsql.SpanOptions{
      DisableErrSkip: true,
   }),
   otelsql.WithDisableSkipErrMeasurement(true),
)
if err != nil {
   panic(err)
}
err = otelsql.RegisterDBStatsMetrics(db, otelsql.WithAttributes(
   semconv.DBSystemMySQL,
))
if err != nil {
   panic(err)
}

可供参考的其他中间件接入方案:

Python系统接入

Python接入与Golang基本思路相似,具体如下:

python 复制代码
resource = Resource(attributes={
    "service.name": "ezcoding-langchain",
    "host.name": socket.gethostname(),
})
tracer_provider = TracerProvider(resource=resource)
trace.set_tracer_provider(tracer_provider)

otlp_endpoint = os.getenv("OTEL_HTTP_ENDPOINT")
authorization = os.getenv("OTEL_AUTHORIZATION")

otlp_exporter = OTLPSpanExporter(
    endpoint=otlp_endpoint + "/v1/traces",
    headers={
        "Authorization": authorization,
        "stream-name": "default",
    }
)
tracer_provider.add_span_processor(
    BatchSpanProcessor(otlp_exporter)
)

FastAPI框架接入方式:

python 复制代码
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
FastAPIInstrumentor.instrument_app(app, exclude_spans=["send"])

OpenAI客户端接入方式:

python 复制代码
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
OpenAIInstrumentor().instrument()

LangChain框架可尝试使用此项目:github.com/traceloop/o...

Django框架官方目前支持仍不完善,对于WSGI方式启动的项目,可以直接使用:opentelemetry官方实现的instrumentation。对于ASGI项目,该实现目前无法收集和上报数据库相关链路数据,且上报的请求信息中对于路由地址的解析有误,具体解决还有待官方修复。针对这个问题目前团队内选择使用opentelemetry-instrumentation-mysqlclient库手动上报数据库链路,并通过monkey-patch的方式临时解决Django路由解析问题。

实现效果

如图所见,通过OpenObserve系统,我们实现了链路数据上报及可视化。

后记

根据笔者团队这段时间的使用,发现OpenObserve存在日志压缩机制,因此建议准备接入该平台的团队搭配告警功能使用,在获得故障链路后及时通知运维开发同事。由于该项目使用Rust语言编写,部署开销相较其他方案而言较为轻量,适宜在中小型团队内使用。官方目前仍在活跃更新中,建议大家如果采用此方案及时跟进官方的更新内容。

相关推荐
YH行业报告分析40 分钟前
2026电缆铺设设备市场:自动化敷设与电网升级驱动增长
运维·自动化
ajassi20002 小时前
AI语音智能体开发日记(十八)智能体服务器xiaozhi-esp32-server源码部署指南
运维·服务器·人工智能·ai·ai编程
‎ദ്ദിᵔ.˛.ᵔ₎2 小时前
linux 进程信号(下)
linux·运维·服务器
Mikko72 小时前
Linux 磁盘满了但 du 找不到?LVM 扩容 df 不变?fstab 写错开不了机?三台真机实测磁盘排查
linux·运维·centos
蓝胖的四次元口袋2 小时前
Jenkins知识梳理(2)
运维·jenkins
yumgpkpm2 小时前
Acceldata ODP 3.3.6.4 vs CDP Private Cloud Base 7.3.2 对比
大数据·运维·服务器·hadoop·华为·zookeeper·hbase
Liuqy-052 小时前
特殊进程——孤儿、僵尸、守护进程
linux·运维·服务器
考研保研资料分享3 小时前
自动化控制保研经验合辑:上交夏令营、北工大预推免与跨专业面试复盘(2025)
运维·面试·自动化
乐维_lwops4 小时前
2026选择运维监控系统时应该重点考察哪些功能?
大数据·运维·人工智能