容器监控与日志收集
本文介绍了构建可观测Python应用的实战方案,通过Prometheus+Grafana实现指标监控,EFK(Elasticsearch+Filebeat+Kibana)实现日志收集。文章包含: 容器可观测性的核心概念与三大支柱(指标、日志、追踪) Flask应用代码示例,集成Prometheus指标和JSON格式日志 多阶段Dockerfile构建优化 完整的Docker Compose部署方案
目录
『宝藏代码胶囊开张啦!』—— 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 “白菜价”+“量身定制”!无论是卡脖子的毕设/课设/文献复现,需要灵光一现的算法改进,还是想给项目加个“外挂”,这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网
容器监控与日志收集实战:构建可观测的Python应用
一、引言:容器时代的可观测性挑战
在容器化和微服务架构日益普及的今天,一个应用往往由数十个甚至上百个容器组成,它们动态地运行在集群中,生命周期短暂。当系统出现故障时,传统的“登录服务器查看日志”方式已难以为继:容器随时可能重启或迁移,日志随之消失;多个服务间的调用链错综复杂,问题定位如同大海捞针。
监控与日志收集成为容器化环境下不可或缺的可观测性基石。监控让我们实时了解容器的资源使用、业务指标;日志收集则帮助我们在海量记录中追溯异常现场。然而,如何为容器应用设计合理的监控指标?如何将分散的日志统一收集、存储和分析?如何确保监控系统本身不成为性能瓶颈?
本文将从一个 Python Flask 应用 的实战出发,通过 Prometheus + Grafana 搭建监控体系,通过 Elasticsearch + Filebeat + Kibana(EFK)搭建日志收集体系,并运用多阶段构建优化镜像体积,带你全面掌握容器监控与日志收集的核心实践。
二、核心概念:可观测性的三大支柱
现代可观测性通常包含三个维度:指标(Metrics)、日志(Logs)、追踪(Traces)。本文聚焦前两者。
2.1 指标监控(Metrics)
指标是随时间变化的数值度量,如 CPU 使用率、请求延迟、错误计数。Prometheus 是目前容器监控的事实标准,通过 Pull 模型定期从目标暴露的 HTTP 端点采集指标,存储在时序数据库中,并通过 PromQL 查询。
2.2 日志收集(Logs)
日志是离散的事件记录,描述应用运行时的详细信息。容器日志默认输出到标准输出和标准错误,由 Docker 收集。为了集中管理,需要使用日志采集代理(如 Filebeat、Fluentd)将日志发送到中央存储(如 Elasticsearch),再通过 Kibana 进行可视化查询。
2.3 指标与日志的协同
指标用于快速发现异常(如 500 错误激增),日志用于深入分析异常原因(查看具体错误堆栈)。两者结合,构成完整的可观测性视图。
三、实战演练:构建可观测的 Python 应用
本节将创建一个简单的 Flask 应用,为其添加 Prometheus 指标,并通过 Docker Compose 部署完整的监控和日志栈。
3.1 项目结构
observability-demo/
├── app/
│ ├── app.py # Flask应用,包含指标和日志
│ ├── requirements.txt # Python依赖
│ ├── Dockerfile # 多阶段构建
│ └── .dockerignore
├── docker-compose.yml # 编排所有服务
├── prometheus/
│ └── prometheus.yml # Prometheus配置
├── filebeat/
│ └── filebeat.yml # Filebeat配置
└── .env # 环境变量
3.2 Flask 应用代码
app/app.py:实现一个计数器接口,使用 Prometheus 客户端库暴露指标,并输出 JSON 格式日志。
import os
import time
import random
import logging
import json
from flask import Flask, request, jsonify
from prometheus_client import Counter, Histogram, generate_latest, REGISTRY
from prometheus_client import CONTENT_TYPE_LATEST
# 配置JSON日志
class JsonFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': self.formatTime(record),
'level': record.levelname,
'name': record.name,
'message': record.getMessage(),
'module': record.module,
'funcName': record.funcName,
'lineNo': record.lineno
}
if hasattr(record, 'extra'):
log_record.update(record.extra)
return json.dumps(log_record)
# 设置根日志器
handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger(__name__)
app = Flask(__name__)
# 定义Prometheus指标
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP requests', ['method', 'endpoint', 'status'])
REQUEST_DURATION = Histogram('http_request_duration_seconds', 'HTTP request duration', ['method', 'endpoint'])
@app.before_request
def before_request():
request.start_time = time.time()
@app.after_request
def after_request(response):
# 记录请求时长
duration = time.time() - request.start_time
REQUEST_DURATION.labels(method=request.method, endpoint=request.path).observe(duration)
# 计数
REQUEST_COUNT.labels(method=request.method, endpoint=request.path, status=response.status_code).inc()
# 结构化日志
logger.info('Request processed', extra={
'method': request.method,
'path': request.path,
'status': response.status_code,
'duration': duration,
'ip': request.remote_addr
})
return response
@app.route('/')
def hello():
return jsonify({'message': 'Hello, Observability!'})
@app.route('/counter')
def counter():
# 模拟一些随机延迟
delay = random.uniform(0.1, 0.5)
time.sleep(delay)
# 模拟错误
if random.random() < 0.05:
logger.error('Random error occurred', extra={'path': '/counter'})
return jsonify({'error': 'Internal Server Error'}), 500
return jsonify({'count': 1})
@app.route('/metrics')
def metrics():
"""Prometheus metrics endpoint"""
return generate_latest(REGISTRY), 200, {'Content-Type': CONTENT_TYPE_LATEST}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
app/requirements.txt:
flask==2.3.3
prometheus-client==0.19.0
gunicorn==21.2.0
3.3 多阶段构建 Dockerfile
# app/Dockerfile
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-slim
# 创建非root用户
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --gid 1001 --no-create-home appuser
WORKDIR /app
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
COPY . .
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 5000
HEALTHCHECK CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/')" || exit 1
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
app/.dockerignore:
__pycache__
*.pyc
.env
.git
README.md
Dockerfile
.dockerignore
3.4 Prometheus 配置
prometheus/prometheus.yml:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'flask-app'
static_configs:
- targets: ['app:5000']
metrics_path: /metrics
3.5 Filebeat 配置
filebeat/filebeat.yml:
filebeat.inputs:
- type: container
paths:
- /var/lib/docker/containers/*/*.log
json.keys_under_root: true
json.add_error_key: true
json.message_key: message
output.elasticsearch:
hosts: ['elasticsearch:9200']
username: elastic
password: ${ELASTIC_PASSWORD}
setup.kibana:
host: 'kibana:5601'
3.6 docker-compose.yml(完整编排)
version: '3.8'
services:
# Flask应用
app:
build: ./app
container_name: observability-app
restart: unless-stopped
ports:
- "5000:5000"
networks:
- observability
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# 无需额外卷,日志由Docker收集
# Prometheus监控
prometheus:
image: prom/prometheus:v2.47.0
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "9090:9090"
networks:
- observability
# Grafana可视化
grafana:
image: grafana/grafana:10.2.2
container_name: grafana
restart: unless-stopped
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
networks:
- observability
depends_on:
- prometheus
# Elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: elasticsearch
restart: unless-stopped
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
- xpack.security.enabled=true
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
networks:
- observability
# Kibana
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
container_name: kibana
restart: unless-stopped
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=elastic
- ELASTICSEARCH_PASSWORD=${ELASTIC_PASSWORD}
ports:
- "5601:5601"
networks:
- observability
depends_on:
- elasticsearch
# Filebeat(日志采集)
filebeat:
image: docker.elastic.co/beats/filebeat:8.11.0
container_name: filebeat
restart: unless-stopped
user: root
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- observability
depends_on:
- elasticsearch
- kibana
networks:
observability:
driver: bridge
volumes:
prometheus-data:
grafana-data:
elasticsearch-data:
3.7 环境变量文件(.env)
ELASTIC_PASSWORD=your_elastic_password
3.8 构建与运行
# 创建.env文件并设置密码
echo "ELASTIC_PASSWORD=your_elastic_password" > .env
# 启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f
# 生成一些测试请求
for i in {1..100}; do curl http://localhost:5000/; curl http://localhost:5000/counter; sleep 0.1; done
# 访问服务
# Prometheus: http://localhost:9090
# Grafana: http://localhost:3000 (admin/admin)
# Kibana: http://localhost:5601
四、进阶优化:监控与日志的最佳实践
4.1 指标命名规范
遵循 Prometheus 命名惯例:
- 使用蛇形命名(snake_case)
- 以单位结尾(如
_seconds、_total) - 用标签区分维度,避免指标爆炸
4.2 结构化日志
始终输出 JSON 格式日志,便于 Elasticsearch 自动解析。包含必要字段:timestamp、level、message、service、trace_id 等。
4.3 多阶段构建的收益
对比镜像体积:
| 构建方式 | 基础镜像 | 最终镜像大小 |
|---|---|---|
| 单阶段 | python:3.11 | 912 MB |
| 单阶段 slim | python:3.11-slim | 412 MB |
| 多阶段 | python:3.11-slim | 88 MB |
体积减少 90%,拉取和启动更快,攻击面更小。
4.4 使用 sidecar 模式收集日志
在生产环境(如 Kubernetes)中,通常使用 sidecar 容器专门负责日志采集,与主应用容器共享日志目录,避免日志丢失。
4.5 监控告警集成
在 Prometheus 中配置告警规则,通过 Alertmanager 发送通知到 Slack、钉钉等。例如,当 5xx 错误率超过 1% 时告警。
4.6 日志生命周期管理
在 Elasticsearch 中配置索引生命周期管理(ILM),自动将旧日志转移到冷存储或删除,避免存储膨胀。
五、效果验证
5.1 Prometheus 指标查询
访问 Prometheus UI(http://localhost:9090),执行查询:
rate(http_requests_total[1m])
应看到请求速率曲线。
5.2 Grafana 仪表板
导入 Prometheus 数据源,创建 Dashboard 展示:
- 请求速率(QPS)
- 请求延迟(P99)
- 错误率
- 容器资源使用(需额外配置 cAdvisor)
5.3 Kibana 日志查询
在 Kibana 中创建索引模式(如 filebeat-*),进入 Discover 页面,搜索 level:ERROR,应看到应用产生的随机错误日志。
5.4 结构化日志示例
{
"timestamp": "2024-01-15 10:30:45,123",
"level": "INFO",
"name": "__main__",
"message": "Request processed",
"method": "GET",
"path": "/counter",
"status": 200,
"duration": 0.234,
"ip": "192.168.1.100"
}
六、完整代码
6.1 app/app.py
(见上文)
6.2 app/requirements.txt
flask==2.3.3
prometheus-client==0.19.0
gunicorn==21.2.0
6.3 app/Dockerfile
(见上文)
6.4 app/.dockerignore
(见上文)
6.5 prometheus/prometheus.yml
(见上文)
6.6 filebeat/filebeat.yml
(见上文)
6.7 docker-compose.yml
(见上文)
6.8 .env.example
ELASTIC_PASSWORD=your_elastic_password
七、总结与最佳实践清单
通过本文的实战,我们为 Python Flask 应用添加了 Prometheus 指标和结构化日志,并通过 Docker Compose 部署了完整的监控(Prometheus + Grafana)和日志收集(EFK)栈。以下是 容器监控与日志收集的最佳实践清单:
- ✅ 指标先行:为关键业务和系统资源定义指标,使用 Prometheus 采集。
- ✅ 日志结构化:输出 JSON 格式日志,包含时间、级别、服务名等字段。
- ✅ 集中存储:使用 Elasticsearch 或 Loki 集中存储日志,便于检索。
- ✅ 可视化:Grafana 展示指标,Kibana 展示日志,建立统一可观测性视图。
- ✅ 多阶段构建:大幅减小镜像体积,加速部署。
- ✅ 资源限制:为监控组件设置资源限制,避免影响业务容器。
- ✅ 告警配置:基于指标设置告警,及时发现异常。
- ✅ 日志轮转:配置 Docker 日志驱动,防止日志撑满磁盘。
容器监控与日志收集是构建可靠系统的基石。掌握这些技术,你将能快速定位问题、评估系统健康度,为微服务架构的稳定运行保驾护航。未来,随着 OpenTelemetry 的普及,指标、日志、追踪将进一步融合,但本文介绍的基础实践仍将是可观测性体系的坚固底座。
更多推荐

所有评论(0)