系统监控与可观测性踩坑记录
系统监控与可观测性我没按教科书顺序做。
先解决眼前的阻塞,再回头补原理。
最初的状态
刚开始的监控方式很原始:
- 手工写脚本定期检查服务状态
- 错误日志记录在本地文件
- 出问题了看日志,日志满了就删
- 没有告警,靠用户投诉才知道有问题
问题很明显:
- 故障发现不及时,用户先投诉
- 排查困难,日志分散在多台机器
- 没有历史数据,无法分析趋势
- 告警混乱,该告警的不告警,不该告警的一直告
第一步:基础监控
先解决最基本的"知道挂了没"的问题。
服务健康检查
# 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 查看完整的调用链,清楚看到哪个服务最慢、哪个节点有问题。
第五步:告警配置
有了监控数据,需要告警来及时发现问题。
告警规则
# 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"
告警级别
踩过的坑
坑一:告警泛滥
刚开始告警规则配得很激进,CPU > 80% 就告警,结果半夜一直被吵醒。
后来发现 CPU 短时间超过 80% 是正常的,关键是要看持续时间和趋势。
解决:
- 告警阈值留余地,不要设太紧
- 增加持续时间判断,避免瞬时抖动
- 告警聚合,相关问题合并成一个
坑二:日志太多
一开始记录所有日志,结果日志量太大,查询慢,存储也不够。
解决:
- 调整日志级别,生产环境不用 DEBUG
- 关键日志单独记录,便于查询
- 定期归档和清理旧日志
坑三:指标命名不规范
不同服务的指标命名不一致,查询和聚合很困难。
解决:
- 统一命名规范,如
http_requests_total而不是request_count - 使用标签(label)区分不同维度
- 文档化,确保团队理解
什么时候该上可观测性
值得做的场景:
- 生产环境、对稳定性要求高
- 微服务架构,调用链复杂
- 用户量大、业务复杂
- 团队有一定运维能力
不值得做的场景:
- 开发环境、测试环境
- 简单应用、单体架构
- 小团队、预算有限
- 对稳定性要求不高
写在最后
监控和可观测性这东西,不是有了工具就完事了。
关键是怎么用这些工具发现问题、定位问题、预防问题。指标、日志、链路追踪,三者要配合使用,才能真正理解系统发生了什么。
但也不是一开始就上全栈监控。先解决最基础的问题,再逐步完善。过度建设反而会成为负担。
这次监控体系升级花了半年,中间走过弯路。但回头看,从"靠用户投诉发现故障"到"提前预警",这个变化确实值得。
版权声明: 本文首发于 指尖魔法屋-系统监控与可观测性踩坑记录(https://blog.thinkmoon.cn/post/18-monitoring-observability-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。