系统监控与可观测性踩坑记录

系统监控与可观测性我没按教科书顺序做。

先解决眼前的阻塞,再回头补原理。

最初的状态

刚开始的监控方式很原始:

  • 手工写脚本定期检查服务状态
  • 错误日志记录在本地文件
  • 出问题了看日志,日志满了就删
  • 没有告警,靠用户投诉才知道有问题

问题很明显:

  • 故障发现不及时,用户先投诉
  • 排查困难,日志分散在多台机器
  • 没有历史数据,无法分析趋势
  • 告警混乱,该告警的不告警,不该告警的一直告

第一步:基础监控

先解决最基本的"知道挂了没"的问题。

服务健康检查

# health_check.py
import requests
import time

def check_service(url, name):
    try:
        response = requests.get(url, timeout=5)
        if response.status_code == 200:
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {name}: OK")
            return True
        else:
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {name}: FAILED ({response.status_code})")
            return False
    except Exception as e:
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {name}: ERROR ({str(e)})")
        return False

services = {
    'web': 'http://localhost:8080/health',
    'api': 'http://localhost:8081/health',
    'db': 'http://localhost:5432'  # 假设有健康检查端点
}

while True:
    for name, url in services.items():
        check_service(url, name)
    time.sleep(60)

配合 crontab 定时执行,基础监控就搞定了。

系统资源监控

# 使用监控代理(node_exporter)
curl https://github.com/prometheus/node_exporter/releases/download/v1.6.0/node_exporter-1.6.0.linux-amd64.tar.gz
tar xvf node_exporter-1.6.0.linux-amd64.tar.gz
cd node_exporter-1.6.0.linux-amd64
./node_exporter --web.listen-address=:9100

node_exporter 会暴露 /metrics 端点,Prometheus 可以拉取这些指标。

第二步:引入 Prometheus

手工监控毕竟有限,上了 Prometheus 之后情况好多了。

Prometheus 安装

# Docker 安装
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

基础配置

# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

  - job_name: 'web_app'
    static_configs:
      - targets: ['localhost:8080']
    metrics_path: '/metrics'

应用埋点

from prometheus_client import Counter, Histogram, start_http_server

# 定义指标
request_count = Counter('http_requests_total', 'Total HTTP requests', ['method', 'endpoint'])
request_duration = Histogram('http_request_duration_seconds', 'HTTP request duration')

# 中间件
@app.middleware
async def metrics_middleware(request, call_next):
    start_time = time.time()
    response = await call_next(request)

    # 记录指标
    request_count.labels(method=request.method, endpoint=request.url.path).inc()
    request_duration.observe(time.time() - start_time)

    return response

第三步:日志收集

系统日志、应用日志分散在各处,查问题很不方便。

ELK Stack

# docker-compose.yml
version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"

  logstash:
    image: docker.elastic.co/logstash/logstash:8.5.0
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    ports:
      - "5044:5044"

  kibana:
    image: docker.elastic.co/kibana/kibana:8.5.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"

结构化日志

import json
import logging
from datetime import datetime

class StructuredLogger:
    def __init__(self, service_name):
        self.service_name = service_name

    def log(self, level, message, **context):
        log_entry = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': level,
            'service': self.service_name,
            'message': message,
            **context
        }
        print(json.dumps(log_entry))

logger = StructuredLogger('web-api')

# 使用
logger.log('INFO', 'User login', user_id=123, ip='192.168.1.1')

结构化日志的好处是便于查询和分析,Kibana 可以直接过滤。

第四步:链路追踪

微服务架构下,一个请求会经过多个服务,出问题时很难定位。

集成 OpenTelemetry

from opentelemetry import trace
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.jaeger.thrift import JaegerExporter

# 配置 Tracer
trace.set_tracer_provider(TracerProvider())
jaeger_exporter = JaegerExporter(
    agent_host_name="localhost",
    agent_port=6831,
)
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(jaeger_exporter)
)

# 自动埋点
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)

链路查询

在 Jaeger UI 里,可以通过 Trace ID 查看完整的调用链,清楚看到哪个服务最慢、哪个节点有问题。

