{SITE.title} logo 云萱笔记
可观测性 · · 4 分钟阅读

监控告警体系设计实践:从 ELK 到 Prometheus

大家好,我是云萱。

监控和告警是 DevOps 的基本功,但很多团队的监控体系都有问题——要么是告警太多没人看,要么是出了问题告警没响。“监控告警”四个字说起来简单,做好其实不容易。

这篇文章把我整理的监控告警相关知识分享出来,从架构设计到告警策略,再到实战中的日志监控机器人,尽量覆盖全面。


一、可观测性的三大支柱

可观测性(Observability)不是只有监控,它有三大支柱:

作用典型工具数据特点
Logging(日志)记录具体事件,用于排查问题ELK、Loki非结构化/半结构化,量大
Metrics(指标)聚合统计,用于监控告警Prometheus、InfluxDB结构化,数值型,相对量小
Tracing(链路追踪)请求全链路追踪,性能分析Jaeger、Zipkin、SkyWalking结构化,带 trace ID

三者是互补关系,不是替代关系。

排查问题的典型路径:

告警触发(Metrics)
  → 定位到某个服务/接口(Tracing)
    → 查看具体错误日志(Logging)

很多团队一开始只做了日志和指标,忽略了链路追踪。等服务多了之后,排查问题就像大海捞针——你知道有问题,但不知道是哪个环节出的。


二、日志系统架构(ELK)

经典 ELK 架构

应用 → Filebeat → Logstash → Elasticsearch → Kibana
              ↓(可选)
           Kafka / Redis(缓冲层,防止日志洪峰)

各组件作用:

  • Filebeat:轻量日志采集器,部署在每台机器上,收集日志发往 Logstash 或 ES
  • Logstash:日志处理管道,过滤、解析、转换日志格式
  • Elasticsearch:分布式搜索引擎,存储和索引日志
  • Kibana:可视化界面,搜索、分析、仪表盘
  • Kafka / Redis(可选):缓冲层,当日志量很大的时候用,防止 ES 被打垮

日志采集最佳实践

1. 结构化日志优先 尽量输出 JSON 格式的日志,而不是纯文本。结构化日志查询和统计起来方便太多了。

不好的格式:

[2024-01-01 12:00:00] ERROR user-service login failed user=123 ip=1.2.3.4 reason=wrong_password

好的格式:

{
  "timestamp": "2024-01-01T12:00:00Z",
  "level": "ERROR",
  "service": "user-service",
  "action": "login",
  "user_id": 123,
  "client_ip": "1.2.3.4",
  "reason": "wrong_password"
}

2. 统一日志格式 所有服务的日志字段要统一(时间、级别、服务名、trace_id 等),不然后面查日志的时候很痛苦。

3. 日志分级

  • DEBUG:开发调试用,生产环境关闭
  • INFO:正常业务日志
  • WARN:警告,不影响运行但需要关注
  • ERROR:错误,需要处理
  • FATAL:致命错误,服务不可用

4. 不要打敏感信息 密码、身份证、银行卡号这些,打日志前一定要脱敏。

ES 索引模板设计

建议按日期分索引(比如 app-logs-2024.01.01),方便管理和清理。

索引模板里要注意:

  • 关键字段(level、service、trace_id)设为 keyword 类型,方便聚合
  • message 字段用 text + keyword 双类型(既可以全文检索又可以聚合)
  • 设置合理的分片数和副本数
  • 配合 ILM(索引生命周期管理)自动滚动和删除过期索引

三、指标体系(Prometheus)

四种指标类型

类型含义例子
Counter只增不减的计数器请求总数、错误数、处理字节数
Gauge可增可减的瞬时值CPU 使用率、内存使用率、队列长度
Histogram直方图,统计分布请求延迟分布、响应大小分布
Summary类似 Histogram,直接计算分位数客户端计算分位数

面试常考:Histogram 和 Summary 的区别?

  • Histogram 在服务端统计分位数,占存储空间,但可以聚合
  • Summary 在客户端统计分位数,省存储空间,但不能聚合
  • 大多数场景推荐用 Histogram

黄金指标(Four Golden Signals)

Google SRE 提出的四个核心监控指标:

  1. 延迟(Latency) - 处理请求花了多久
  2. 流量(Traffic) - 每秒多少请求
  3. 错误率(Errors) - 失败请求占比
  4. 饱和度(Saturation) - 资源使用了多少(CPU、内存、磁盘)

