平台架构

技术选型

OpenObserve(原名O2)是一款开源的可观测性平台,相比传统ELK方案具有显著优势:

  • 资源占用低:内存占用仅为Elasticsearch的1/10,存储空间节省90%
  • 高性能:基于Rust编写,单节点可处理5TB+日志/天
  • 全功能:集成日志、指标、链路追踪,替代Grafana+Loki+Tempo
  • 兼容性强:支持Elasticsearch API、Prometheus API、OpenTelemetry
  • 部署简单:单一二进制文件,无外部依赖

Fluent Bit是云原生日志收集器:

  • 轻量级:内存占用<1MB,适合嵌入式和容器环境
  • 高性能:多线程异步处理,吞吐量可达100MB/s
  • 丰富插件:支持200+输入/输出/过滤/解析器
  • Kubernetes原生:自动发现Pod、标签注入、元数据关联

架构设计

┌─────────────────────────────────────────────────────────────────┐
│                        业务应用层                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │  Java应用 │  │  Go服务  │  │ Nginx日志 │  │ 系统日志  │       │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘       │
│       │             │             │             │               │
│  stdout/stderr   stdout/stderr  /var/log/*   /var/log/*         │
└───────┼─────────────┼─────────────┼─────────────┼───────────────┘
        │             │             │             │
┌───────┴─────────────┴─────────────┴─────────────┴───────────────┐
│                      Kubernetes集群                              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │              DaemonSet: Fluent Bit                      │   │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │   │
│  │  │  Tail输入    │→ │  过滤/解析   │→ │  Buffer缓冲  │  │   │
│  │  └──────────────┘  └──────────────┘  └──────────────┘  │   │
│  └─────────────────────────────────────────────────────────┘   │
└──────────────────────────────┬──────────────────────────────────┘
                               │ HTTP/HTTPS
┌──────────────────────────────┴──────────────────────────────────┐
│                    OpenObserve集群                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │  Ingest节点  │→ │  数据存储    │→ │  查询引擎    │          │
│  │  (写入优化)  │  │  (Parquet)   │  │  (全文检索)  │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
└─────────────────────────────────────────────────────────────────┘

组件版本

组件 版本 说明
OpenObserve 0.15.0+ 稳定版,支持分布式
Fluent Bit 4.0.0+ 最新稳定版
Kubernetes 1.24+ 生产环境
Helm 3.0+ 部署工具

OpenObserve部署

前置准备

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1. 创建Namespace
kubectl create namespace logging

# 2. 添加OpenObserve Helm仓库
helm repo add openobserve https://charts.openobserve.ai
helm repo update

# 3. 创建PV/PVC(生产环境使用分布式存储)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: openobserve-data
  namespace: logging
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: zstack-rbd  # 根据实际SC调整
  resources:
    requests:
      storage: 500Gi
EOF

Helm部署OpenObserve

创建values文件 openobserve-values.yaml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
# openobserve-values.yaml

image:
  repository: public.ecr.aws/zinclabs/openobserve
  tag: "0.15.1"
  pullPolicy: IfNotPresent

replicaCount: 3  # 生产环境建议3节点高可用

# 资源配置(根据日志量调整)
resources:
  limits:
    cpu: "8"
    memory: 16Gi
  requests:
    cpu: "4"
    memory: 8Gi

# 数据持久化
persistence:
  enabled: true
  existingClaim: openobserve-data
  mountPath: /data

# 环境变量
env:
  - name: ZO_ROOT_USER_EMAIL
    value: "[email protected]"
  - name: ZO_ROOT_USER_PASSWORD
    valueFrom:
      secretKeyRef:
        name: openobserve-secret
        key: password
  - name: ZO_DATA_DIR
    value: "/data"
  - name: ZO_HTTP_PORT
    value: "5080"
  - name: ZO_MEMORY_CACHE_ENABLED
    value: "true"
  - name: ZO_MEMORY_CACHE_MAX_SIZE
    value: "4096"  # MB
  - name: ZO_COMPRESSION_ENABLED
    value: "true"
  - name: ZO_COMPRESSION_FORMAT
    value: "zstd"
  - name: ZO_PARquet_COMPRESSION
    value: "zstd"
  - name: ZO_META_STORE
    value: "sqlite"  # 生产环境使用PostgreSQL
  - name: ZO_METRICS_ENABLED
    value: "true"

# 配置PostgreSQL元数据库(生产环境推荐)
envFrom:
  - secretRef:
      name: postgres-connection

# Service配置
service:
  type: ClusterIP
  port: 5080
  targetPort: 5080
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

# Ingress配置
ingress:
  enabled: true
  className: "nginx"
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
  hosts:
    - host: logs.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: logs-tls
      hosts:
        - logs.example.com

# Pod调度
nodeSelector: {}
tolerations: []
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values:
                  - openobserve
          topologyKey: kubernetes.io/hostname

# 监控配置
monitoring:
  enabled: true
  serviceMonitor:
    enabled: true
    interval: 30s
    namespace: logging

部署OpenObserve

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 创建密码Secret
kubectl create secret generic openobserve-secret \
  --from-literal=password='your-secure-password' \
  -n logging

# 2. 部署
helm upgrade --install openobserve openobserve/openobserve \
  -n logging \
  -f openobserve-values.yaml \
  --version 0.10.2 \
  --timeout 15m

# 3. 等待Pod就绪
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/name=openobserve \
  -n logging \
  --timeout=300s

# 4. 查看状态
kubectl get pods -n logging
kubectl logs -f deployment/openobserve -n logging

# 5. 访问Web UI
echo "访问地址: https://logs.example.com"
echo "默认用户: [email protected]"

Fluent Bit部署

安装 Helm Chart

1
2
3
4
helm repo add fluent https://fluent.github.io/helm-charts
helm repo update
helm pull fluent/fluent-bit --version 0.50.0
helm install fluent-bit ./fluent-bit-0.50.0.tgz --namespace uganda-prod

调整 DaemonSet 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
spec:
  volumes:
    - name: config
      configMap:
        name: fluent-bit
        defaultMode: 420
  containers:
    resources:
      requests:
        cpu: "1"
        memory: 2Gi
      limits:
        cpu: "2"
        memory: 4Gi
    volumeMounts:
      - name: config
        mountPath: /fluent-bit/etc/conf/custom_parsers.conf
        subPath: custom_parsers.conf
      - name: config
        mountPath: /fluent-bit/etc/conf/multiline_parsers.conf
        subPath: multiline_parsers.conf

Fluent Bit采集规则调整

完整ConfigMap 配置

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
kind: ConfigMap
apiVersion: v1
metadata:
  name: fluent-bit-config
  namespace: uganda-prod
  labels:
    app: fluent-bit
    tier: logging
data:
  # ---------------------------------------------------------------------------
  # 1. 自定义解析器 (Custom Parsers)
  # 用于提取特定格式的日志字段,例如从 Java 日志中提取日志级别 (INFO, ERROR 等)
  # ---------------------------------------------------------------------------
  custom_parsers.conf: |
    [PARSER]
        Name        java_log_level
        Format      regex
        # 匹配格式: 2023-10-27 10:00:00.123 INFO ...
        # 捕获组 'level' 将包含日志级别
        Regex       ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\s+(?<level>[A-Z]+)

  # ---------------------------------------------------------------------------
  # 2. 多行解析器 (Multiline Parsers)
  # 专门用于处理 Java 堆栈跟踪等多行日志,防止堆栈被拆分成多条记录
  # ---------------------------------------------------------------------------
  multiline_parsers.conf: |
    [MULTILINE_PARSER]
        Name          java_md_multiline
        Type          regex
        # 规则 1: 如果行以日期开头 (如 2023-...),视为新日志的开始
        rule      "start_state"   "/^\d{4}-\d{2}-\d{2}/"           "cont"
        # 规则 2: 如果行不以日期开头,视为上一行的延续 (堆栈部分)
        rule      "cont"        "/^(?!\d{4}-\d{2}-\d{2}).+/"     "cont"
        # 刷新超时时间 (秒),超时后强制输出当前缓冲的多行日志
        flush_timeout 5

  # ---------------------------------------------------------------------------
  # 3. 主配置文件 (Main Configuration)
  # ---------------------------------------------------------------------------
  fluent-bit.conf: |
    [SERVICE]
        Daemon              Off
        Flush               1
        Log_Level           info
        # 加载标准解析器和自定义解析器
        Parsers_File        /fluent-bit/etc/conf/parsers.conf
        Parsers_File        /fluent-bit/etc/conf/custom_parsers.conf
        Parsers_File        /fluent-bit/etc/conf/multiline_parsers.conf
        
        # 开启内置 HTTP 服务器,用于健康检查和指标暴露 (/api/v1/metrics)
        HTTP_Server         On
        HTTP_Listen         0.0.0.0
        HTTP_Port           2020
        Health_Check        On

        # 文件系统缓冲配置 (防止后端不可用时丢失数据)
        storage.path              /var/log/flb_storage
        storage.sync              normal
        storage.checksum          off
        storage.backlog.mem_limit 200M

    # -----------------------------------------------------------------------
    # [INPUT] 尾随文件采集
    # 采集 Docker/Containerd 生成的标准容器日志
    # -----------------------------------------------------------------------
    [INPUT]
        Name                tail
        Path                /var/log/containers/*.log
        # 启用内置的多行解析 (docker, cri 格式)
        multiline.parser    docker, cri
        Tag                 kube.*
        Mem_Buf_Limit       500MB
        Skip_Long_Lines     On
        Refresh_Interval    10
        # 持久化偏移量数据库,重启后从断点继续读取
        DB                  /var/log/flb_kube.db
        DB.Sync             Normal
        Rotate_Wait         30
        Read_from_Head      Off

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 1: 多行合并
    # 在获取 K8s 元数据之前先合并多行日志,确保堆栈跟踪作为单条记录处理
    # -----------------------------------------------------------------------
    [FILTER]
        Name                multiline
        Match               kube.*
        multiline.key_content log
        multiline.parser    java_md_multiline

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 2: K8s 元数据增强
    # 调用 K8s API 获取 Pod 详细信息 (Namespace, Pod Name, Labels 等)
    # -----------------------------------------------------------------------
    [FILTER]
        Name                kubernetes
        Match               kube.*
        Merge_Log           Off       # 不尝试解析 log 字段为 JSON (避免性能开销或错误)
        Keep_Log            On        # 保留原始 log 字段
        K8S-Logging.Parser  On        # 允许 Pod Annotation 指定 parser
        K8S-Logging.Exclude Off       # 不排除带有特定 annotation 的日志

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 3: 日志内容解析
    # 使用自定义解析器提取 'level' 字段
    # -----------------------------------------------------------------------
    [FILTER]
        Name                parser
        Match               kube.*
        Key_Name            log
        Parser              java_log_level
        Reserve_Data        On        # 保留原始数据
        Preserve_Key        On        # 保留原始 log 键

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 4: 字段初步清洗
    # 复制提取的 level 字段,移除不需要的临时字段
    # -----------------------------------------------------------------------
    [FILTER]
        Name                modify
        Match               kube.*
        Copy                level level
        Remove              _p
        Remove              stream
        Remove              time

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 5: 结构重组 (Nest Lift)
    # 将 kubernetes 对象下的字段提升到顶层,并添加前缀 'k8s_'
    # 目的:扁平化数据结构,便于后续规则匹配
    # -----------------------------------------------------------------------
    [FILTER]
        Name                nest
        Match               kube.*
        Operation           lift
        Nested_under        kubernetes
        Add_prefix          k8s_

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 6: 移除冗余元数据
    # 删除不需要发送到后端的庞大字段 (如 annotations, docker_id)
    # -----------------------------------------------------------------------
    [FILTER]
        Name                modify
        Match               kube.*
        Remove              k8s_annotations
        Remove              k8s_docker_id
        Remove              k8s_container_hash

    # -----------------------------------------------------------------------
    # [FILTER] 阶段 7: 结构重组 (Nest Nest)
    # 将所有 'k8s_' 前缀的字段重新打包回 'kubernetes' 对象下,去除前缀
    # 目的:恢复整洁的嵌套结构,同时已清理了无用字段
    # -----------------------------------------------------------------------
    [FILTER]
        Name                nest
        Match               kube.*
        Operation           nest
        Wildcard            k8s_*
        Nested_under        kubernetes
        Remove_prefix       k8s_

    # =======================================================================
    # [FILTER] 阶段 8: 动态路由 (Rewrite Tag) - 第一跳:按环境分流
    # 根据 Namespace 名称修改 Tag,将日志引导至不同的处理流
    # 语法: Rule $字段名 正则表达式 新Tag 保持原记录(布尔)
    # =======================================================================
    
    # 路由:uganda-uat 环境
    [FILTER]
        Name                rewrite_tag
        Match               kube.*
        Rule                $kubernetes['namespace_name'] ^uganda-uat$ uganda-uat.temp false
        Emitter_Name        re_emitted_uganda-uat-temp

    # 路由:uganda-test 环境
    [FILTER]
        Name                rewrite_tag
        Match               kube.*
        Rule                $kubernetes['namespace_name'] ^uganda-test$ uganda-test.temp false
        Emitter_Name        re_emitted_uganda-test-temp

    # 路由:uganda-prod 环境 (当前部署命名空间)
    [FILTER]
        Name                rewrite_tag
        Match               kube.*
        Rule                $kubernetes['namespace_name'] ^uganda-prod$ uganda-prod.temp false
        Emitter_Name        re_emitted_uganda-prod-temp

    # 路由:uganda-offline 环境
    [FILTER]
        Name                rewrite_tag
        Match               kube.*
        Rule                $kubernetes['namespace_name'] ^uganda-offline$ uganda-offline.temp false
        Emitter_Name        re_emitted_uganda-offline-temp

    # =======================================================================
    # [FILTER] 阶段 9: 动态路由 (Rewrite Tag) - 第二跳:按服务分流
    # 针对特定环境的特定容器进行二次分流,实现细粒度索引隔离
    # =======================================================================

    # 示例:UAT 环境 -> lms-backend 服务
    [FILTER]
        Name                rewrite_tag
        Match               uganda-uat.temp
        Rule                $kubernetes['container_name'] ^lms-backend$ uganda-uat-lms-backend false
        Emitter_Name        re_emitted_uganda-uat-lms-backend

    # 示例:UAT 环境 -> other-service 服务 (需补充完整配置)
    [FILTER]
        Name                rewrite_tag
        Match               uganda-uat.temp
        Rule                $kubernetes['container_name'] ^other-service$ uganda-uat-other-service false
        Emitter_Name        re_emitted_uganda-uat-other-service

    # 注意:Production 和其他环境需要类似地添加对应的容器名过滤规则
    # 例如:
    # [FILTER]
    #     Name rewrite_tag
    #     Match uganda-prod.temp
    #     Rule $kubernetes['container_name'] ^payment-service$ uganda-prod-payment-service false
    #     Emitter_Name re_emitted_uganda-prod-payment-service

    # =======================================================================
    # [OUTPUT] 输出插件配置
    # 将经过路由筛选的日志发送到 OpenObserve
    # =======================================================================

    # 输出:UAT LMS Backend
    [OUTPUT]
        Name                http
        Match               uganda-uat-lms-backend
        URI                 /api/39NVPcXSEBOwGM5UnceQ35hQFNB/lms_backend/_json
        Host                openobserve.uganda-uat.svc.cluster.local
        Port                5080
        tls                 Off
        Format              json
        Json_date_key       _timestamp
        Json_date_format    iso8601
        
        HTTP_User           [email protected]
        HTTP_Passwd         ROPe50N4BJjovJiT 
        
        compress            gzip
        Retry_Limit         False       # 无限重试直到成功
        net.connect_timeout 10
        net.io_timeout      30

    # 输出:UAT Other Service (示例)
    [OUTPUT]
        Name                http
        Match               uganda-uat-other-service
        URI                 /api/39NVPcXSEBOwGM5UnceQ35hQFNB/other_service/_json
        Host                openobserve.uganda-uat.svc.cluster.local
        Port                5080
        tls                 Off
        Format              json
        Json_date_key       _timestamp
        Json_date_format    iso8601
        HTTP_User           [email protected]
        HTTP_Passwd         ROPe50N4BJjovJiT
        compress            gzip
        Retry_Limit         False

    # 默认输出 (可选): 捕获未匹配任何特定规则的日志,防止数据丢失
    # [OUTPUT]
    #     Name http
    #     Match uganda-prod.temp
    #     URI /api/.../default/_json
    #     ...

配置说明

  1. Filter 阶段: 使用 rewrite_tag 过滤器按 Namespace 和容器名称过滤日志
  2. Output 阶段: 将过滤后的日志通过 HTTP 发送到不同环境下的OpenObserve
  3. 多行日志: 使用 multiline 过滤器处理 Java 异常堆栈
  4. 日志优化: gzip 压缩、失败重试、TCP 超时配置

多行合并规则说明

状态 匹配规则 说明
start_state /^\d{4}-\d{2}-\d{2}/ 以日期开头 → 新日志开始,进入 cont 状态
cont /^(?!\d{4}-\d{2}-\d{2}).+/ 不以日期开头 → 继续拼接到上一条日志

注意: 修改完 ConfigMap 后需要重启 fluent-bit 服务

数据管理

创建数据流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 方法1:通过API创建
curl -X POST "https://logs.example.com/api/demo/streams" \
  -u "[email protected]:password" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "app-logs",
    "storage_type": "memory",
    "stream_type": "logs"
  }'

# 方法2:自动创建(首次写入自动创建,我们这里采用这种方式)
# Fluent Bit写入新stream会自动创建

设置数据保留策略

openobserve的dashboard里可以基于索引设置保留策略,主要策略如下:

  • 基于时间保留
  • 基于大小保留
  • 混合策略(3650天或30TB,先到为准,这里我们采用这种方式,由于业务为金融服务,故设置保留10年)

数据归档配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 启用S3归档
env:
  - name: ZO_S3_STORE_ENABLED
    value: "true"
  - name: ZO_S3_STORE_BUCKET
    value: "logs-archive"
  - name: ZO_S3_STORE_REGION
    value: "us-east-1"
  - name: ZO_S3_STORE_ACCESS_KEY
    valueFrom:
      secretKeyRef:
        name: s3-credentials
        key: access-key
  - name: ZO_S3_STORE_SECRET_KEY
    valueFrom:
      secretKeyRef:
        name: s3-credentials
        key: secret-key

数据压缩

1
2
3
4
5
6
7
8
# OpenObserve自动压缩
env:
  - name: ZO_COMPRESSION_ENABLED
    value: "true"
  - name: ZO_COMPRESSION_FORMAT
    value: "zstd"  # zstd/gzip/snappy
  - name: ZO_COMPRESSION_LEVEL
    value: "3"  # 1-19,越高压缩率越高但CPU消耗越大

查询与分析

OpenObserve 提供强大的查询引擎,支持 SQL 模式 和 原生查询语言 (VQL)。以下介绍基本查询和聚合查询在不同场景下使用,帮助您快速定位关键日志。

💡 核心提示

  • 模式切换:聚合查询建议在 SQL 模式 下执行。
  • 表名规范:Stream 名称(表名)必须用双引号包裹,例如 "lms"
  • 时间范围:所有查询均受 UI 右上角时间选择器的限制,请确保时间范围覆盖目标数据。

快速定位单条日志或特定事件。

1.1 全文索引搜索

自动扫描所有文本字段,适合模糊查找。

1
match_all('timeout')
  • 场景:不确定错误出现在哪个字段,快速全局搜索关键词。

1.2 指定字段搜索

针对特定字段进行精确或包含匹配,性能更优。

1
str_match(log, 'tenant-backend')
  • 场景:筛选特定服务(如 tenant-backend)或特定级别(str_match(level, 'ERROR'))的日志。

1.3 多条件组合查询

使用逻辑运算符 (AND, OR, NOT) 构建复杂过滤规则。

1
2
(level = 'INFO' OR level = 'ERROR') 
AND str_match(log, 'AuthInterceptor')
  • 场景:只看认证模块的日志,且包含正常流程和报错流程。

2. 聚合查询 (Aggregation Queries) 🚀

通过 SQL 的 GROUP BY 和聚合函数 (COUNT, SUM, AVG, MAX, MIN),将海量日志转化为统计图表数据。

2.1 按日志级别统计 (Count by Level)

统计不同日志级别的数量,快速判断系统健康度。

1
2
3
4
SELECT level, COUNT(*) as log_count 
FROM "lms" 
GROUP BY level 
ORDER BY log_count DESC
  • 输出示例
    level log_count
    INFO 15420
    ERROR 32
    WARN 105

2.2 按时间窗口趋势分析 (Time Series Histogram)

结合 date_trunc 函数,统计单位时间内的日志量,用于绘制趋势图。

1
2
3
4
5
SELECT date_trunc('minute', _timestamp) as time_window, COUNT(*) as hits
FROM "lms"
WHERE str_match(log, 'payment')
GROUP BY time_window
ORDER BY time_window ASC
  • 场景:观察“支付”相关日志在每分钟内的波动情况,定位突发流量或故障时间点。
  • 注意'minute' 可替换为 'hour', 'day' 等。

2.3 按服务/容器分组统计 (Group by Service)

分析哪个微服务产生的日志最多或错误最多。

1
2
3
4
5
6
SELECT kubernetes.container_name, COUNT(*) as total_logs
FROM "lms"
WHERE level = 'ERROR'
GROUP BY kubernetes.container_name
ORDER BY total_logs DESC
LIMIT 5
  • 场景:找出当前产生错误最多的前 5 个容器,优先排查。

2.4 多维度交叉分析 (Multi-Dimension Analysis)

同时按两个或多个维度分组,进行深度下钻分析。

1
2
3
4
5
6
7
8
SELECT 
  date_trunc('hour', _timestamp) as hour,
  level,
  COUNT(*) as count
FROM "lms"
WHERE str_match(log, 'database')
GROUP BY hour, level
ORDER BY hour ASC, level
  • 场景:查看每小时中,数据库相关日志的 INFOERROR 分布变化。

2.5 数值字段统计 (Numeric Aggregation)

如果日志中包含提取出的数值字段(如响应时间 duration),可进行数学计算。

1
2
3
4
5
6
SELECT 
  AVG(duration) as avg_latency,
  MAX(duration) as max_latency,
  MIN(duration) as min_latency
FROM "lms"
WHERE str_match(log, 'API_REQUEST')
  • 场景:计算特定 API 请求的平均耗时、最大耗时,评估性能瓶颈。

3. 高级技巧与最佳实践

技巧 说明 示例
限制结果集 避免返回过多数据导致浏览器卡顿 添加 LIMIT 100
去重统计 统计唯一的错误消息种类 COUNT(DISTINCT log)
别名优化 让输出列名更易读 COUNT(*) as "错误总数"
空值处理 排除字段为空的记录 WHERE level IS NOT NULL
正则匹配 str_match 更灵活的匹配 REGEXP_MATCH(log, 'Error.*\d+')

⚠️ 性能建议

  1. 先过滤后聚合:务必在 WHERE 子句中尽可能缩小数据范围(如指定时间、关键字),再进行 GROUP BY,能显著提升查询速度。
  2. 时间粒度:在大时间范围(如 7 天)查询时,使用 date_trunc('hour', ...)date_trunc('day', ...),避免使用 'second' 导致数据点过多。
  3. 字段索引:对常用于 GROUP BY 的字段(如 level, container_name),建议在 Stream Settings 中开启索引以获得最佳性能。

OpenObserve优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 内存缓存
env:
  - name: ZO_MEMORY_CACHE_ENABLED
    value: "true"
  - name: ZO_MEMORY_CACHE_MAX_SIZE
    value: "8192"  # 8GB

# 查询缓存
  - name: ZO_QUERY_CACHE_ENABLED
    value: "true"
  - name: ZO_QUERY_CACHE_MAX_SIZE
    value: "4096"  # 4GB

# 并行查询
  - name: ZO_QUERY_WORKER_THREADS
    value: "8"

存储优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Parquet列式存储
env:
  - name: ZO_PARQUET_COMPRESSION
    value: "zstd"
  - name: ZO_PARQUET_ROW_GROUP_SIZE
    value: "1048576"  # 1MB

# 数据分区
  - name: ZO_PARTITION_ENABLED
    value: "true"
  - name: ZO_PARTITION_FIELDS
    value: "_timestamp,kubernetes.namespace_name"

监控告警

这里有两个部分需要做监控,一部分是Openobserve本身服务监控,一部分是业务日志监控,对于Openobseve和fluentbit服务本身的监控直接采用Prometheus监控即可,对于核心业务订单日志监控直接采用Openobserve自带告警机制即可满足。

Prometheus指标采集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Fluent Bit ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: fluent-bit
  endpoints:
    - port: metrics
      interval: 30s

# OpenObserve ServiceMonitor
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: openobserve
  namespace: logging
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: openobserve
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

告警规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: logging-alerts
  namespace: logging
spec:
  groups:
    - name: logging
      rules:
        # Fluent Bit日志丢失告警
        - alert: FluentBitLogsDropping
          expr: rate(fluentbit_output_proc_records_failed_total[5m]) > 100
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Fluent Bit日志丢失"
            description: "过去5分钟丢失{{ $value }}条日志"

        # OpenObserve存储告警
        - alert: OpenObserveDiskSpaceLow
          expr: (node_filesystem_avail_bytes{mountpoint="/data"} / node_filesystem_size_bytes{mountpoint="/data"}) < 0.1
          for: 10m
          labels:
            severity: critical
          annotations:
            summary: "OpenObserve存储空间不足"
            description: "剩余空间不足10%"

        # 日志摄入延迟
        - alert: LoggingIngestLag
          expr: openobserve_ingest_delay_seconds > 300
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "日志摄入延迟"
            description: "日志延迟{{ $value }}秒"

业务日志告警

涉及如下几个关键部分:

告警消息模版

设置–> 模版 –>添加模版,定义Webhook告警消息格式, 注意这里的消息需要根据不同的聊天工具

1
2
3
4
5
6
7
8
新建\:cdi-prod-template

    {
      "msgtype": "text",
      "text": {
        "content": "🚨 【告警通知】\n告警名称: {alert_name}\n流名称: {stream_name}\n严重程度: Error\n日志信息: {rows:1}\n触发时间: {alert_trigger_time_str}"
      }
    }

注意: 这里的消息需要根据不同的聊天工具配置有差异,我这里的配置是企业微信配置,更多请参考https://openobserve.ai/docs/user-guide/management/templates/ 这里配置自己适合的告警模版

告警地址

通过企业微信群组添加消息助手,获取推送消息Webhook地址,然后Openobserve UI 设置–> 地址 –>添加地址即可.

告警规则配置

这个需要根据内部研发团队沟通,业务日志匹配规则,日常告警只匹配Erorr级别即可,对于核心订单和风控流程建议添加上电话告警:

openobserve告警

OpenObserve开源版风险与注意事项

RBAC权限控制

开源版限制

  • 无细粒度的RBAC控制(企业版支持)
  • 所有用户拥有相同权限
  • 无法按组织/项目隔离数据

解决方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 1. 使用多租户(Stream级别隔离)
# 为每个团队创建独立的Stream
curl -X POST "https://logs.example.com/api/demo/streams" \
  -d '{"name":"team-a-logs"}'
curl -X POST "https://logs.example.com/api/demo/streams" \
  -d '{"name":"team-b-logs"}'

# 2. 使用反向代理做权限控制
# Nginx根据用户路由到不同Stream
location /api/team-a/ {
  internal;
  proxy_pass http://openobserve.logging.svc/api/team-a-logs/;
}

# 3. 使用API Key做简单鉴权
# 为每个团队生成独立的API Key

这里由于项目比较多,通常在反向代理添加权限控制规则,比如限制Post请求的IP、和Host

数据安全

风险点

  • 开源版无字段级加密
  • 敏感信息可能明文存储
  • 缺少审计日志

最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. Fluent Bit脱敏
[FILTER]
    Name                modify
    Match               kube.*
    Remove              password,token,secret,key,ssn
    Rename              message  log_message

# 2. 使用Lua过滤器脱敏
[FILTER]
    Name                lua
    Match               kube.*
    Script              mask.lua
    Call                mask_sensitive

# mask.lua:
function mask_sensitive(tag, timestamp, record)
    if record["message"] ~= nil then
        record["message"] = string.gsub(record["message"], "password=%S+", "password=***")
    end
    return 1, timestamp, record
end

高可用风险

单点故障

  • 开源版无自动故障转移
  • 节点宕机可能导致数据丢失

解决方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 1. 部署多副本
replicaCount: 3

# 2. 使用共享存储
persistence:
  enabled: true
  storageClass: "nfs-client"  # 使用分布式存储,我这里底层采用的Ceph的RBD

# 3. 配置Pod反亲和性
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
            - key: app.kubernetes.io/name
              operator: In
              values:
                - openobserve
        topologyKey: kubernetes.io/hostname

容量规划

日志量 Fluent Bit资源 OpenObserve资源 存储空间/月
10GB/天 100m CPU / 100Mi 2 CPU / 4Gi 100GB
100GB/天 200m CPU / 200Mi 4 CPU / 8Gi 1TB
1TB/天 500m CPU / 500Mi 8 CPU / 16Gi 10TB

总结

基于OpenObserve和Fluent Bit的日志平台方案具有以下优势:

  1. 成本优势:相比ELK方案,存储成本降低90%,计算资源降低70%
  2. 高性能:单节点支持5TB+/天日志摄入,查询响应<100ms
  3. 简单易用:部署时间<30分钟,学习成本低
  4. 云原生:Kubernetes原生集成,支持自动扩缩容

已在生产环境稳定运行半年,数据量5TB+,查询响应稳定在200ms以内,基本满足了大部分业务需求,如果对RBAC有强需求的建议直接购买商业版或者放弃本方案。