sequenceDiagram autonumber participant User as 用户 participant Gateway as 网关 participant Auth as 认证服务 participant UserSvc as 用户服务 participant OrderSvc as 订单服务 User->>Gateway: HTTP请求 Gateway->>Gateway: 创建 Trace ID Gateway->>Auth: 验证令牌<br/>传递 Trace ID Auth->>Auth: Span: 认证 Auth-->>Gateway: 认证结果 Gateway->>UserSvc: 获取用户信息<br/>传递 Trace ID UserSvc->>UserSvc: Span: 查询用户 UserSvc-->>Gateway: 用户信息 Gateway->>OrderSvc: 创建订单<br/>传递 Trace ID OrderSvc->>OrderSvc: Span: 创建订单 OrderSvc->>OrderSvc: Span: 库存检查 OrderSvc->>OrderSvc: Span: 支付处理 OrderSvc-->>Gateway: 订单结果 Gateway-->>User: HTTP响应

第五步:告警配置

有了监控数据,需要告警来及时发现问题。

告警规则

# alert_rules.yml
groups:
  - name: web_app_alerts
    interval: 30s
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High error rate detected"
          description: "Error rate is {{ $value }} errors/sec"

      - alert: ServiceDown
        expr: up{job="web_app"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service is down"
          description: "Service {{ $labels.instance }} has been down for more than 1 minute"

告警级别

graph TB subgraph 告警级别 A[P1 严重告警<br/>服务不可用] A --> A1[影响所有用户] A --> A2[需要立即处理] A --> A3[电话/短信通知] B[P2 高级告警<br/>服务降级] B --> B1[影响部分用户] B --> B2[需要快速处理] B --> B3[邮件/即时消息通知] C[P3 中级告警<br/>性能异常] C --> C1[可能影响用户] C --> C2[需要调查] C --> C3[邮件通知] D[P4 低级告警<br/>预警信息] D --> D1[暂不影响用户] D --> D2[需要关注] D --> D3[定期汇总] end style A fill:#FF0000,color:#FFFFFF style B fill:#FF6B6B style C fill:#FFD700 style D fill:#90EE90

踩过的坑

坑一:告警泛滥

刚开始告警规则配得很激进,CPU > 80% 就告警,结果半夜一直被吵醒。

后来发现 CPU 短时间超过 80% 是正常的,关键是要看持续时间和趋势。

解决

  • 告警阈值留余地,不要设太紧
  • 增加持续时间判断,避免瞬时抖动
  • 告警聚合,相关问题合并成一个

坑二:日志太多

一开始记录所有日志,结果日志量太大,查询慢,存储也不够。

解决

  • 调整日志级别,生产环境不用 DEBUG
  • 关键日志单独记录,便于查询
  • 定期归档和清理旧日志

坑三:指标命名不规范

不同服务的指标命名不一致,查询和聚合很困难。

解决

  • 统一命名规范,如 http_requests_total 而不是 request_count
  • 使用标签(label)区分不同维度
  • 文档化,确保团队理解

什么时候该上可观测性

值得做的场景

  • 生产环境、对稳定性要求高
  • 微服务架构,调用链复杂
  • 用户量大、业务复杂
  • 团队有一定运维能力

不值得做的场景

  • 开发环境、测试环境
  • 简单应用、单体架构
  • 小团队、预算有限
  • 对稳定性要求不高

写在最后

监控和可观测性这东西,不是有了工具就完事了。

关键是怎么用这些工具发现问题、定位问题、预防问题。指标、日志、链路追踪,三者要配合使用,才能真正理解系统发生了什么。

但也不是一开始就上全栈监控。先解决最基础的问题,再逐步完善。过度建设反而会成为负担。


这次监控体系升级花了半年,中间走过弯路。但回头看,从"靠用户投诉发现故障"到"提前预警",这个变化确实值得。

版权声明: 本文首发于 指尖魔法屋-系统监控与可观测性踩坑记录https://blog.thinkmoon.cn/post/18-monitoring-observability-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!