把这四个指标监控到位,系统的健康状况基本就有数了。

RED 方法(微服务场景)

针对微服务的每一个服务,监控三个指标:

  • Rate:请求速率
  • Errors:错误率
  • Duration:请求耗时

比 USE 方法(使用率/饱和度/错误,适合资源监控)更适合服务监控。


四、告警体系设计

很多团队的告警是”拍脑袋”加的——看到一个指标就加个告警。结果告警泛滥,大家都麻木了,真正重要的告警反而被淹没。

告警分级策略

我推荐 P0-P3 四级告警:

级别响应时间通知方式示例处理原则
P0 - 紧急立即(5 分钟内)电话 + 短信 + IM整个系统不可用、数据丢失立即处理,不分昼夜
P1 - 严重15 分钟内IM + 电话核心功能不可用、大量用户影响工作时间立即,非工作时间尽快
P2 - 一般1 小时内IM非核心功能异常、性能下降工作时间处理
P3 - 提示工作日处理邮件 / 工单磁盘使用率预警、日志告警有空再看

原则:告警级别要和实际影响匹配。 P0/P1 的告警必须”少而准”——每次响都是真的有大事。如果一周响好几个 P1 都是鸡毛蒜皮的事,下次真出大事就没人信了。

避免告警风暴

告警风暴是最常见的问题。几个解决思路:

1. 告警抑制(Deduplication) 同一类告警,短时间内只发一次。比如同一个错误 1 分钟内出现 100 次,只发 1 次告警。

2. 告警聚合(Aggregation) 同一服务的多个告警聚合成一条。比如一个服务的 10 个 Pod 同时挂了,发一条”服务 X 有 10 个 Pod 不可用”的告警,而不是 10 条。

3. 告警静默(Silence) 已知的维护窗口,提前设置静默,避免骚扰。

4. 告警分级 前面说的 P0-P3,不同级别不同处理方式。P3 的告警不应该半夜把人叫起来。

5. 自动恢复通知 故障恢复了也要通知。不然你爬起来处理了半天,结果人家自己好了。

告警设计原则

  • 告警必须可行动:收到告警之后要知道”该做什么”。如果一个告警收到之后只能”先看看”,那这个告警就不应该存在,或者级别不够。
  • 告警要有 owner:每个告警都要明确谁负责。没人负责的告警 = 没人管的告警。
  • 定期清理失效告警:系统改了,告警也要跟着改。不然过半年告警列表里一半都是没用的。

五、实战:Python 日志监控机器人

光有 ELK 还不够——有时候你需要对特定日志做实时监控和告警。我用 Python 写过一个日志监控机器人,分享一下思路。

核心功能

  1. 实时监控日志文件(类似 tail -f)
  2. 多规则匹配(关键词、正则、日志级别)
  3. 告警去重与抑制(防止告警风暴)
  4. 多通道推送(企业微信、钉钉、飞书、邮件)
  5. 支持从 Elasticsearch 查询历史日志

关键模块设计

1. 告警去重器

核心思路:对同一种告警(规则名 + 相似内容),在一个时间窗口内只发 N 次。

class AlertDeduplicator:
    def __init__(self, dedup_window=300, max_alerts=5):
        self.dedup_window = dedup_window  # 去重窗口(秒)
        self.max_alerts = max_alerts      # 窗口内最多告警次数
        self.alert_history = defaultdict(deque)

    def should_alert(self, rule_name, content):
        fp = self._fingerprint(rule_name, content)
        now = time.time()
        cutoff = now - self.dedup_window
        
        # 清理过期记录
        history = self.alert_history[fp]
        while history and history[0] < cutoff:
            history.popleft()
        
        if len(history) < self.max_alerts:
            history.append(now)
            return True, len(history)
        else:
            return False, len(history)

2. 日志解析器

支持多种日志格式(nginx、java、通用等),用正则把非结构化日志解析成结构化数据。

3. tail -f 实现原理

# 核心就是:定期检查文件大小和 inode
while True:
    line = f.readline()
    if line:
        process(line)
    else:
        time.sleep(1)
        # 检查文件是否被轮转了(inode 变了)
        new_inode = os.stat(filename).st_ino
        if new_inode != current_inode:
            f.close()
            f = open(filename)
            current_inode = new_inode

4. 告警分级推送

不同级别走不同通道:

  • P0:电话 + 短信 + IM
  • P1:IM + 电话
  • P2:IM
  • P3:邮件/工单

完整的脚本在我的 DevOps 面试脚本合集中,感兴趣的可以去看 09-python/log-monitor.py。


六、Prometheus 告警规则示例

给大家看一组实际生产中用的告警规则,按类别分:

系统级别(主机)

groups:
  - name: system
    rules:
      - alert: HostHighCPU
        expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: P2
        annotations:
          summary: "主机 CPU 使用率过高"
          description: "{{ $labels.instance }} CPU 使用率 {{ $value | printf \"%.2f\" }}%"

      - alert: HostHighMemory
        expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
        for: 5m
        labels:
          severity: P2

      - alert: HostDiskAlmostFull
        expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 > 85
        for: 5m
        labels:
          severity: P1

应用级别

  - name: application
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 2m
        labels:
          severity: P1
        annotations:
          summary: "服务错误率过高"
          description: "{{ $labels.service }} 错误率 {{ $value | printf \"%.2f\" }}%"

      - alert: HighLatencyP95
        expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 3
        for: 5m
        labels:
          severity: P2

      - alert: PodCrashLooping
        expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 15 > 3
        for: 0m
        labels:
          severity: P1

Kubernetes 级别

  - name: kubernetes
    rules:
      - alert: NodeNotReady
        expr: kube_node_status_condition{condition="Ready",status="true"} == 0
        for: 5m
        labels:
          severity: P1

      - alert: DeploymentNotAvailable
        expr: kube_deployment_status_available_replicas < kube_deployment_spec_replicas
        for: 10m
        labels:
          severity: P2

数据库级别

  - name: database
    rules:
      - alert: MySQLDown
        expr: mysql_up == 0
        for: 1m
        labels:
          severity: P0

      - alert: MySQLSlowQueries
        expr: rate(mysql_slow_queries_total[5m]) > 10
        for: 5m
        labels:
          severity: P2

      - alert: MySQLReplicationLag
        expr: mysql_slave_status_seconds_behind_master > 60
        for: 2m
        labels:
          severity: P1

七、面试中常见问题

Q: 你们的监控体系是怎么设计的?

按照三大支柱来设计:

  • Metrics 用 Prometheus + Grafana,做指标监控和告警
  • Logging 用 Filebeat + Kafka + Logstash + Elasticsearch + Kibana,做日志搜索分析
  • Tracing 用 Jaeger,做分布式链路追踪

告警用 Alertmanager,做告警分级、去重、聚合、路由。

Q: 告警风暴怎么处理?

  1. 告警分级(P0-P3),不同级别不同响应
  2. 告警去重:同一窗口内相同告警只发几次
  3. 告警聚合:同一服务的多个问题聚合成一条
  4. 告警静默:维护期间提前静默
  5. 精简告警:只保留可行动的告警,没用的都删掉

Q: ELK 性能优化有哪些?

  • 写入端:使用 Kafka 做缓冲,批量写入,用 SSD
  • 索引:按日期分索引,合理设置分片数,mapping 设计好(不要的字段不索引)
  • 查询:尽量用 filter 上下文(走缓存),避免大范围查询
  • 集群:节点角色分离(master/data/ingest),冷热分层存储
  • 生命周期管理:ILM 自动滚动、收缩、删除

Q: Prometheus 怎么实现高可用?

几种方案:

  1. 联邦集群(Federation):多个 Prometheus 实例,上层聚合
  2. 主从复制:Thanos / VictoriaMetrics
  3. 直接用 VictoriaMetrics 替代 Prometheus(兼容 PromQL,性能更好)

Alertmanager 本身就是高可用的,多个实例之间通过 gossip 同步告警状态。


写在最后

监控告警这个事,核心不是”装了多少工具”,而是”出了问题能不能及时发现、快速定位”。

我见过太多团队,监控工具一大堆,告警满天飞,真出了故障还是靠人肉排查。这样的监控体系是失败的。

好的监控体系应该是:

  • 少而准的告警 - 每次响都值得爬起来处理
  • 清晰的排查路径 - 从告警到根因的路径短
  • 完善的仪表盘 - 看一眼就知道系统状态
  • 持续迭代优化 - 每次故障后复盘,补监控、加告警、优化流程

监控不是一次性的事,是要持续打磨的。

希望这篇文章对你有帮助~