09-Docker监控与日志管理
Docker监控与日志管理
本文是Docker专栏的第九篇,将全面深入地讲解Docker容器监控与日志管理的完整知识体系。从Docker内置监控工具到Prometheus + Grafana监控栈,从Docker日志驱动到ELK/EFK和Loki日志收集方案,涵盖理论讲解、配置详解和完整实战。
引言
在现代云原生架构中,Docker容器已经成为应用部署的标准方式。然而,随着容器数量的增长和微服务架构的复杂化,如何有效地监控容器运行状态、如何收集和分析海量日志,成为了每个运维团队必须面对的核心挑战。
没有监控的系统就像没有仪表盘的汽车——你无法知道当前的速度、油量,更无法预知即将到来的故障。没有日志管理的系统则像没有黑匣子的飞机——当问题发生时,你无法回溯和定位根因。
本文将从以下维度全面展开:
- 监控体系:从Docker内置工具到cAdvisor,再到Prometheus + Grafana完整监控栈
- 日志体系:从Docker日志驱动到ELK/EFK Stack,再到Loki轻量级日志方案
- 最佳实践:RED方法、USE方法、Golden Signals、SLO/SLI设计
- 完整实战:一键部署监控栈和日志栈的Docker Compose方案
无论你是刚开始使用Docker的开发者,还是需要构建生产级可观测性体系的运维工程师,本文都将为你提供系统性的指导。
第一章 Docker监控概述
1.1 为什么需要容器监控
在传统的物理机或虚拟机时代,监控相对简单——机器数量有限,生命周期长,资源边界清晰。但进入容器时代后,一切都发生了变化。容器监控不再是可选项,而是保障业务稳定运行的刚需。
容器监控的核心价值体现在以下几个方面:
第一,故障发现与快速定位。 容器化应用通常由数十甚至数百个微服务组成,任何一个组件的异常都可能导致连锁反应。通过监控,我们可以在用户感知到问题之前就发现异常——比如某个容器的CPU使用率突然飙升、某个服务的响应时间急剧增加、某个数据库容器的内存即将耗尽。没有监控,故障排查就像大海捞针。
第二,容量规划与资源优化。 容器的优势在于资源利用率高,但前提是你需要准确知道每个容器到底消耗了多少资源。通过长期监控数据的分析,你可以发现哪些容器被过度分配了资源(可以缩容),哪些容器资源不足(需要扩容),从而实现精细化的资源管理,降低基础设施成本。
第三,性能优化与瓶颈分析。 监控数据可以揭示系统的性能瓶颈所在。例如,通过对比CPU使用率和网络I/O,你可以判断某个服务是CPU密集型还是I/O密集型;通过分析磁盘I/O监控,你可以发现日志写入是否成为了性能瓶颈。
第四,SLA/SLO保障。 在现代DevOps实践中,服务等级目标(SLO)是衡量系统可靠性的核心指标。要量化和跟踪SLO(如"99.9%的请求在200ms内完成"),就必须有完善的监控体系作为支撑。
第五,安全审计与合规。 监控数据可以用于安全审计,例如检测异常的进程启动、异常的网络连接、异常的资源消耗模式等。在合规要求严格的行业(如金融、医疗),监控数据的保存和分析是审计的必要环节。
1.2 容器监控的挑战
容器监控相比传统监控面临着独特的挑战,理解这些挑战是构建有效监控体系的前提。
挑战一:容器的短暂性(Ephemeral)
容器的生命周期可能非常短暂。一个容器可能只存活几秒钟就被销毁和重建。这意味着传统的"静态配置监控目标"的方式不再适用。当容器被销毁时,其监控数据如果没有被持久化存储,就会永久丢失。监控系统必须能够动态发现和跟踪容器,并在容器消亡后仍能查询其历史数据。
# 容器可能随时被创建和销毁
docker run --rm -d --name temp-task alpine sleep 30 && echo "done"
# 30秒后容器自动消失,但它的监控数据需要被保留
挑战二:高密度部署
一台物理机上可能运行数百个容器,每个容器都需要被独立监控。这对监控系统的采集能力、存储能力和查询能力都提出了很高的要求。如果使用轮询方式采集,数百个目标可能导致采集延迟过大;如果存储粒度过细,数据量会爆炸式增长。
# 一台32核64GB的机器上可能运行:
Container 1: Nginx (0.5 CPU, 128MB)
Container 2: Redis (1 CPU, 2GB)
Container 3: MySQL (4 CPU, 8GB)
Container 4: Java App (2 CPU, 4GB)
...
Container 200: Sidecar (0.1 CPU, 64MB)
# 每个容器都需要CPU/内存/网络/磁盘监控
挑战三:动态调度
在Kubernetes或Docker Swarm等编排系统中,容器会被动态调度到不同的节点上,随时可能发生迁移。监控目标列表在不断变化,监控系统必须具备服务发现能力,能够自动感知容器的创建、销毁和迁移。
挑战四:多维度数据
容器监控不仅需要采集容器本身的资源指标(CPU、内存、网络、磁盘),还需要采集容器内应用的业务指标(请求量、错误率、响应时间)。这些指标的采集方式、数据格式、采集频率各不相同,需要一个统一的框架来管理。
挑战五:命名空间与标签管理
在多租户或多团队环境中,容器可能属于不同的命名空间、项目或团队。监控数据需要通过标签(Labels)来进行维度划分,方便按团队、按项目、按环境进行筛选和聚合。标签设计的好坏直接影响监控数据的可用性。
1.3 容器监控维度
全面的容器监控需要覆盖以下六个维度:
| 监控维度 | 关键指标 | 说明 |
|---|---|---|
| CPU | 使用率、负载、上下文切换、节流 | CPU是最容易成为瓶颈的资源 |
| 内存 | 使用量、缓存、RSS、OOM事件 | 内存不足会导致容器被Kill |
| 网络 | 收发字节数、包数、丢包率、连接数 | 网络问题影响服务间通信 |
| 磁盘 | 读写IOPS、吞吐量、使用率、inode | 磁盘I/O影响数据持久化性能 |
| 进程 | 进程数、线程数、僵尸进程 | 异常进程可能预示应用问题 |
| 应用 | 请求量、错误率、响应时间、QPS | 应用级指标反映业务健康度 |
CPU监控详解:
CPU监控不仅仅看使用率百分比。在容器环境中,CPU节流(Throttling)是一个常被忽略但影响巨大的指标。Docker通过CFS(Completely Fair Scheduler)进行CPU限制,当容器超出CPU配额时会被节流,导致应用响应变慢。
# 查看容器的CPU限制
docker inspect --format='{{.HostConfig.NanoCpus}}' mycontainer
# 查看CPU节流情况(需要读取cgroup文件)
cat /sys/fs/cgroup/cpu/docker/<container_id>/cpu.stat
# 输出示例:
# nr_periods 12345
# nr_throttled 678
# throttled_time 987654321
内存监控详解:
内存监控需要注意几个关键概念:RSS(Resident Set Size,实际使用的物理内存)、Cache(文件缓存)、Swap(交换分区)。Docker默认允许容器使用Swap,这可能导致性能下降。OOM(Out of Memory)事件是内存监控中最重要的告警指标。
# 查看容器内存限制
docker inspect --format='{{.HostConfig.Memory}}' mycontainer
# 查看容器的内存使用详情
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.stat
网络监控详解:
容器网络监控需要区分入站和出站流量,以及不同网络接口的流量。在多容器通信的场景中,网络延迟和丢包率是关键指标。
1.4 监控系统架构
一个完整的监控系统通常包含以下四个层次:
┌─────────────────────────────────────────────────────────────┐
│ 告警层 (Alerting) │
│ AlertManager / 邮件 / Slack / 钉钉 / PagerDuty │
├─────────────────────────────────────────────────────────────┤
│ 可视化层 (Visualization) │
│ Grafana / Kibana / 自定义Dashboard │
├─────────────────────────────────────────────────────────────┤
│ 存储层 (Storage) │
│ Prometheus TSDB / Elasticsearch / InfluxDB │
├─────────────────────────────────────────────────────────────┤
│ 采集层 (Collection) │
│ cAdvisor / Node Exporter / Application Exporters / Agents │
├─────────────────────────────────────────────────────────────┤
│ 数据源 (Data Source) │
│ Docker Engine / 容器 / 主机 / 应用程序 │
└─────────────────────────────────────────────────────────────┘
采集层:负责从各种数据源收集指标数据。在Docker环境中,常用的采集器包括cAdvisor(容器指标)、Node Exporter(主机指标)、应用自身的Prometheus Exporter(业务指标)。
存储层:负责将采集到的时序数据持久化存储。Prometheus内置了时序数据库(TSDB),适合存储指标数据;Elasticsearch适合存储日志数据;InfluxDB是另一个流行的时序数据库选择。
可视化层:将存储的数据以图表的形式展示,帮助运维人员直观地了解系统状态。Grafana是最流行的可视化工具,支持多种数据源。
告警层:当指标超出预设阈值时,触发告警通知。告警需要支持多种通知渠道,并且要有合理的路由和抑制规则,避免告警风暴。
1.5 主流容器监控方案对比
| 方案 | 类型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| docker stats | 内置 | 零配置,即开即用 | 功能有限,无历史数据 | 快速排查,临时查看 |
| cAdvisor | 开源 | Google出品,容器监控专精 | 无长期存储,无告警 | 容器指标采集,配合Prometheus使用 |
| Prometheus | 开源 | 强大的查询语言,生态丰富 | 需要额外组件,无日志能力 | 指标监控的主力方案 |
| Grafana | 开源 | 可视化能力强大,支持多数据源 | 本身不采集数据 | 可视化展示层 |
| Datadog | 商业 | 全托管,功能全面 | 费用高昂,数据出境 | 不想自建监控的团队 |
| Sysdig | 开源/商业 | 系统级追踪,安全监控 | 学习曲线陡峭 | 安全监控,深度排查 |
| Zabbix | 开源 | 成熟稳定,监控全面 | 对容器支持不够原生 | 传统IT环境监控 |
方案选择建议:
- 中小团队/初创公司:Prometheus + Grafana + cAdvisor,全开源,社区活跃,文档丰富
- 大型企业/复杂环境:Prometheus + Grafana + AlertManager + 多Exporter,配合自研扩展
- 不想自建的团队:Datadog或阿里云ARMS等商业方案
- 安全合规要求高:Sysdig Falco + Sysdig Monitor
在开源方案中,Prometheus + Grafana 已经成为云原生监控的事实标准,本文也将以这套方案为主线展开讲解。
第二章 Docker内置监控工具
2.1 docker stats命令详解
docker stats是Docker内置的实时资源监控命令,它可以显示所有运行中容器的CPU、内存、网络和磁盘I/O使用情况。这是最快速、最简单的容器监控方式,无需安装任何额外工具。
# 监控所有运行中的容器(实时刷新,默认每秒更新)
docker stats
# 输出示例:
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
# a1b2c3d4e5f6 nginx-web 0.50% 25.5MiB / 7.64GiB 0.33% 3.14kB / 1.88kB 8.19kB / 0B 3
# b2c3d4e5f6g7 redis-db 0.25% 8.2MiB / 7.64GiB 0.10% 1.24kB / 648B 0B / 0B 5
# c3d4e5f6g7h8 mysql-db 2.30% 356.7MiB / 7.64GiB 4.56% 5.67kB / 2.34kB 12.3MB / 4.1MB 28
各字段含义详解:
| 字段 | 说明 | 注意事项 |
|---|---|---|
| CONTAINER ID | 容器ID | 12位短ID |
| NAME | 容器名称 | 可读性更好的标识 |
| CPU % | CPU使用率百分比 | 基于CPU配额计算,非物理CPU占比 |
| MEM USAGE / LIMIT | 内存使用量/限制 | 不设置限制时显示物理内存总量 |
| MEM % | 内存使用率 | 接近100%时要警惕OOM |
| NET I/O | 网络收发字节数 | 累计值,非速率 |
| BLOCK I/O | 磁盘读写字节数 | 累计值,非速率 |
| PIDS | 容器内进程/线程数 | 异常增长可能表示进程泄漏 |
# 监控指定容器
docker stats nginx-web redis-db
# 只输出一次结果(不持续刷新),适合脚本使用
docker stats --no-stream
# 监控所有容器(包括已停止的)
docker stats -a
2.2 docker stats输出格式化与自定义
docker stats支持通过--format参数自定义输出格式,这在编写监控脚本时非常有用。
# 使用Go模板格式化输出
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# 输出示例:
# NAME CPUPerc MEM USAGE / LIMIT
# nginx-web 0.50% 25.5MiB / 7.64GiB
# redis-db 0.25% 8.2MiB / 7.64GiB
# 只显示容器名称和CPU使用率
docker stats --format "{{.Name}}: {{.CPUPerc}}"
# JSON格式输出,适合程序处理
docker stats --format "{{json .}}" --no-stream
# 自定义表格,添加分隔线
docker stats --format "table {{.Name}}\t{{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
可用的模板变量:
| 变量 | 说明 |
|---|---|
.Container | 容器ID |
.Name | 容器名称 |
.ID | 容器完整ID |
.CPUPerc | CPU使用率 |
.MemUsage | 内存使用量/限制 |
.MemPerc | 内存使用率 |
.NetIO | 网络I/O |
.BlockIO | 磁盘I/O |
.PIDs | 进程数 |
2.3 docker top查看容器进程
docker top命令用于查看容器内部运行的进程,类似于在容器内执行top或ps命令。
# 查看指定容器内的进程
docker top nginx-web
# 输出示例:
# UID PID PPID C STIME TTY TIME CMD
# root 1234 1230 0 10:00 ? 00:00:00 nginx: master process nginx
# root 1235 1234 0 10:00 ? 00:00:00 nginx: worker process
# root 1236 1234 0 10:00 ? 00:00:00 nginx: worker process
# 使用ps选项过滤显示
docker top nginx-web aux
docker top nginx-web -ef
docker top nginx-web -o pid,ppid,cmd
# 查看Java容器的线程数(排查线程泄漏)
docker top java-app -L | wc -l
2.4 docker events事件监听
docker events命令可以实时监听Docker守护进程的各种事件,包括容器的创建、启动、停止、销毁,镜像的拉取、删除等。
# 监听所有Docker事件(实时)
docker events
# 输出示例:
# 2024-01-15T10:00:01.000000000+08:00 container create a1b2c3d4e5f6 (image=nginx:latest, name=nginx-web)
# 2024-01-15T10:00:02.000000000+08:00 container start a1b2c3d4e5f6 (image=nginx:latest, name=nginx-web)
# 2024-01-15T10:05:00.000000000+08:00 container die a1b2c3d4e5f6 (exitCode=0, image=nginx:latest, name=nginx-web)
# 按时间范围过滤
docker events --since "2024-01-15T10:00:00" --until "2024-01-15T11:00:00"
# 按事件类型过滤
docker events --filter type=container
docker events --filter type=image
docker events --filter type=network
# 按容器过滤
docker events --filter container=nginx-web
# 按事件动作过滤
docker events --filter event=start
docker events --filter event=die
docker events --filter event=oom # 监听OOM事件(非常重要!)
# 按镜像过滤
docker events --filter image=nginx:latest
# 组合过滤:监听nginx-web容器的启动和停止事件
docker events --filter container=nginx-web --filter event=start --filter event=stop
# 格式化输出
docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}}'
# JSON格式输出
docker events --format '{{json .}}'
2.5 docker inspect获取容器详细信息
docker inspect可以获取容器或镜像的详细配置信息,包括资源限制、网络配置、挂载卷等。
# 获取容器完整信息(JSON格式)
docker inspect nginx-web
# 使用--format提取特定字段
# 获取容器IP地址
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx-web
# 获取容器状态
docker inspect --format='{{.State.Status}}' nginx-web
# 获取容器启动时间
docker inspect --format='{{.State.StartedAt}}' nginx-web
# 获取容器CPU限制(单位:纳秒)
docker inspect --format='{{.HostConfig.NanoCpus}}' nginx-web
# 获取容器内存限制(单位:字节)
docker inspect --format='{{.HostConfig.Memory}}' nginx-web
# 获取容器重启策略
docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' nginx-web
# 获取容器挂载的卷
docker inspect --format='{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}' nginx-web
# 获取容器环境变量
docker inspect --format='{{range .Config.Env}}{{.}}{{"\n"}}{{end}}' nginx-web
# 获取容器日志路径
docker inspect --format='{{.LogPath}}' nginx-web
# 获取容器资源限制的完整信息
docker inspect --format='CPU: {{.HostConfig.NanoCpus}} ns, Memory: {{.HostConfig.Memory}} bytes, Pids: {{.HostConfig.PidsLimit}}' nginx-web
2.6 docker system df磁盘使用
docker system df命令用于查看Docker的磁盘使用情况,包括镜像、容器、数据卷和缓存占用的空间。
# 查看Docker磁盘使用概况
docker system df
# 输出示例:
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 15 8 5.2GB 2.1GB (40%)
# Containers 20 12 500MB 200MB (40%)
# Local Volumes 5 3 2.8GB 1.2GB (42%)
# Build Cache 50 0 800MB 800MB
# 显示详细信息(每个镜像/容器/卷的单独大小)
docker system df -v
# 清理未使用的资源(谨慎使用!)
docker system prune
# 清理所有未使用的资源(包括未被引用的镜像)
docker system prune -a
# 清理构建缓存
docker builder prune
2.7 docker info系统信息
docker info命令显示Docker守护进程的系统级信息,包括存储驱动、运行时、容器数量、镜像数量等。
# 查看Docker系统信息
docker info
# 输出示例(关键字段):
# Containers: 20 # 容器总数
# Running: 12 # 运行中
# Paused: 0 # 暂停
# Stopped: 8 # 已停止
# Images: 15 # 镜像数
# Server Version: 24.0.7 # Docker版本
# Storage Driver: overlay2 # 存储驱动
# Cgroup Version: 2 # Cgroup版本
# Docker Root Dir: /var/lib/docker # Docker数据目录
# 格式化输出特定字段
docker info --format '{{.ServerVersion}}'
docker info --format '{{.ContainersRunning}}'
docker info --format '{{.Driver}}'
2.8 编写Shell脚本实现简单监控
下面是一个使用Docker内置命令实现的简单容器监控脚本,可以定期采集容器状态并在异常时发送告警。
#!/bin/bash
# ============================================
# Docker容器简易监控脚本
# 功能:监控容器CPU/内存使用率,超出阈值时告警
# 用法: ./docker_monitor.sh [cpu_threshold] [mem_threshold]
# ============================================
# 告警阈值(默认值)
CPU_THRESHOLD=${1:-80} # CPU使用率阈值,默认80%
MEM_THRESHOLD=${2:-80} # 内存使用率阈值,默认80%
LOG_FILE="/var/log/docker_monitor.log" # 日志文件路径
# 告警函数(可替换为邮件、钉钉、企业微信等通知方式)
send_alert() {
local container_name=$1
local metric=$2
local value=$3
local threshold=$4
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local message="[$timestamp] ALERT: Container '$container_name' ${metric}=${value}% (threshold=${threshold}%)"
# 输出到控制台
echo "$message" | tee -a "$LOG_FILE"
# 这里可以集成钉钉/企业微信通知
# curl -s -X POST "https://oapi.dingtalk.com/robot/send?access_token=xxx" \
# -H 'Content-Type: application/json' \
# -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$message\"}}"
}
# 检查容器是否运行
check_containers() {
local running_count=$(docker ps -q | wc -l)
local total_count=$(docker ps -aq | wc -l)
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] Running: $running_count, Total: $total_count" >> "$LOG_FILE"
if [ "$running_count" -lt "$total_count" ]; then
local stopped=$((total_count - running_count))
send_alert "SYSTEM" "stopped_containers" "$stopped" "0"
fi
}
# 检查容器资源使用
check_resources() {
# 使用docker stats获取一次性快照
docker stats --no-stream --format "{{.Name}}|{{.CPUPerc}}|{{.MemPerc}}" | while IFS='|' read -r name cpu mem; do
# 去除百分号,转换为数字
cpu_num=$(echo "$cpu" | tr -d '%')
mem_num=$(echo "$mem" | tr -d '%')
# 浮点数比较
if (( $(echo "$cpu_num > $CPU_THRESHOLD" | bc -l) )); then
send_alert "$name" "CPU" "$cpu_num" "$CPU_THRESHOLD"
fi
if (( $(echo "$mem_num > $MEM_THRESHOLD" | bc -l) )); then
send_alert "$name" "MEMORY" "$mem_num" "$MEM_THRESHOLD"
fi
done
}
# 检查OOM事件
check_oom() {
# 检查最近的OOM事件
local oom_events=$(docker events --since 1m --until 0s --filter event=oom --format '{{.Actor.Attributes.name}}' 2>/dev/null)
if [ -n "$oom_events" ]; then
send_alert "$oom_events" "OOM" "triggered" "0"
fi
}
# 主循环
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] Docker monitor started. CPU threshold: ${CPU_THRESHOLD}%, MEM threshold: ${MEM_THRESHOLD}%"
while true; do
check_containers
check_resources
check_oom
sleep 60 # 每60秒检查一次
done
将脚本保存为docker_monitor.sh并运行:
# 赋予执行权限
chmod +x docker_monitor.sh
# 使用默认阈值运行
./docker_monitor.sh
# 自定义阈值(CPU 70%, 内存 75%)
./docker_monitor.sh 70 75
# 后台运行
nohup ./docker_monitor.sh > /dev/null 2>&1 &
2.9 内置工具的局限性
虽然Docker内置监控工具方便快捷,但在生产环境中存在明显局限:
局限一:无历史数据存储。 docker stats只显示当前实时数据,一旦容器销毁或Docker重启,历史数据全部丢失。无法回答"昨天下午3点这个容器的CPU是多少?"这样的问题。
局限二:无可视化能力。 内置工具只能输出文本,无法生成图表、趋势线等可视化视图,难以直观地分析趋势。
局限三:无告警能力。 内置工具没有告警功能(除了我们手动编写脚本),无法在指标异常时自动通知。
局限四:无集中管理。 在多主机环境中,内置工具只能在单机上运行,无法集中查看所有主机的容器状态。
局限五:性能开销。 频繁执行docker stats会对Docker守护进程造成一定压力,在容器数量多时尤为明显。
局限六:指标维度有限。 内置工具只提供基础的资源指标,无法采集应用级别的业务指标(如请求量、错误率)。
正因如此,生产环境中我们需要更专业的监控方案——这就是下一章cAdvisor和后续章节Prometheus + Grafana要解决的问题。
第三章 cAdvisor容器监控
3.1 cAdvisor概述与架构
cAdvisor(Container Advisor)是Google开源的容器监控工具,它能够采集、处理、聚合和导出运行中容器的资源使用信息和性能指标。cAdvisor最初是为Kubernetes设计的,但同样适用于独立的Docker环境。
cAdvisor的核心特性:
- 零配置采集:cAdvisor自动发现主机上的所有容器,无需手动配置监控目标
- 多维度指标:采集CPU、内存、文件系统、网络、进程、Docker事件等多维度指标
- 低开销:cAdvisor本身资源消耗很低,适合在生产环境长期运行
- 多后端导出:支持将指标导出到Prometheus、InfluxDB、Elasticsearch等多个后端
- Web UI:内置Web界面,可以直接查看容器指标图表
cAdvisor架构:
┌──────────────────────────────────────────────┐
│ cAdvisor │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 容器发现 │ │ 指标采集 │ │ 指标聚合 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 历史存储 │ │ Web UI │ │ API导出 │ │
│ │ (内存DB) │ │ (:8080) │ │(Prom等) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────┤
│ Docker Engine │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│
│ │容器1 │ │容器2 │ │容器3 │ │容器N ││
│ └────────┘ └────────┘ └────────┘ └────────┘│
└──────────────────────────────────────────────┘
cAdvisor通过读取cgroup文件系统和Docker API来获取容器指标,将这些指标聚合后存储在内存数据库中(默认保留约5分钟的历史数据),同时通过HTTP API和Web界面提供查询接口。
3.2 cAdvisor安装与部署
使用Docker方式部署cAdvisor是最简单的方案:
# 拉取cAdvisor镜像
docker pull google/cadvisor:latest
# 注意:新版本镜像已迁移到 registry.k8s.io/cadvisor/cadvisor
docker pull registry.k8s.io/cadvisor/cadvisor:v0.47.2
# 运行cAdvisor容器
docker run -d \
--name=cadvisor \
--volume=/:/rootfs:ro \ # 挂载根文件系统(只读),用于读取cgroup
--volume=/var/run:/var/run:ro \ # 挂载Docker socket,用于与Docker通信
--volume=/sys:/sys:ro \ # 挂载sysfs(只读),用于读取内核信息
--volume=/var/lib/docker/:/var/lib/docker:ro \ # 挂载Docker数据目录(只读)
--volume=/dev/disk/:/dev/disk:ro \ # 挂载磁盘信息(只读)
--publish=8080:8080 \ # 映射Web端口
--detach=true \ # 后台运行
--privileged \ # 特权模式(某些指标需要)
google/cadvisor:latest
# 验证cAdvisor是否运行
docker ps | grep cadvisor
# 访问Web界面
# 浏览器打开: http://localhost:8080
部署参数详解:
| 参数 | 说明 | 为什么需要 |
|---|---|---|
--volume=/:/rootfs:ro | 挂载宿主机根目录 | cAdvisor需要读取cgroup文件来获取容器资源信息 |
--volume=/var/run:/var/run:ro | 挂载Docker socket | cAdvisor通过Docker API获取容器信息 |
--volume=/sys:/sys:ro | 挂载sysfs | 读取内核统计信息(网络、磁盘等) |
--volume=/var/lib/docker/:/var/lib/docker:ro | 挂载Docker数据目录 | 获取容器存储层信息 |
--volume=/dev/disk/:/dev/disk:ro | 挂载磁盘设备 | 获取磁盘I/O统计 |
--privileged | 特权模式 | 某些cgroup v2指标需要特权访问 |
--publish=8080:8080 | 端口映射 | 暴露Web界面和API |
3.3 cAdvisor Web界面使用
访问http://localhost:8080可以看到cAdvisor的Web界面。界面分为以下几个主要部分:
主页(Dashboard):显示主机整体的资源使用概览,包括CPU使用率、内存使用量、网络流量、文件系统使用等。
容器列表(Subcontainers):列出所有被监控的容器,点击某个容器可以查看其详细指标。
每个容器的详细指标页面包含:
- CPU:CPU使用率、CPU使用时间、节流统计
- Memory:内存使用量(含工作集、RSS、缓存等细分)
- Network:网络接口的收发字节数、包数
- Filesystem:文件系统读写字节数
- Processes:容器内进程列表
- Event:容器事件
# cAdvisor Web界面URL结构
http://localhost:8080/ # 主页
http://localhost:8080/containers/ # 容器列表
http://localhost:8080/docker/<container_id> # 特定容器详情
http://localhost:8080/metrics # Prometheus格式指标
3.4 cAdvisor REST API详解
cAdvisor提供了RESTful API,可以程序化地获取容器指标:
# 获取所有容器信息
curl http://localhost:8080/api/v1.3/containers
# 获取特定容器信息
curl http://localhost:8080/api/v1.3/containers/docker/<container_id>
# 获取主机信息
curl http://localhost:8080/api/v1.3/machine
# 获取指定时间范围的指标(过去60秒)
curl "http://localhost:8080/api/v1.3/containers/docker/<container_id>?count=60"
# 获取特定子容器
curl http://localhost:8080/api/v1.3/subcontainers
# 获取事件流
curl http://localhost:8080/api/v1.3/events
# 获取指定类型的指标(如cpu)
curl http://localhost:8080/api/v1.3/containers/docker/<container_id>/cpu
API返回数据示例(JSON格式):
{
"name": "docker/a1b2c3d4e5f6nginx-web",
"id": "docker/a1b2c3d4e5f6",
"state": "running",
"aliases": ["nginx-web"],
"spec": {
"memory": {
"limit": 536870912
},
"cpu": {
"limit": 1000
}
},
"stats": [
{
"timestamp": "2024-01-15T10:00:00Z",
"cpu": {
"usage": {
"total": 12345678,
"per_cpu": [12345678],
"user": 5000000,
"system": 7000000
},
"cfs": {
"periods": 1000,
"throttled_periods": 50,
"throttled_time": 5000000
}
},
"memory": {
"usage": 26738688,
"working_set": 25000000,
"rss": 20000000,
"cache": 6738688
},
"network": {
"name": "eth0",
"rx_bytes": 3140,
"tx_bytes": 1880,
"rx_packets": 30,
"tx_packets": 25
}
}
]
}
3.5 cAdvisor监控指标详解
cAdvisor采集的指标非常丰富,以下是各维度的关键指标说明:
CPU相关指标:
| 指标 | 说明 | 关注点 |
|---|---|---|
cpu_usage_total | CPU总使用时间(纳秒) | 单调递增,需要用rate计算速率 |
cpu_usage_user | 用户态CPU时间 | 高值表示应用计算密集 |
cpu_usage_system | 内核态CPU时间 | 高值表示系统调用频繁 |
cpu_usage_per_cpu | 每核CPU使用时间 | 用于查看CPU亲和性 |
cpu_cfs_periods | CFS调度周期数 | 基准值 |
cpu_cfs_throttled_periods | 被节流的周期数 | 节流率高说明CPU配额不足 |
cpu_cfs_throttled_seconds | 被节流的总时间 | 累计的节流时间 |
内存相关指标:
| 指标 | 说明 | 关注点 |
|---|---|---|
memory_usage | 总内存使用(含缓存) | 包含Page Cache |
memory_working_set | 工作集内存 | OOM判断的依据,最关键 |
memory_rss | 常驻内存集 | 实际占用的物理内存 |
memory_cache | 页面缓存 | 可回收,不是真正内存压力 |
memory_swap | Swap使用量 | 应该为0,非0说明物理内存不足 |
memory_failcnt | 内存分配失败次数 | 非0说明内存压力 |
网络相关指标:
| 指标 | 说明 |
|---|---|
network_rx_bytes | 接收字节数 |
network_tx_bytes | 发送字节数 |
network_rx_packets | 接收包数 |
network_tx_packets | 发送包数 |
network_rx_errors | 接收错误数 |
network_tx_errors | 发送错误数 |
network_rx_dropped | 接收丢包数 |
network_tx_dropped | 发送丢包数 |
文件系统相关指标:
| 指标 | 说明 |
|---|---|
filesystem_usage | 文件系统使用量 |
filesystem_limit | 文件系统总量 |
filesystem_available | 文件系统可用量 |
filesystem_reads_completed | 读操作完成数 |
filesystem_writes_completed | 写操作完成数 |
filesystem_reads_bytes | 读取字节数 |
filesystem_writes_bytes | 写入字节数 |
3.6 cAdvisor与Prometheus集成
cAdvisor内置了Prometheus格式的指标导出功能,访问http://localhost:8080/metrics即可获取Prometheus格式的指标数据。
cAdvisor导出的Prometheus指标示例:
# HELP container_cpu_usage_seconds_total Total CPU time consumed
# TYPE container_cpu_usage_seconds_total counter
container_cpu_usage_seconds_total{container_label_com_docker_compose_service="nginx",id="/docker/a1b2c3d4e5f6",image="nginx:latest",name="nginx-web"} 12.35
# HELP container_memory_usage_bytes Current memory usage in bytes
# TYPE container_memory_usage_bytes gauge
container_memory_usage_bytes{container_label_com_docker_compose_service="nginx",id="/docker/a1b2c3d4e5f6",image="nginx:latest",name="nginx-web"} 26738688
# HELP container_network_receive_bytes_total Cumulative count of bytes received
# TYPE container_network_receive_bytes_total counter
container_network_receive_bytes_total{id="/docker/a1b2c3d4e5f6",interface="eth0",name="nginx-web"} 3140
在Prometheus中的抓取配置:
# prometheus.yml中的cAdvisor抓取配置
scrape_configs:
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080'] # 如果Prometheus和cAdvisor在同一Docker网络
# 可选:自定义抓取间隔
scrape_interval: 15s
# 可选:只采集特定指标(减少数据量)
metric_relabel_configs:
- source_labels: [__name__]
regex: 'container_(cpu_usage_seconds_total|memory_usage_bytes|network_receive_bytes_total|network_transmit_bytes_total)'
action: keep
3.7 cAdvisor配置选项
cAdvisor支持通过命令行参数进行配置:
docker run -d \
--name=cadvisor \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--volume=/dev/disk/:/dev/disk:ro \
--publish=8080:8080 \
google/cadvisor:latest \
--port=8080 \ # Web端口,默认8080
--housekeeping_interval=10s \ # 采集间隔,默认1s(生产环境建议10s)
--max_housekeeping_interval=15s \ # 最大采集间隔
--allow_dynamic_housekeeping=true \ # 允许动态调整采集频率
--global_housekeeping_interval=1m \ # 全局采集间隔
--storage_duration=2m \ # 内存中数据保留时长,默认5m
--docker_only=true \ # 只监控Docker容器
--disable_metrics=disk,tcp,udp \ # 禁用不需要的指标(减少开销)
--env_metadata Whitelist=label.com.docker.compose.service \ # 采集Docker标签作为元数据
--store_container_labels=false # 不存储容器标签(减少数据量)
常用配置参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
--housekeeping_interval | 1s | 采集间隔,生产建议10-15s |
--storage_duration | 5m | 内存数据保留时间 |
--docker_only | false | 只监控Docker容器 |
--disable_metrics | 无 | 禁用指定指标采集 |
--allow_dynamic_housekeeping | true | 动态调整采集频率 |
3.8 cAdvisor在生产环境的部署
在生产环境中,建议通过Docker Compose或systemd部署cAdvisor,并注意以下几点:
# docker-compose.yml中的cAdvisor生产配置
version: '3.8'
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.2
container_name: cadvisor
restart: unless-stopped
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
ports:
- "8080:8080"
command:
- "--housekeeping_interval=10s"
- "--storage_duration=2m"
- "--docker_only=true"
- "--disable_metrics=disk,tcp,udp,advertiser,referencer"
deploy:
resources:
limits:
cpus: '0.5' # 限制cAdvisor自身CPU
memory: 512M # 限制cAdvisor自身内存
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
生产环境注意事项:
- 资源限制:cAdvisor本身需要设置资源限制,防止其消耗过多主机资源
- 采集频率:生产环境建议将
housekeeping_interval设置为10-15秒,减少开销 - 禁用无用指标:使用
--disable_metrics禁用不需要的指标,减少数据量 - 安全:cAdvisor暴露了容器详细信息,生产环境应限制访问(通过网络策略或反向代理)
- 持久化:cAdvisor内存存储有限,长期存储依赖Prometheus等后端
3.9 cAdvisor监控实战案例
下面通过一个完整的实战案例展示cAdvisor的实际监控效果。
场景:部署一个Nginx + Redis的Web应用,使用cAdvisor监控其资源使用情况,并通过压力测试观察指标变化。
# 步骤1:部署测试容器
docker run -d --name nginx-web -p 80:80 --memory=256m --cpus=1 nginx:latest
docker run -d --name redis-cache --memory=128m --cpus=0.5 redis:latest
# 步骤2:部署cAdvisor
docker run -d --name=cadvisor -p 8080:8080 \
--volume=/:/rootfs:ro --volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro --volume=/var/lib/docker/:/var/lib/docker:ro \
google/cadvisor:latest
# 步骤3:对Nginx进行压力测试
# 使用ab (Apache Bench)进行压测
docker run --rm williamyeh/ab -n 10000 -c 100 http://host.docker.internal:80/
# 步骤4:观察cAdvisor指标变化
# 通过API获取Nginx容器的CPU使用情况
CONTAINER_ID=$(docker inspect --format='{{.Id}}' nginx-web)
curl -s "http://localhost:8080/api/v1.3/containers/docker/$CONTAINER_ID" | \
python3 -c "
import sys, json
data = json.load(sys.stdin)
stats = data.get('stats', [])
if stats:
latest = stats[-1]
cpu = latest.get('cpu', {}).get('usage', {})
mem = latest.get('memory', {})
net = latest.get('network', {})
print(f'CPU Total: {cpu.get(\"total\", 0) / 1e9:.2f} seconds')
print(f'Memory Usage: {mem.get(\"usage\", 0) / 1024 / 1024:.2f} MB')
print(f'Memory Working Set: {mem.get(\"working_set\", 0) / 1024 / 1024:.2f} MB')
print(f'Network RX: {net.get(\"rx_bytes\", 0) / 1024:.2f} KB')
print(f'Network TX: {net.get(\"tx_bytes\", 0) / 1024:.2f} KB')
print(f'CPU Throttled: {latest.get(\"cpu\", {}).get(\"cfs\", {}).get(\"throttled_periods\", 0)} periods')
"
# 步骤5:清理
docker rm -f nginx-web redis-cache cadvisor
通过以上实战,你可以直观地看到cAdvisor如何实时监控容器的CPU、内存、网络等指标,并通过API程序化地获取这些数据。
第四章 Prometheus监控体系
4.1 Prometheus概述与核心概念
Prometheus是由SoundCloud开发、后捐赠给CNCF的开源监控告警系统。它是继Kubernetes之后第二个从CNCF毕业的项目,已经成为云原生监控的事实标准。
Prometheus的核心概念:
Metric Types(指标类型):
Prometheus定义了四种指标类型,每种类型适用于不同的监控场景:
- Counter(计数器):只增不减的指标,如HTTP请求总数、错误总数。Counter的值只能增加(或在重启时归零),不能减少。适合用
rate()函数计算变化速率。
# 示例:HTTP请求总数
http_requests_total{method="GET", status="200"} 12345
- Gauge(仪表盘):可增可减的指标,如当前内存使用量、CPU使用率、队列长度。Gauge反映的是当前状态的瞬时值。
# 示例:当前内存使用量
container_memory_usage_bytes{container="nginx"} 26738688
- Histogram(直方图):将数据分布到不同桶(Bucket)中的指标,如请求延迟分布。Histogram可以计算百分位数(如P50、P95、P99)。
# 示例:请求延迟分布
http_request_duration_seconds_bucket{le="0.1"} 8000 # <=0.1秒的请求8000个
http_request_duration_seconds_bucket{le="0.5"} 9500 # <=0.5秒的请求9500个
http_request_duration_seconds_bucket{le="1.0"} 9900 # <=1.0秒的请求9900个
http_request_duration_seconds_bucket{le="+Inf"} 10000 # 所有请求10000个
http_request_duration_seconds_sum 850.5 # 总延迟850.5秒
http_request_duration_seconds_count 10000 # 总请求数10000个
- Summary(摘要):类似Histogram,但在客户端预先计算了百分位数。与Histogram的区别是Summary的百分位数在采集端计算,而Histogram在查询端计算。
# 示例:请求延迟的百分位数
http_request_duration_seconds{quantile="0.5"} 0.05 # P50延迟50ms
http_request_duration_seconds{quantile="0.9"} 0.15 # P90延迟150ms
http_request_duration_seconds{quantile="0.99"} 0.35 # P99延迟350ms
Targets(目标):Prometheus监控的对象称为Target。每个Target通过一个HTTP端点暴露指标,Prometheus定期从这些端点抓取(Pull)指标数据。
Scrape(抓取):Prometheus采用Pull模式主动抓取指标数据,这与传统的Push模式不同。Pull模式的优势在于监控目标不需要知道监控系统的地址,且Prometheus可以控制抓取频率。
4.2 Prometheus架构详解
┌──────────────────────────────────────────────────────────────┐
│ Prometheus Server │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Retrieval │ │ TSDB │ │ HTTP │ │ Alerting │ │
│ │ (采集) │ │ (存储) │ │ Server │ │ Engine │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ │
│ │Service │ │ WAL │ │ API │ │ Rules │ │
│ │Discovery │ │ (预写日志)│ │ 查询接口 │ │ (规则) │ │
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
└──────────────────────────────────────────────────┼──────────┘
│
┌───────────────────────────────┼────────┐
│ │ │
┌─────┴──────┐ ┌────────┴──┐ ┌──┴──────────┐
│ Pushgateway │ │AlertManager│ │ Grafana │
│ (推送网关) │ │ (告警管理) │ │ (可视化) │
└─────────────┘ └──────┬─────┘ └─────────────┘
│
┌─────────┴─────────┐
│ 邮件/Slack/钉钉 │
└───────────────────┘
Targets: Exporters:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────────┐ ┌──────────┐
│cAdvisor │ │Node Exp │ │App Exp │ │MySQL Exporter│ │Redis Exp│
└─────────┘ └─────────┘ └─────────┘ └──────────────┘ └──────────┘
各组件说明:
- Prometheus Server:核心组件,负责采集、存储和查询指标数据
- Exporters:指标导出器,将各种系统的指标暴露为Prometheus格式
- Pushgateway:用于短生命周期任务的指标推送(因为Prometheus是Pull模式,短任务可能等不到抓取就结束了)
- AlertManager:告警管理组件,负责告警的去重、分组、路由和通知
- Grafana:可视化工具,通过Prometheus API查询数据并展示
4.3 Prometheus安装与配置
使用Docker方式安装Prometheus:
# 步骤1:创建配置文件目录
mkdir -p /opt/prometheus/{config,rules,data}
# 步骤2:创建基本的prometheus.yml配置文件
cat > /opt/prometheus/config/prometheus.yml << 'EOF'
# my global config
global:
scrape_interval: 15s # 全局抓取间隔,默认15秒
evaluation_interval: 15s # 规则评估间隔,默认15秒
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them
rule_files:
# - "rules/*.yml"
# A scrape configuration containing exactly one endpoint to scrape
scrape_configs:
# 监控Prometheus自身
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
EOF
# 步骤3:运行Prometheus容器
docker run -d \
--name=prometheus \
-p 9090:9090 \
-v /opt/prometheus/config/prometheus.yml:/etc/prometheus/prometheus.yml \
-v /opt/prometheus/data:/prometheus \
prom/prometheus:latest \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/prometheus \
--storage.tsdb.retention.time=30d \ # 数据保留30天
--web.enable-lifecycle # 启用热重载API
# 步骤4:验证
# 浏览器访问 http://localhost:9090
# 访问 http://localhost:9090/targets 查看抓取目标状态
4.4 prometheus.yml配置文件详解
以下是完整的prometheus.yml配置文件,包含所有重要配置项的详细注释:
# ============================================
# Prometheus配置文件 - 完整版
# ============================================
# 全局配置
global:
scrape_interval: 15s # 抓取间隔,每次抓取之间的时间间隔
evaluation_interval: 15s # 评估间隔,规则评估的时间间隔
scrape_timeout: 10s # 抓取超时时间,默认为10秒
# 外部标签,用于区分不同Prometheus实例的数据
# 在Federation或远程写入场景中非常有用
external_labels:
monitor: 'docker-monitor' # 监控系统名称
environment: 'production' # 环境标识
datacenter: 'dc-1' # 数据中心标识
# 告警管理器配置
alerting:
alertmanagers:
- static_configs:
- targets:
- 'alertmanager:9093' # AlertManager地址
# 如果AlertManager需要认证
# basic_auth:
# username: admin
# password: password
# tls_config:
# insecure_skip_verify: true
# 规则文件配置
rule_files:
- "rules/alert_rules.yml" # 告警规则文件
- "rules/recording_rules.yml" # 记录规则文件
# 抓取配置
scrape_configs:
# ==================== Prometheus自身监控 ====================
- job_name: 'prometheus'
metrics_path: '/metrics' # 指标路径,默认/metrics
scrape_interval: 30s # 自身监控可以间隔长一些
static_configs:
- targets: ['localhost:9090']
labels:
instance: 'prometheus-main'
# ==================== Docker主机监控 ====================
- job_name: 'node_exporter'
static_configs:
- targets: ['node-exporter:9100']
labels:
node: 'docker-host-1'
# ==================== 容器监控(cAdvisor) ====================
- job_name: 'cadvisor'
scrape_interval: 15s
static_configs:
- targets: ['cadvisor:8080']
# 通过relabel_config添加自定义标签
relabel_configs:
- source_labels: [__address__]
target_label: 'monitor_host'
replacement: 'docker-host-1'
# ==================== 应用监控 ====================
- job_name: 'app_metrics'
metrics_path: '/actuator/prometheus' # Spring Boot Actuator
static_configs:
- targets: ['app:8080']
labels:
application: 'my-spring-app'
team: 'backend'
# ==================== 使用Docker服务发现 ====================
# 自动发现运行中的Docker容器(需要Docker socket)
- job_name: 'docker'
docker_sd_configs:
- host: unix:///var/run/docker.sock # Docker socket路径
# 只采集暴露了端口的容器
relabel_configs:
- source_labels: [__meta_docker_container_name]
regex: '/(.*)'
target_label: 'container_name'
- source_labels: [__meta_docker_container_log_stream]
target_label: 'log_stream'
# 过滤:只采集有特定标签的容器
- source_labels: [__meta_docker_container_label_monitoring]
regex: 'enabled'
action: keep
4.5 PromQL查询语言基础
PromQL(Prometheus Query Language)是Prometheus的查询语言,用于从时序数据库中检索和计算指标数据。掌握PromQL是使用Prometheus的核心技能。
基本查询:
# 1. 瞬时向量查询(返回当前时刻的值)
container_memory_usage_bytes # 所有容器的内存使用量
container_memory_usage_bytes{container="nginx"} # 特定容器的内存使用量
container_memory_usage_bytes{container=~"nginx.*"} # 正则匹配容器名
# 2. 区间向量查询(返回一段时间内的值)
container_memory_usage_bytes[5m] # 过去5分钟的内存使用量
container_cpu_usage_seconds_total[1h] # 过去1小时的CPU使用时间
# 3. 偏移查询(查询历史时刻的值)
container_memory_usage_bytes offset 1h # 1小时前的内存使用量
container_memory_usage_bytes[5m] offset 1h # 1小时前5分钟的内存使用量
Counter类型指标的处理:
Counter类型的指标是累计值,直接查看意义不大,通常需要使用rate()或increase()函数计算变化速率:
# rate(): 计算每秒平均增长率(适合Counter)
rate(container_cpu_usage_seconds_total[5m])
# 含义:过去5分钟内,每秒平均CPU使用率
# increase(): 计算一段时间内的总增长量
increase(http_requests_total[1h])
# 含义:过去1小时内,HTTP请求总数增加了多少
# irate(): 计算最近两个数据点的瞬时增长率
irate(container_cpu_usage_seconds_total[1m])
# 含义:基于最近两个数据点计算的瞬时CPU使用率
# 比rate更敏感,但波动更大,适合短时间窗口
rate vs irate:
| 函数 | 计算方式 | 特点 | 适用场景 |
|---|---|---|---|
rate() | 时间窗口内所有数据点的平均斜率 | 平滑,反应慢 | 长期趋势,告警 |
irate() | 仅使用最后两个数据点 | 敏感,波动大 | 短期实时监控,Grafana图表 |
Gauge类型指标的处理:
Gauge类型的指标可以直接使用,也可以通过聚合函数进行分析:
# 直接查询当前值
container_memory_usage_bytes
# 计算平均值(跨所有容器)
avg(container_memory_usage_bytes)
# 计算最大值
max(container_memory_usage_bytes)
# 计算总和
sum(container_memory_usage_bytes)
# 按容器名称分组计算
sum by (container_name) (container_memory_usage_bytes)
# 计算Top 5内存使用最多的容器
topk(5, container_memory_usage_bytes)
# 计算Bottom 5
bottomk(5, container_memory_usage_bytes)
4.6 PromQL高级查询
Histogram与百分位数计算:
Histogram是PromQL中最复杂的指标类型,但在延迟分析中极为重要:
# histogram_quantile(): 计算百分位数
# P50(中位数)延迟
histogram_quantile(0.50, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# P95延迟
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# P99延迟
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# 按API路径分组的P99延迟
histogram_quantile(0.99, sum by (le, path) (rate(http_request_duration_seconds_bucket[5m])))
数学运算:
# 基本算术运算
container_memory_usage_bytes / 1024 / 1024 # 字节转MB
(container_memory_usage_bytes / container_spec_memory_limit_bytes) * 100 # 内存使用率百分比
# 计算CPU使用率(百分比)
# 容器CPU使用率 = rate(CPU总使用时间) * 100
rate(container_cpu_usage_seconds_total[5m]) * 100
# 限制到特定CPU核心的CPU使用率
rate(container_cpu_usage_seconds_total[5m]) / on(container) group_left container_spec_cpu_period * 100
时间序列预测:
# predict_linear(): 基于线性回归预测未来值
# 预测4小时后磁盘是否会满
predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600) < 0
# 预测2小时后内存是否会耗尽
predict_linear(container_memory_usage_bytes[30m], 2 * 3600) > container_spec_memory_limit_bytes
多维度聚合与连接:
# 多维分组
sum by (node, container_name) (rate(container_cpu_usage_seconds_total[5m]))
# 向量匹配(Join操作)
# 将容器CPU使用量与容器CPU限制进行除法运算
rate(container_cpu_usage_seconds_total[5m])
/ on(id) group_left
container_spec_cpu_quota * container_spec_cpu_period
# 使用label_replace动态修改标签
label_replace(container_memory_usage_bytes, "short_id", "$1", "id", "/docker/(.{12}).*")
# 逻辑运算
# CPU使用率超过80%且内存使用率超过80%的容器
(rate(container_cpu_usage_seconds_total[5m]) * 100 > 80)
and on (id)
(container_memory_usage_bytes / container_spec_memory_limit_bytes * 100 > 80)
常用PromQL查询示例:
# 1. 所有容器的CPU使用率(%)
sum by (name) (rate(container_cpu_usage_seconds_total{container!="POD"}[5m])) * 100
# 2. 容器内存使用率(%)
container_memory_usage_bytes / container_spec_memory_limit_bytes * 100
# 3. 容器网络接收速率(bytes/s)
rate(container_network_receive_bytes_total[5m])
# 4. 容器网络发送速率(bytes/s)
rate(container_network_transmit_bytes_total[5m])
# 5. 主机CPU使用率(%)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 6. 主机内存使用率(%)
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# 7. 主机磁盘使用率(%)
(1 - (node_filesystem_avail_bytes{fstype=~"ext4|xfs"} / node_filesystem_size_bytes{fstype=~"ext4|xfs"})) * 100
# 8. 过去5分钟内OOM的容器数量
increase(container_last_seen{container!=""}[5m]) > 0
# 9. 每个Docker主机的容器总数
count by (instance) (container_last_seen)
# 10. 过去1小时的错误日志数量
increase(log_messages_total{level="error"}[1h])
4.7 Prometheus抓取Docker指标配置
Prometheus提供了原生Docker服务发现功能,可以自动发现运行中的容器:
# 使用Docker SD自动发现容器
scrape_configs:
- job_name: 'docker-containers'
docker_sd_configs:
- host: unix:///var/run/docker.sock
# 过滤容器
filters:
- name: status
values: ['running']
# 配置relabel规则
relabel_configs:
# 提取容器名称(去掉前导/)
- source_labels: [__meta_docker_container_name]
regex: '/(.*)'
target_label: container_name
# 提取Docker镜像名
- source_labels: [__meta_docker_container_label_com_docker_compose_service]
target_label: service
# 提取环境标签
- source_labels: [__meta_docker_container_label_environment]
target_label: environment
# 只采集暴露了9100端口的容器
- source_labels: [__address__]
regex: '.*:(9100)'
action: keep
# 将容器IP:端口映射为抓取地址
- target_label: __address__
replacement: 'docker-host:9090'
source_labels: [__meta_docker_container_name]
4.8 Node Exporter主机监控
Node Exporter是Prometheus官方维护的主机指标采集器,用于采集Linux系统级的监控指标:
# 使用Docker部署Node Exporter
docker run -d \
--name=node-exporter \
-p 9100:9100 \
--restart=unless-stopped \
-v /proc:/host/proc:ro \ # 进程信息
-v /sys:/host/sys:ro \ # 系统信息
-v /:/rootfs:ro \ # 根文件系统
prom/node-exporter:latest \
--path.procfs=/host/proc \ # procfs路径
--path.sysfs=/host/sys \ # sysfs路径
--path.rootfs=/rootfs \ # 根文件系统路径
--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($$|/) # 排除Docker内部挂载
# 验证Node Exporter
curl http://localhost:9100/metrics | head -20
Node Exporter关键指标:
# CPU相关
node_cpu_seconds_total # 每个CPU核心在各模式下的时间
node_load1 # 1分钟平均负载
node_load5 # 5分钟平均负载
node_load15 # 15分钟平均负载
# 内存相关
node_memory_MemTotal_bytes # 总内存
node_memory_MemAvailable_bytes # 可用内存
node_memory_MemFree_bytes # 空闲内存
node_memory_Buffers_bytes # 缓冲区
node_memory_Cached_bytes # 页面缓存
# 磁盘相关
node_filesystem_size_bytes # 文件系统总大小
node_filesystem_avail_bytes # 文件系统可用空间
node_filesystem_free_bytes # 文件系统空闲空间
node_disk_read_bytes_total # 磁盘读取字节总数
node_disk_written_bytes_total # 磁盘写入字节总数
node_disk_io_time_seconds_total # 磁盘I/O时间
# 网络相关
node_network_receive_bytes_total # 网络接收字节总数
node_network_transmit_bytes_total # 网络发送字节总数
node_network_receive_errs_total # 网络接收错误总数
node_network_transmit_errs_total # 网络发送错误总数
# 文件系统
node_filesystem_files # 文件系统inode总数
node_filesystem_files_free # 文件系统空闲inode
常用的Node Exporter PromQL查询:
# 主机CPU使用率(%)
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 每核CPU使用率
100 - (avg by(cpu) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 主机内存使用率(%)
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# 磁盘使用率(%)
(1 - (node_filesystem_avail_bytes{fstype=~"ext4|xfs"} / node_filesystem_size_bytes{fstype=~"ext4|xfs"})) * 100
# 网络接收速率(MB/s)
rate(node_network_receive_bytes_total{device=~"eth.*|en.*"}[5m]) / 1024 / 1024
# 磁盘I/O使用率(%)
rate(node_disk_io_time_seconds_total[5m]) * 100
# 系统负载
node_load1 / count(count(node_cpu_seconds_total) by (cpu))
4.9 cAdvisor + Prometheus完整监控栈搭建
下面使用Docker Compose一键搭建cAdvisor + Node Exporter + Prometheus的完整监控栈:
# docker-compose.monitoring.yml
version: '3.8'
services:
# ==================== Prometheus Server ====================
prometheus:
image: prom/prometheus:v2.45.0
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle'
networks:
- monitoring
# ==================== cAdvisor ====================
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.2
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
command:
- '--housekeeping_interval=10s'
- '--docker_only=true'
networks:
- monitoring
# ==================== Node Exporter ====================
node-exporter:
image: prom/node-exporter:v1.6.1
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
- '--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($$|/)'
networks:
- monitoring
volumes:
prometheus_data:
networks:
monitoring:
driver: bridge
对应的prometheus.yml配置:
# prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['prometheus:9090']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
# 启动监控栈
docker compose -f docker-compose.monitoring.yml up -d
# 查看所有组件状态
docker compose -f docker-compose.monitoring.yml ps
# 访问Prometheus Web界面
# http://localhost:9090
# http://localhost:9090/targets # 查看抓取目标
4.10 Prometheus数据模型与存储
Prometheus使用自定义的时序数据库(TSDB)来存储指标数据。理解其数据模型对于优化查询和管理存储至关重要。
数据模型:
每个时间序列由指标名称(Metric Name)和一组键值对标签(Labels)唯一标识:
# 格式: metric_name{label1="value1", label2="value2"} value timestamp
container_cpu_usage_seconds_total{container="nginx", image="nginx:latest", instance="cadvisor:8080", job="cadvisor"} 12.345 1705312800000
存储结构:
Prometheus TSDB数据目录结构:
/data/
├── chunks_head/ # 最近数据的内存映射块
│ ├── 000001 # 数据块文件
│ └── 000002
├── chunks/ # 持久化数据块
│ ├── 000001 # 压缩的数据块
│ └── 000002
├── index/ # 倒排索引
│ └── 000001
├── wal/ # 预写日志(WAL)
│ ├── 00000001 # WAL段文件
│ └── checkpoint.00000001
├── tombstones/ # 删除标记
└── queries.active/ # 活跃查询状态
存储优化配置:
# Prometheus启动参数中的存储相关配置
docker run -d \
--name=prometheus \
prom/prometheus:latest \
--storage.tsdb.path=/prometheus \ # 数据存储路径
--storage.tsdb.retention.time=30d \ # 数据保留时间(30天)
--storage.tsdb.retention.size=50GB \ # 数据保留大小(50GB)
--storage.tsdb.wal-compression \ # 启用WAL压缩
--storage.tsdb.min-block-duration=2h \ # 最小块持续时间
--storage.tsdb.max-block-duration=2h \ # 最大块持续时间(影响压缩)
--query.max-samples=50000000 \ # 单次查询最大样本数
--query.timeout=2m # 查询超时时间
4.11 Recording Rules与Alerting Rules
Recording Rules(记录规则):
记录规则用于预先计算常用且计算量较大的查询,将结果保存为新的时间序列,提高查询性能:
# prometheus/rules/recording_rules.yml
groups:
- name: container_metrics
interval: 30s # 每30秒计算一次
rules:
# 容器CPU使用率(%)
- record: container:cpu_usage_percent
expr: |
rate(container_cpu_usage_seconds_total{container!=""}[5m]) * 100
# 容器内存使用率(%)
- record: container:memory_usage_percent
expr: |
container_memory_usage_bytes / container_spec_memory_limit_bytes * 100
# 每个主机的容器CPU总使用率
- record: instance:container_cpu_usage:sum
expr: |
sum by (instance) (rate(container_cpu_usage_seconds_total[5m]))
# 每个主机的容器内存总使用量
- record: instance:container_memory_usage:sum
expr: |
sum by (instance) (container_memory_usage_bytes)
# 主机CPU使用率
- record: instance:cpu_usage:percent
expr: |
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 主机内存使用率
- record: instance:memory_usage:percent
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# 主机磁盘使用率
- record: instance:disk_usage:percent
expr: |
(1 - (node_filesystem_avail_bytes{fstype=~"ext4|xfs"}
/ node_filesystem_size_bytes{fstype=~"ext4|xfs"})) * 100
Alerting Rules(告警规则):
# prometheus/rules/alert_rules.yml
groups:
- name: container_alerts
rules:
# 容器CPU使用率过高
- alert: ContainerHighCpuUsage
expr: |
rate(container_cpu_usage_seconds_total[5m]) * 100 > 80
for: 5m # 持续5分钟才告警
labels:
severity: warning # 告警级别
category: container
annotations:
summary: "容器CPU使用率过高"
description: "容器 {{ $labels.name }} CPU使用率超过80%,当前值: {{ $value }}%"
# 容器内存使用率过高
- alert: ContainerHighMemoryUsage
expr: |
container_memory_usage_bytes / container_spec_memory_limit_bytes * 100 > 85
for: 5m
labels:
severity: warning
category: container
annotations:
summary: "容器内存使用率过高"
description: "容器 {{ $labels.name }} 内存使用率超过85%,当前值: {{ $value }}%"
# 容器OOM
- alert: ContainerOomKilled
expr: |
increase(container_last_seen[5m]) == 0
for: 0m
labels:
severity: critical
category: container
annotations:
summary: "容器被OOM Kill"
description: "容器 {{ $labels.name }} 可能被OOM Kill"
# 容器停止运行
- alert: ContainerDown
expr: |
time() - container_last_seen{name!=""} > 60
for: 0m
labels:
severity: critical
category: container
annotations:
summary: "容器已停止运行"
description: "容器 {{ $labels.name }} 已停止运行超过60秒"
- name: host_alerts
rules:
# 主机CPU使用率过高
- alert: HostHighCpuLoad
expr: |
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
category: host
annotations:
summary: "主机CPU使用率过高"
description: "主机 {{ $labels.instance }} CPU使用率超过85%,当前值: {{ $value }}%"
# 主机内存不足
- alert: HostOutOfMemory
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 5m
labels:
severity: critical
category: host
annotations:
summary: "主机内存不足"
description: "主机 {{ $labels.instance }} 内存使用率超过90%"
# 主机磁盘空间不足
- alert: HostDiskSpaceLow
expr: |
(1 - (node_filesystem_avail_bytes{fstype=~"ext4|xfs"}
/ node_filesystem_size_bytes{fstype=~"ext4|xfs"})) * 100 > 85
for: 5m
labels:
severity: warning
category: host
annotations:
summary: "主机磁盘空间不足"
description: "主机 {{ $labels.instance }} 磁盘 {{ $labels.device }} 使用率超过85%"
# 主机磁盘即将满(预测)
- alert: HostDiskWillFillIn24h
expr: |
predict_linear(node_filesystem_avail_bytes{fstype=~"ext4|xfs"}[1h], 24 * 3600) < 0
for: 10m
labels:
severity: warning
category: host
annotations:
summary: "磁盘将在24小时内被写满"
description: "主机 {{ $labels.instance }} 磁盘 {{ $labels.device }} 预计在24小时内被写满"
热重载规则配置:
# 修改规则文件后,无需重启Prometheus,通过API热重载
curl -X POST http://localhost:9090/-/reload
# 或发送SIGHUP信号
docker kill -s HUP prometheus
第五章 Grafana可视化
5.1 Grafana概述与安装
Grafana是一个开源的可视化平台,支持多种数据源(Prometheus、Elasticsearch、InfluxDB、Loki等),能够创建丰富多样的监控仪表盘。它以灵活的查询编辑器、丰富的面板类型和强大的告警功能著称,是云原生可视化的事实标准。
Grafana的核心特性:
- 多数据源支持:支持Prometheus、Elasticsearch、InfluxDB、Loki、MySQL、PostgreSQL等30+数据源
- 丰富的可视化面板:Graph、Stat、Table、Heatmap、Gauge、Bar Gauge等多种面板类型
- 模板化Dashboard:支持变量和模板,实现一个Dashboard监控多个实例
- 告警集成:内置告警引擎,支持多种通知渠道
- 用户与权限管理:支持组织、团队、用户多级权限管理
- 插件生态:丰富的社区插件,扩展功能
使用Docker安装Grafana:
# 创建Grafana数据持久化目录
mkdir -p /opt/grafana/{data,provisioning}
# 运行Grafana容器
docker run -d \
--name=grafana \
-p 3000:3000 \
-v /opt/grafana/data:/var/lib/grafana \ # 数据持久化
-v /opt/grafana/provisioning:/etc/grafana/provisioning \ # 配置自动配置
-e GF_SECURITY_ADMIN_USER=admin \ # 管理员用户名
-e GF_SECURITY_ADMIN_PASSWORD=admin123 \ # 管理员密码
-e GF_USERS_ALLOW_SIGN_UP=false \ # 禁止用户注册
-e GF_SERVER_ROOT_URL=http://grafana.example.com \ # 根URL
-e GF_SMTP_ENABLED=true \ # 启用SMTP邮件通知
-e GF_SMTP_HOST=smtp.example.com:587 \ # SMTP服务器
-e GF_SMTP_USER=alert@example.com \ # SMTP用户
-e GF_SMTP_PASSWORD=smtp_password \ # SMTP密码
--restart=unless-stopped \
grafana/grafana:10.2.0
# 验证Grafana
# 浏览器访问 http://localhost:3000
# 默认用户名/密码: admin / admin123
Grafana环境变量说明:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
GF_SECURITY_ADMIN_USER | 管理员用户名 | admin |
GF_SECURITY_ADMIN_PASSWORD | 管理员密码 | admin |
GF_USERS_ALLOW_SIGN_UP | 是否允许用户注册 | true |
GF_SERVER_HTTP_PORT | HTTP端口 | 3000 |
GF_SMTP_ENABLED | 启用SMTP | false |
GF_AUTH_ANONYMOUS_ENABLED | 允许匿名访问 | false |
5.2 Grafana数据源配置
Grafana支持通过Web界面或配置文件(provisioning)添加数据源。
方式一:Web界面添加Prometheus数据源
- 登录Grafana -> 左侧菜单点击"Connections" -> “Data Sources”
- 点击"Add data source" -> 选择"Prometheus"
- 填写配置:
- URL:
http://prometheus:9090(同一Docker网络)或http://host.docker.internal:9090 - Access: Server(default)
- Scrape interval: 15s
- URL:
- 点击"Save & Test"验证连接
方式二:通过Provisioning自动配置数据源
# /opt/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
# Prometheus数据源 - 指标监控
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true # 设为默认数据源
editable: true # 允许在界面编辑
jsonData:
httpMethod: POST # 查询方法
manageAlerts: true # 管理告警
prometheusType: Prometheus # Prometheus类型
prometheusVersion: 2.45.0 # Prometheus版本
# 基本认证(如果Prometheus启用了认证)
# basicAuth: true
# basicAuthUser: admin
# secureJsonData:
# basicAuthPassword: password
# Loki数据源 - 日志查询(后续章节使用)
- name: Loki
type: loki
access: proxy
url: http://loki:3100
jsonData:
maxLines: 1000 # 最大返回行数
# Elasticsearch数据源 - 日志查询(ELK方案)
- name: Elasticsearch
type: elasticsearch
access: proxy
url: http://elasticsearch:9200
database: "logstash-*" # 索引模式
jsonData:
esVersion: "8.0.0" # ES版本
timeField: "@timestamp" # 时间字段
5.3 创建Dashboard与Panel
创建Dashboard的步骤:
- 左侧菜单 -> “Dashboards” -> “New Dashboard”
- 点击"Add visualization"
- 选择数据源(Prometheus)
- 在Query编辑器中输入PromQL
创建一个CPU监控Panel:
# Panel配置示例:容器CPU使用率
# Query:
sum by (name) (rate(container_cpu_usage_seconds_total{container!=""}[5m])) * 100
# Panel设置:
# Title: 容器CPU使用率
# Visualization: Time series
# Unit: percent (0-100)
# Legend: {{name}}
# Fill: 2
# Stack: 关闭
# 颜色配置:
# - 阈值: 80 = yellow, 90 = red
# - 颜色模式: background
Panel JSON配置示例:
{
"title": "容器CPU使用率",
"type": "timeseries",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"targets": [
{
"expr": "sum by (name) (rate(container_cpu_usage_seconds_total{container!=\"\"}[5m])) * 100",
"legendFormat": "{{name}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 70},
{"color": "orange", "value": 85},
{"color": "red", "value": 95}
]
}
}
},
"options": {
"legend": {"displayMode": "table", "placement": "bottom"},
"tooltip": {"mode": "multi", "sort": "desc"}
},
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
}
5.4 Grafana面板类型
Grafana提供了多种面板类型,适用于不同的监控场景:
1. Time Series(时间序列图)
最常用的面板类型,适合展示随时间变化的指标,如CPU使用率、内存使用量、网络流量等。
适用场景: CPU/内存/网络趋势图
配置要点: 设置Y轴单位、阈值线、图例位置
2. Stat(统计值)
显示单个或少量指标的当前值,适合展示概要信息,如容器总数、告警数量等。
适用场景: 容器总数、运行状态概览
配置要点: 设置颜色阈值、背景模式、字体大小
3. Table(表格)
以表格形式展示数据,适合展示多列结构化数据,如容器列表及其资源使用情况。
# 表格查询:每个容器的关键指标
{
"expr": "sum by (name) (container_memory_usage_bytes)",
"format": "table",
"instant": true
}
4. Heatmap(热力图)
以热力图形式展示数据分布,适合展示延迟分布、请求量分布等。
适用场景: 请求延迟分布、响应时间分布
配置要点: 使用Histogram指标,设置Bucket配置
5. Gauge(仪表盘)
以仪表盘形式展示单一指标的当前值和范围,适合展示使用率百分比。
适用场景: CPU/内存/磁盘使用率
配置要点: 设置最小值0、最大值100、阈值颜色
6. Bar Gauge(条形仪表)
以条形图形式展示多个指标的值,适合对比多个实例的同一指标。
适用场景: 多容器CPU使用率对比
配置要点: 设置排序方式、显示模式
7. Node Graph(节点图)
以节点关系图展示系统拓扑,适合展示微服务调用链。
8. Pie Chart(饼图)
以饼图形式展示数据的占比分布。
5.5 变量与模板化Dashboard
变量是Grafana模板化的核心功能,允许你创建动态Dashboard,通过下拉菜单切换监控对象。
创建变量:
- 进入Dashboard设置 -> “Variables” -> “Add variable”
- 配置变量:
# 变量1: 容器名称
Name: container_name
Type: Query
Data source: Prometheus
Query: label_values(container_cpu_usage_seconds_total, name)
Refresh: On time range change
Multi-value: true # 允许多选
Include All option: true # 包含"全部"选项
# 变量2: 主机名称
Name: instance
Type: Query
Data source: Prometheus
Query: label_values(node_cpu_seconds_total, instance)
Refresh: On time range change
# 变量3: 时间间隔(自定义)
Name: interval
Type: Interval
Values: 1m,5m,10m,30m,1h,6h,12h,1d
Default: 5m
# 变量4: 数据源(支持切换数据源)
Name: datasource
Type: Datasource
Type: Prometheus
在Panel中使用变量:
# 使用变量过滤容器
sum by (name) (rate(container_cpu_usage_seconds_total{name=~"$container_name"}[$interval]))
# 使用变量过滤主机
100 - (avg by (instance) (rate(node_cpu_seconds_total{instance="$instance", mode="idle"}[$interval])) * 100)
变量的高级用法:
# 变量链接:根据第一个变量的值过滤第二个变量
# 变量2的Query引用变量1:
label_values(container_cpu_usage_seconds_total{name=~"$container_name"}, image)
# 使用变量进行条件判断
# 如果选择了"All",展示所有数据;否则只展示选中容器
{# 使用=~操作符支持正则,All选项会生成.*正则}
# 在Panel Title中使用变量
"容器 $container_name CPU使用率"
5.6 告警规则配置
Grafana内置了统一的告警引擎,可以直接在Grafana中创建告警规则,无需依赖Prometheus AlertManager。
创建告警规则:
# 通过Grafana Provisioning配置告警规则
# /opt/grafana/provisioning/alerting/alert_rules.yml
apiVersion: 1
groups:
- name: Docker容器告警
interval: 30s
rules:
- uid: container_cpu_high
title: 容器CPU使用率过高
condition: A
data:
# 查询A:获取CPU使用率
- refId: A
relativeTimeRange:
from: 600 # 过去10分钟
to: 0
datasourceUid: prometheus
model:
expr: |
rate(container_cpu_usage_seconds_total[5m]) * 100
instant: true
refId: A
# 查询B:阈值判断
- refId: B
relativeTimeRange:
from: 600
to: 0
datasourceUid: __expr__
model:
type: threshold
expression: A
conditions:
- evaluator:
type: gt
params: [80]
refId: B
noDataState: OK
execErrState: Error
for: 5m # 持续5分钟
annotations:
summary: "容器 {{ $labels.name }} CPU使用率过高"
description: "当前CPU使用率: {{ $values.A }}%,超过80%阈值"
labels:
severity: warning
category: container
isPaused: false
Grafana告警的优势:
| 特性 | Grafana Alerting | Prometheus AlertManager |
|---|---|---|
| 多数据源 | 支持多种数据源 | 仅Prometheus |
| 界面操作 | Web界面配置 | YAML配置文件 |
| 告警状态 | 正常/待处理/触发/已解决 | inactive/pending/firing/resolved |
| 路由能力 | 通知策略和静默 | 更强大的路由和分组 |
| 适用场景 | 统一告警管理 | 大规模告警处理 |
5.7 通知渠道配置
Grafana支持多种通知渠道,以下是最常用的几种:
1. 邮件通知:
# grafana.ini或环境变量配置SMTP
[smtp]
enabled = true
host = smtp.example.com:587
user = alert@example.com
password = "smtp_password"
from_address = alert@example.com
from_name = Grafana Alert
2. 钉钉通知(Webhook方式):
# 通过Grafana Provisioning配置通知策略
# /opt/grafana/provisioning/alerting/contact_points.yml
apiVersion: 1
contactPoints:
- orgId: 1
name: 钉钉告警
receivers:
- uid: dingtalk_1
type: webhook
settings:
url: "https://oapi.dingtalk.com/robot/send?access_token=YOUR_ACCESS_TOKEN"
httpMethod: POST
title: "{{ template \"default.title\" . }}"
message: |
{{ range .Alerts }}
告警名称: {{ .Labels.alertname }}
严重级别: {{ .Labels.severity }}
描述: {{ .Annotations.description }}
触发时间: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{ end }}
# 钉钉需要特殊的消息格式
# 可以使用自定义模板
- orgId: 1
name: 企业微信告警
receivers:
- uid: wechat_1
type: webhook
settings:
url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY"
httpMethod: POST
title: "Grafana告警通知"
message: |
{{ range .Alerts }}
**{{ .Labels.alertname }}**
> 级别: {{ .Labels.severity }}
> 描述: {{ .Annotations.description }}
> 时间: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{ end }}
- orgId: 1
name: 邮件通知
receivers:
- uid: email_1
type: email
settings:
addresses: "ops-team@example.com;dev-team@example.com"
- orgId: 1
name: Slack通知
receivers:
- uid: slack_1
type: slack
settings:
url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
title: "{{ template \"slack.title\" . }}"
text: "{{ template \"slack.text\" . }}"
通知策略配置:
# /opt/grafana/provisioning/alerting/notification_policies.yml
apiVersion: 1
policies:
- orgId: 1
receiver: 默认通知 # 默认通知渠道
group_by: ['alertname'] # 按告警名称分组
group_wait: 30s # 首次告警等待时间
group_interval: 5m # 同组告警间隔
repeat_interval: 4h # 重复通知间隔
routes:
# Critical级别告警 -> 钉钉+邮件
- receiver: 钉钉告警
matchers:
- severity="critical"
group_wait: 0s # 立即发送
repeat_interval: 1h # 每小时重复一次
continue: true # 继续匹配下一条规则
- receiver: 邮件通知
matchers:
- severity="critical"
group_wait: 0s
# Warning级别告警 -> 钉钉
- receiver: 钉钉告警
matchers:
- severity="warning"
group_wait: 5m
repeat_interval: 4h
# 容器相关告警 -> 企业微信
- receiver: 企业微信告警
matchers:
- category="container"
group_wait: 1m
5.8 导入社区Dashboard模板
Grafana社区提供了大量现成的Dashboard模板,可以直接导入使用。
常用社区Dashboard ID:
| Dashboard | ID | 说明 |
|---|---|---|
| Docker Monitoring | 193 | 基于cAdvisor的Docker监控 |
| Docker and system monitoring | 179 | Docker+主机监控 |
| Node Exporter Full | 1860 | Node Exporter完整监控 |
| Prometheus Stats | 3662 | Prometheus自身监控 |
| Kubernetes cluster monitoring | 315 | K8s集群监控 |
| Container Stats | 11558 | 容器统计 |
导入Dashboard的步骤:
- 左侧菜单 -> “Dashboards” -> “Import”
- 输入Dashboard ID(如193)或上传JSON文件
- 选择数据源(Prometheus)
- 点击"Import"
通过Provisioning自动导入Dashboard:
# /opt/grafana/provisioning/dashboards/dashboard.yml
apiVersion: 1
providers:
- name: 'Docker监控'
orgId: 1
folder: 'Docker监控' # Dashboard所在文件夹
type: file
disableDeletion: false # 允许删除
editable: true # 允许编辑
updateIntervalSeconds: 30 # 更新间隔
options:
path: /etc/grafana/provisioning/dashboards/json # JSON文件目录
5.9 自定义Docker监控Dashboard实战
下面我们创建一个完整的Docker监控Dashboard,涵盖容器和主机的主要指标。
Dashboard JSON结构:
{
"title": "Docker全面监控Dashboard",
"tags": ["docker", "monitoring"],
"timezone": "browser",
"schemaVersion": 38,
"version": 1,
"refresh": "15s",
"time": {"from": "now-1h", "to": "now"},
"templating": {
"list": [
{
"name": "container_name",
"type": "query",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"query": "label_values(container_cpu_usage_seconds_total, name)",
"refresh": 1,
"includeAll": true,
"multi": true
}
]
},
"panels": [
{
"title": "容器CPU使用率(%)",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [{
"expr": "sum by (name) (rate(container_cpu_usage_seconds_total{name=~\"$container_name\"}[5m])) * 100",
"legendFormat": "{{name}}"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 70},
{"color": "red", "value": 90}
]
}
}
}
},
{
"title": "容器内存使用量(MB)",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [{
"expr": "container_memory_usage_bytes{name=~\"$container_name\"} / 1024 / 1024",
"legendFormat": "{{name}}"
}],
"fieldConfig": {
"defaults": {"unit": "MB"}
}
},
{
"title": "容器网络流量(bytes/s)",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
"targets": [
{
"expr": "rate(container_network_receive_bytes_total{name=~\"$container_name\"}[5m])",
"legendFormat": "{{name}} - RX"
},
{
"expr": "rate(container_network_transmit_bytes_total{name=~\"$container_name\"}[5m])",
"legendFormat": "{{name}} - TX"
}
]
},
{
"title": "主机CPU使用率(%)",
"type": "gauge",
"gridPos": {"h": 8, "w": 6, "x": 12, "y": 8},
"targets": [{
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 70},
{"color": "red", "value": 90}
]
}
}
}
},
{
"title": "主机内存使用率(%)",
"type": "gauge",
"gridPos": {"h": 8, "w": 6, "x": 18, "y": 8},
"targets": [{
"expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 80},
{"color": "red", "value": 90}
]
}
}
}
},
{
"title": "容器状态总览",
"type": "stat",
"gridPos": {"h": 4, "w": 24, "x": 0, "y": 16},
"targets": [{
"expr": "count(count(container_last_seen{name!=\"\"}) by (name))",
"legendFormat": "运行中容器"
}],
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"steps": [{"color": "blue", "value": null}]
}
}
},
"options": {
"reduceOptions": {"calcs": ["lastNotNull"]},
"orientation": "horizontal"
}
}
]
}
5.10 Grafana用户与权限管理
Grafana提供了完善的用户与权限管理机制,适合团队协作。
组织与团队:
Grafana权限层级:
├── Organization (组织)
│ ├── Team (团队)
│ │ ├── User1 (开发者)
│ │ └── User2 (运维)
│ └── User3 (管理员)
│
├── Folder (文件夹)
│ ├── Dashboard1 (看板)
│ └── Dashboard2 (看板)
│
└── Permissions (权限)
├── Admin: 管理员权限
├── Editor: 可编辑Dashboard
└── Viewer: 只读查看
用户管理:
# 通过Grafana API创建用户
curl -X POST http://localhost:3000/api/admin/users \
-H "Authorization: Basic YWRtaW46YWRtaW4xMjM=" \
-H "Content-Type: application/json" \
-d '{
"name": "张三",
"email": "zhangsan@example.com",
"login": "zhangsan",
"password": "user_password"
}'
# 创建团队
curl -X POST http://localhost:3000/api/teams \
-H "Authorization: Basic YWRtaW46YWRtaW4xMjM=" \
-H "Content-Type: application/json" \
-d '{
"name": "DevOps团队",
"email": "devops@example.com"
}'
# 添加用户到团队
curl -X POST http://localhost:3000/api/teams/1/members \
-H "Authorization: Basic YWRtaW46YWRtaW4xMjM=" \
-H "Content-Type: application/json" \
-d '{
"userId": 2
}'
# 设置Dashboard权限
curl -X POST http://localhost:3000/api/dashboards/uid/dashboard-uid/permissions \
-H "Authorization: Basic YWRtaW46YWRtaW4xMjM=" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"role": "Viewer",
"permission": 1
},
{
"teamId": 1,
"permission": 2
},
{
"userId": 2,
"permission": 4
}
]
}'
# permission: 1=View, 2=Edit, 4=Admin
第六章 完整监控栈实战
6.1 监控栈架构设计
在生产环境中,一个完整的Docker监控栈通常包含以下组件:
┌─────────────────────────────────────────────────────────────────┐
│ 完整监控栈架构 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Grafana │ │ Alert │ │Prometheus│ │ Blackbox │ │
│ │ (可视化) │ │ Manager │ │ (存储) │ │ Exporter │ │
│ │ :3000 │ │ :9093 │ │ :9090 │ │ :9115 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ │ ┌─────┴──────────────┴──────┐ │ │
│ │ │ 采集层 │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ │ │ │
│ └─────────┤ │ cAdvisor │ │Node Exp. │ │◄─────┘ │
│ │ │ :8080 │ │ :9100 │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │App Meter │ │Blackbox │ │ │
│ │ │ :8080 │ │ Exporter │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └────────────────────────────┘ │
│ │
│ 通知渠道: │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │
│ │ 邮件 │ │ 钉钉 │ │ Slack │ │ PagerDuty│ │
│ └────────┘ └────────┘ └────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
各组件职责:
| 组件 | 职责 | 端口 |
|---|---|---|
| Prometheus | 指标采集与存储 | 9090 |
| cAdvisor | 容器资源指标采集 | 8080 |
| Node Exporter | 主机系统指标采集 | 9100 |
| Blackbox Exporter | 服务可用性探测 | 9115 |
| AlertManager | 告警路由与通知 | 9093 |
| Grafana | 可视化展示 | 3000 |
6.2 使用Docker Compose一键部署监控栈
下面是完整的监控栈Docker Compose配置,这是本文最核心的实战内容之一。
6.3 完整docker-compose.yml编写
# /opt/monitoring/docker-compose.yml
# 完整Docker监控栈 - cAdvisor + Node Exporter + Prometheus + Grafana + AlertManager
version: '3.8'
services:
# ====================================================================
# Prometheus - 监控核心,指标采集与存储
# ====================================================================
prometheus:
image: prom/prometheus:v2.45.0
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
# 配置文件
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
# 规则文件目录
- ./prometheus/rules:/etc/prometheus/rules:ro
# 告警规则模板
- ./prometheus/templates:/etc/prometheus/templates:ro
# 数据持久化
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=50GB'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--web.enable-lifecycle' # 启用热重载
- '--web.enable-admin-api' # 启用管理API
- '--alertmanager.url=http://alertmanager:9093' # AlertManager地址
networks:
- monitoring
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
# ====================================================================
# AlertManager - 告警管理
# ====================================================================
alertmanager:
image: prom/alertmanager:v0.26.0
container_name: alertmanager
restart: unless-stopped
ports:
- "9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- ./alertmanager/templates:/etc/alertmanager/templates:ro
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--web.external-url=http://localhost:9093'
networks:
- monitoring
depends_on:
- prometheus
# ====================================================================
# cAdvisor - 容器资源监控
# ====================================================================
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.47.2
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
command:
- '--housekeeping_interval=10s' # 采集间隔
- '--storage_duration=5m' # 内存数据保留
- '--docker_only=true' # 只监控Docker容器
- '--disable_metrics=tcp,udp' # 禁用不需要的指标
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
networks:
- monitoring
privileged: true
# ====================================================================
# Node Exporter - 主机资源监控
# ====================================================================
node-exporter:
image: prom/node-exporter:v1.6.1
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
- '--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($$|/)'
- '--collector.systemd' # 启用systemd监控
- '--collector.processes' # 启用进程监控
networks:
- monitoring
# ====================================================================
# Blackbox Exporter - 服务可用性探测
# ====================================================================
blackbox-exporter:
image: prom/blackbox-exporter:v0.24.0
container_name: blackbox-exporter
restart: unless-stopped
ports:
- "9115:9115"
volumes:
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
command:
- '--config.file=/etc/blackbox_exporter/config.yml'
networks:
- monitoring
# ====================================================================
# Grafana - 可视化展示
# ====================================================================
grafana:
image: grafana/grafana:10.2.0
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
# 自动配置数据源
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
# 自动配置Dashboard
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
# Dashboard JSON文件
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
# 告警通知配置
- ./grafana/provisioning/alerting:/etc/grafana/provisioning/alerting:ro
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_USERS_ALLOW_SIGN_UP=false
- GF_INSTALL_PLUGINS=grafana-piechart-panel # 安装饼图插件
networks:
- monitoring
depends_on:
- prometheus
# ========================================================================
# 持久化卷
# ========================================================================
volumes:
prometheus_data:
alertmanager_data:
grafana_data:
# ========================================================================
# 网络
# ========================================================================
networks:
monitoring:
driver: bridge
6.4 Prometheus告警规则配置
# /opt/monitoring/prometheus/rules/docker_alerts.yml
# Docker容器监控告警规则
groups:
# ==================== 容器告警 ====================
- name: docker_container_alerts
interval: 30s
rules:
# 容器停止
- alert: ContainerDown
expr: time() - container_last_seen{name!=""} > 60
for: 0m
labels:
severity: critical
category: container
annotations:
summary: "容器 {{ $labels.name }} 已停止"
description: "容器 {{ $labels.name }} (镜像: {{ $labels.image }}) 已停止运行超过60秒"
# 容器CPU使用率过高
- alert: ContainerHighCpuUsage
expr: |
(sum by(name) (rate(container_cpu_usage_seconds_total{name!=""}[5m])) * 100) > 80
for: 5m
labels:
severity: warning
category: container
annotations:
summary: "容器CPU使用率过高: {{ $labels.name }}"
description: "容器 {{ $labels.name }} CPU使用率超过80%,当前值: {{ printf \"%.2f\" $value }}%"
# 容器内存使用率过高
- alert: ContainerHighMemoryUsage
expr: |
(container_memory_working_set_bytes / container_spec_memory_limit_bytes) * 100 > 85
for: 5m
labels:
severity: warning
category: container
annotations:
summary: "容器内存使用率过高: {{ $labels.name }}"
description: "容器 {{ $labels.name }} 内存使用率超过85%,当前值: {{ printf \"%.2f\" $value }}%"
# 容器CPU被节流
- alert: ContainerCpuThrottling
expr: |
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m]) > 0.25
for: 10m
labels:
severity: warning
category: container
annotations:
summary: "容器CPU被节流: {{ $labels.name }}"
description: "容器 {{ $labels.name }} CPU节流率超过25%,当前值: {{ printf \"%.2f\" $value }}"
# 容器重启次数过多
- alert: Container频繁重启
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
for: 0m
labels:
severity: warning
category: container
annotations:
summary: "容器频繁重启: {{ $labels.container }}"
description: "容器 {{ $labels.container }} 在过去1小时内重启超过5次"
# ==================== 主机告警 ====================
- name: docker_host_alerts
interval: 30s
rules:
# 主机CPU使用率过高
- alert: HostHighCpuLoad
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 10m
labels:
severity: warning
category: host
annotations:
summary: "主机CPU使用率过高: {{ $labels.instance }}"
description: "主机 {{ $labels.instance }} CPU使用率超过85%,当前值: {{ printf \"%.2f\" $value }}%"
# 主机内存不足
- alert: HostOutOfMemory
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 5m
labels:
severity: critical
category: host
annotations:
summary: "主机内存不足: {{ $labels.instance }}"
description: "主机 {{ $labels.instance }} 内存使用率超过90%,当前值: {{ printf \"%.2f\" $value }}%"
# 磁盘空间不足
- alert: HostDiskSpaceLow
expr: |
(1 - (node_filesystem_avail_bytes{fstype=~"ext4|xfs"}
/ node_filesystem_size_bytes{fstype=~"ext4|xfs"})) * 100 > 85
for: 5m
labels:
severity: warning
category: host
annotations:
summary: "磁盘空间不足: {{ $labels.instance }} {{ $labels.device }}"
description: "磁盘使用率超过85%,当前值: {{ printf \"%.2f\" $value }}%"
# 磁盘I/O使用率过高
- alert: HostHighDiskIO
expr: |
rate(node_disk_io_time_seconds_total[5m]) * 100 > 80
for: 10m
labels:
severity: warning
category: host
annotations:
summary: "磁盘I/O使用率过高: {{ $labels.instance }} {{ $labels.device }}"
description: "磁盘I/O使用率超过80%,当前值: {{ printf \"%.2f\" $value }}%"
# 网络错误
- alert: HostNetworkErrors
expr: |
rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m]) > 0
for: 5m
labels:
severity: warning
category: host
annotations:
summary: "网络错误: {{ $labels.instance }} {{ $labels.device }}"
description: "检测到网络接收/发送错误"
# ==================== 服务可用性告警 ====================
- name: service_availability_alerts
interval: 30s
rules:
# 服务不可达(HTTP探测)
- alert: ServiceDown
expr: probe_success == 0
for: 1m
labels:
severity: critical
category: service
annotations:
summary: "服务不可达: {{ $labels.instance }}"
description: "服务 {{ $labels.instance }} 已不可达超过1分钟"
# SSL证书即将过期
- alert: SslCertExpiringSoon
expr: |
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 30
for: 1h
labels:
severity: warning
category: service
annotations:
summary: "SSL证书即将过期: {{ $labels.instance }}"
description: "SSL证书将在 {{ printf \"%.0f\" $value }} 天后过期"
# HTTP响应时间过长
- alert: ServiceHighLatency
expr: |
probe_duration_seconds > 5
for: 5m
labels:
severity: warning
category: service
annotations:
summary: "服务响应时间过长: {{ $labels.instance }}"
description: "服务响应时间超过5秒,当前值: {{ printf \"%.2f\" $value }}秒"
6.5 AlertManager告警路由与通知
# /opt/monitoring/alertmanager/alertmanager.yml
# AlertManager完整配置
global:
# SMTP邮件配置
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alertmanager@example.com'
smtp_auth_username: 'alert@example.com'
smtp_auth_password: 'email_password'
smtp_require_tls: true
# Slack配置
# slack_api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
# 告警模板
templates:
- '/etc/alertmanager/templates/*.tmpl'
# 告警路由配置
route:
receiver: 'default' # 默认接收者
group_by: ['alertname', 'cluster', 'severity'] # 告警分组
group_wait: 30s # 同组告警等待时间(收到第一条告警后等待30s)
group_interval: 5m # 同组告警发送间隔
repeat_interval: 4h # 重复告警间隔
routes:
# Critical级别告警 - 立即通知,多渠道
- matchers:
- severity="critical"
receiver: 'critical-alerts'
group_wait: 0s # 不等待,立即发送
repeat_interval: 1h # 每小时重复一次
continue: true # 继续匹配后续规则
# Warning级别告警 - 钉钉通知
- matchers:
- severity="warning"
receiver: 'dingtalk-warnings'
group_wait: 1m
repeat_interval: 4h
# 容器相关告警
- matchers:
- category="container"
receiver: 'container-alerts'
group_wait: 30s
# 主机相关告警
- matchers:
- category="host"
receiver: 'host-alerts'
group_wait: 30s
# 服务可用性告警
- matchers:
- category="service"
receiver: 'service-alerts'
group_wait: 0s
# 抑制规则:当Critical告警触发时,抑制同类的Warning告警
inhibit_rules:
# 当容器Down告警触发时,抑制该容器的CPU/内存告警
- source_matchers:
- alertname="ContainerDown"
target_matchers:
- alertname=~"ContainerHigh.*"
equal: ['name']
# 当主机Down告警触发时,抑制该主机的所有其他告警
- source_matchers:
- alertname="HostDown"
target_matchers:
- severity="warning"
equal: ['instance']
# 接收者配置
receivers:
# 默认接收者
- name: 'default'
email_configs:
- to: 'ops-team@example.com'
send_resolved: true # 恢复时也通知
# Critical级别告警接收者
- name: 'critical-alerts'
email_configs:
- to: 'ops-team@example.com;dev-team@example.com'
send_resolved: true
# 钉钉通知
webhook_configs:
- url: 'http://dingtalk-proxy:8060/dingtalk/webhook1/send'
send_resolved: true
# 企业微信通知
webhook_configs:
- url: 'http://wechat-proxy:8060/wechat/webhook1/send'
send_resolved: true
# Warning级别钉钉通知
- name: 'dingtalk-warnings'
webhook_configs:
- url: 'http://dingtalk-proxy:8060/dingtalk/webhook1/send'
send_resolved: true
# 容器告警接收者
- name: 'container-alerts'
webhook_configs:
- url: 'http://dingtalk-proxy:8060/dingtalk/webhook1/send'
send_resolved: true
# 主机告警接收者
- name: 'host-alerts'
email_configs:
- to: 'infra-team@example.com'
send_resolved: true
webhook_configs:
- url: 'http://dingtalk-proxy:8060/dingtalk/webhook1/send'
send_resolved: true
# 服务告警接收者
- name: 'service-alerts'
email_configs:
- to: 'ops-team@example.com'
send_resolved: true
webhook_configs:
- url: 'http://dingtalk-proxy:8060/dingtalk/webhook1/send'
send_resolved: true
钉钉告警模板:
# /opt/monitoring/alertmanager/templates/dingtalk.tmpl
{{ define "dingtalk.title" }}{{ .Status | title }} - {{ .CommonLabels.alertname }}{{ end }}
{{ define "dingtalk.content" }}
---
**告警状态**: {{ .Status }}
**告警名称**: {{ .CommonLabels.alertname }}
**告警级别**: {{ .CommonLabels.severity }}
**告警分类**: {{ .CommonLabels.category }}
{{ range .Alerts }}
---
**告警详情**:
- **描述**: {{ .Annotations.description }}
- **触发时间**: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{ if .EndsAt.IsZero }}{{ else }}- **恢复时间**: {{ .EndsAt.Format "2006-01-02 15:04:05" }}{{ end }}
- **标签**:
{{ range .Labels.SortedPairs }} - {{ .Name }}: {{ .Value }}
{{ end }}
{{ end }}
{{ end }}
6.6 Grafana Dashboard导入与定制
# /opt/monitoring/grafana/provisioning/dashboards/dashboard.yml
apiVersion: 1
providers:
- name: 'Docker监控看板'
orgId: 1
folder: 'Docker监控'
type: file
disableDeletion: false
editable: true
updateIntervalSeconds: 30
options:
path: /var/lib/grafana/dashboards
// /opt/monitoring/grafana/dashboards/docker-overview.json (精简版)
{
"title": "Docker监控总览",
"schemaVersion": 38,
"version": 1,
"refresh": "15s",
"time": {"from": "now-1h", "to": "now"},
"templating": {
"list": [{
"name": "container",
"type": "query",
"datasource": {"type": "prometheus", "uid": "prom"},
"query": "label_values(container_cpu_usage_seconds_total, name)",
"includeAll": true,
"multi": true,
"refresh": 1
}]
},
"panels": [
{
"id": 1,
"title": "容器CPU使用率 Top 10",
"type": "bargauge",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [{
"expr": "topk(10, sum by(name)(rate(container_cpu_usage_seconds_total[5m])) * 100)",
"legendFormat": "{{name}}"
}],
"fieldConfig": {"defaults": {"unit": "percent"}}
},
{
"id": 2,
"title": "容器内存使用 Top 10",
"type": "bargauge",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [{
"expr": "topk(10, container_memory_usage_bytes / 1024 / 1024)",
"legendFormat": "{{name}}"
}],
"fieldConfig": {"defaults": {"unit": "MB"}}
},
{
"id": 3,
"title": "主机CPU/内存/磁盘",
"type": "row",
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 8},
"collapsed": false
},
{
"id": 4,
"title": "主机CPU使用率",
"type": "timeseries",
"gridPos": {"h": 8, "w": 8, "x": 0, "y": 9},
"targets": [{
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
"legendFormat": "CPU使用率"
}],
"fieldConfig": {"defaults": {"unit": "percent"}}
},
{
"id": 5,
"title": "主机内存使用率",
"type": "timeseries",
"gridPos": {"h": 8, "w": 8, "x": 8, "y": 9},
"targets": [{
"expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100",
"legendFormat": "内存使用率"
}],
"fieldConfig": {"defaults": {"unit": "percent"}}
},
{
"id": 6,
"title": "主机磁盘使用率",
"type": "timeseries",
"gridPos": {"h": 8, "w": 8, "x": 16, "y": 9},
"targets": [{
"expr": "(1 - (node_filesystem_avail_bytes{fstype=~\"ext4|xfs\"} / node_filesystem_size_bytes{fstype=~\"ext4|xfs\"})) * 100",
"legendFormat": "{{mountpoint}}"
}],
"fieldConfig": {"defaults": {"unit": "percent"}}
}
]
}
6.7 监控指标与告警阈值设计
合理的告警阈值是有效监控的关键。阈值过低会导致告警疲劳,过高则可能遗漏故障。
推荐的告警阈值:
| 指标 | Warning | Critical | 说明 |
|---|---|---|---|
| 容器CPU使用率 | > 80% (5m) | > 95% (5m) | 考虑CPU限制 |
| 容器内存使用率 | > 85% (5m) | > 95% (1m) | 接近OOM |
| 容器CPU节流率 | > 25% (10m) | > 50% (5m) | CPU配额不足 |
| 主机CPU使用率 | > 85% (10m) | > 95% (5m) | 主机过载 |
| 主机内存使用率 | > 85% (5m) | > 95% (1m) | 可能触发OOM |
| 主机磁盘使用率 | > 85% (5m) | > 95% (1m) | 磁盘即将满 |
| 主机负载 | > CPU核心数*2 | > CPU核心数*4 | 系统过载 |
| 网络错误率 | > 0 (5m) | > 10/min | 网络异常 |
| 服务响应时间 | > 2s (5m) | > 5s (1m) | 性能问题 |
| 服务可用性 | < 99.9% | < 99% | 服务中断 |
6.8 监控栈的自身监控
监控系统的自身监控至关重要——如果监控系统本身出了问题却无人知晓,那所有监控都形同虚设。
# prometheus.yml中添加自身监控配置
scrape_configs:
# 监控Prometheus自身
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# 监控AlertManager
- job_name: 'alertmanager'
static_configs:
- targets: ['alertmanager:9093']
# 监控Grafana
- job_name: 'grafana'
static_configs:
- targets: ['grafana:3000']
metrics_path: '/metrics'
监控栈自身告警规则:
# /opt/monitoring/prometheus/rules/monitoring_self.yml
groups:
- name: monitoring_self_alerts
rules:
# Prometheus自身不可达
- alert: PrometheusDown
expr: up{job="prometheus"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Prometheus不可达"
description: "Prometheus实例 {{ $labels.instance }} 不可达"
# AlertManager不可达
- alert: AlertmanagerDown
expr: up{job="alertmanager"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "AlertManager不可达"
# Grafana不可达
- alert: GrafanaDown
expr: up{job="grafana"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Grafana不可达"
# Prometheus采集失败率过高
- alert: PrometheusScrapeFailures
expr: |
rate(prometheus_target_scrape_pool_targets_total{state="failed"}[5m])
/ rate(prometheus_target_scrape_pool_targets_total[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "Prometheus采集失败率过高"
description: "采集失败率超过10%"
# Prometheus数据延迟
- alert: PrometheusDataIngestionLag
expr: |
time() - prometheus_tsdb_head_max_timestamp > 60
for: 5m
labels:
severity: warning
annotations:
summary: "Prometheus数据采集延迟"
description: "最新数据延迟超过60秒"
# Prometheus存储空间不足
- alert: PrometheusStorageLow
expr: |
prometheus_tsdb_storage_blocks_bytes / prometheus_tsdb_storage_blocks_bytes > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "Prometheus存储空间不足"
6.9 生产环境监控栈高可用方案
在生产环境中,单节点的监控栈存在单点故障风险。以下是高可用方案:
┌──────────────────────────────────────────────────────────────┐
│ 高可用监控架构 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ Grafana #1 │ │ Grafana #2 │ │ Load Balancer│ │
│ │ (主) │ │ (备) │◄──►│ (VIP/LB) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ 共享数据库 (MySQL/Postgres) │ │
│ └──────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │Prometheus#1 │ │Prometheus#2 │ │ Thanos │ │
│ │ (独立采集) │ │ (独立采集) │───►│ Query/Store │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │AlertManager │ │AlertManager │ │ 共享存储 │ │
│ │ #1 │◄──►│ #2 │ │ (S3/Minio) │ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
│ │
│ 采集层(每台主机部署): │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │cAdvisor │ │Node Exp. │ │App Exp. │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────┘
高可用要点:
- Prometheus双副本:部署两个独立的Prometheus实例,各自采集完整数据。通过Thanos或VictoriaMetrics进行去重和统一查询
- AlertManager集群:部署多个AlertManager实例,通过Gossip协议同步告警状态,避免重复告警
- Grafana共享数据库:多个Grafana实例共享MySQL/PostgreSQL数据库,通过负载均衡对外提供服务
- 远程存储:使用Thanos或VictoriaMetrics将数据同步到对象存储(S3/Minio),实现长期存储
# AlertManager集群配置(多实例)
# 在alertmanager.yml中添加集群配置
# 启动时通过--cluster.*参数配置
docker run -d \
--name=alertmanager-1 \
-p 9093:9093 \
prom/alertmanager:latest \
--config.file=/etc/alertmanager/alertmanager.yml \
--cluster.peer=alertmanager-2:9094 \
--cluster.listen-address=:9094
docker run -d \
--name=alertmanager-2 \
-p 9094:9093 \
prom/alertmanager:latest \
--config.file=/etc/alertmanager/alertmanager.yml \
--cluster.peer=alertmanager-1:9094 \
--cluster.listen-address=:9094
# 一键启动完整监控栈
cd /opt/monitoring
docker compose up -d
# 查看所有服务状态
docker compose ps
# 查看日志
docker compose logs -f prometheus
docker compose logs -f alertmanager
# 停止监控栈
docker compose down
# 停止并删除数据(谨慎!)
docker compose down -v
第七章 Docker日志体系
7.1 Docker日志机制详解
Docker的日志机制与传统的应用日志不同。在Docker中,应用的日志输出主要通过标准输出(stdout)和标准错误(stderr)来实现,Docker守护进程会捕获这些输出并通过日志驱动(Logging Driver)进行处理。
Docker日志的工作原理:
┌─────────────────────────────────────────────────────┐
│ 容器内应用 │
│ ┌───────────────────────────────────────────┐ │
│ │ 应用程序输出日志 │ │
│ │ → stdout (标准输出) │ │
│ │ → stderr (标准错误) │ │
│ └────────────────┬──────────────────────────┘ │
└───────────────────┼─────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────┐
│ Docker Daemon │
│ ┌─────────────────────────────────────────┐ │
│ │ 日志驱动 (Logging Driver) │ │
│ │ 接收 stdout/stderr 输出 │ │
│ │ 根据驱动类型处理日志 │ │
│ └────────────────┬────────────────────────┘ │
└───────────────────┼───────────────────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌──────────┐ ┌────────┐ ┌────────────┐
│json-file │ │syslog │ │fluentd/gelf│
│(默认) │ │ │ │(远程日志) │
└──────────┘ └────────┘ └────────────┘
关键理解:
- Docker只捕获容器的stdout和stderr输出,不处理应用写到文件中的日志
- 日志驱动决定了Docker如何处理这些输出
docker logs命令只能查看使用json-file或journald驱动的容器日志- 每个容器可以独立配置日志驱动和日志选项
# 查看当前Docker的默认日志驱动
docker info --format '{{.LoggingDriver}}'
# 输出: json-file (默认)
# 查看指定容器的日志驱动
docker inspect --format='{{.HostConfig.LogConfig.Type}}' nginx-web
# 输出: json-file
7.2 Docker日志驱动大全
Docker支持多种日志驱动,每种驱动适用于不同的场景:
| 日志驱动 | 说明 | docker logs可用 | 适用场景 |
|---|---|---|---|
| json-file | 默认驱动,JSON格式存储到本地文件 | 是 | 单机开发测试 |
| local | 优化过的本地存储,二进制格式 | 是 | 单机生产(替代json-file) |
| syslog | 写入syslog系统日志服务 | 否 | 系统日志集中管理 |
| journald | 写入systemd journal | 是 | systemd系统 |
| fluentd | 推送到Fluentd | 否 | 日志收集转发 |
| fluentd-prefixed | 带前缀的Fluentd | 否 | 日志收集转发 |
| gelf | Graylog Extended Log Format | 否 | Graylog日志系统 |
| awslogs | 推送到AWS CloudWatch | 否 | AWS云环境 |
| gcplogs | 推送到GCP Logging | 否 | GCP云环境 |
| splunk | 推送到Splunk | 否 | Splunk日志系统 |
| etwlogs | Windows事件跟踪 | 否 | Windows容器 |
| none | 禁用日志 | 否 | 不需要日志的容器 |
# 运行时指定日志驱动
docker run -d \
--name nginx-web \
--log-driver=syslog \
--log-opt syslog-address=udp://192.168.1.100:514 \
--log-opt tag="nginx-web" \
nginx:latest
# 全局配置默认日志驱动(修改/etc/docker/daemon.json)
# 见下方7.3节
7.3 json-file日志驱动配置
json-file是Docker的默认日志驱动,它会将容器日志以JSON格式写入本地文件。如果不配置日志轮转,日志文件会无限增长,最终撑满磁盘。
日志轮转配置:
# 方式1:运行时配置日志轮转
docker run -d \
--name nginx-web \
--log-driver=json-file \
--log-opt max-size=10m \ # 单个日志文件最大10MB
--log-opt max-file=3 \ # 最多保留3个日志文件
--log-opt labels=production \ # 添加标签到日志
--log-opt env=NODE_ENV \ # 添加环境变量到日志
--log-opt compress=true \ # 启用压缩
nginx:latest
# 方式2:全局配置(影响所有新容器)
# 编辑 /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3",
"labels": "production",
"env": "os,customer",
"compress": "true"
}
}
# 修改daemon.json后重启Docker
sudo systemctl restart docker
# 注意:全局配置只对修改后新建的容器生效
# 已有容器需要重建才能应用新配置
json-file日志选项详解:
| 选项 | 说明 | 示例 |
|---|---|---|
| max-size | 单个日志文件最大大小 | 10m, 100m, 1g |
| max-file | 最多保留文件数 | 3, 5, 10 |
| labels | 添加Docker标签到日志元数据 | production,service |
| env | 添加环境变量到日志元数据 | NODE_ENV,APP_VERSION |
| compress | 压缩轮转的日志文件 | true/false |
使用local驱动(推荐替代json-file):
# local驱动使用二进制格式,比json-file更高效
docker run -d \
--name nginx-web \
--log-driver=local \
--log-opt max-size=10m \
--log-opt max-file=3 \
nginx:latest
# daemon.json全局配置
{
"log-driver": "local",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
7.4 docker logs命令高级用法
docker logs是查看容器日志的基本命令,支持多种过滤和格式化选项:
# 基本用法:查看所有日志
docker logs nginx-web
# 实时跟踪日志输出(类似tail -f)
docker logs -f nginx-web
# 或
docker logs --follow nginx-web
# 显示最后N行日志
docker logs --tail 100 nginx-web # 最后100行
docker logs --tail 50 -f nginx-web # 最后50行并持续跟踪
# 显示时间戳
docker logs -t nginx-web
# 输出: 2024-01-15T10:00:00.123456789Z 10.0.0.1 - GET /api/users 200
# 按时间过滤
docker logs --since 30m nginx-web # 最近30分钟的日志
docker logs --since 1h nginx-web # 最近1小时的日志
docker logs --since 2024-01-15T10:00:00 nginx-web # 从指定时间开始
docker logs --until 2024-01-15T11:00:00 nginx-web # 到指定时间结束
docker logs --since 2024-01-15T10:00:00 --until 2024-01-15T11:00:00 nginx-web
# 显示额外详细信息(日志的额外属性)
docker logs --details nginx-web
# 组合使用:查看最近10分钟的最后50行日志并跟踪
docker logs --since 10m --tail 50 -f nginx-web
高级日志分析技巧:
# 搜索包含"error"的日志行
docker logs nginx-web 2>&1 | grep -i "error"
# 统计错误日志数量
docker logs nginx-web 2>&1 | grep -c "error"
# 统计各HTTP状态码出现次数
docker logs nginx-web 2>&1 | grep -oP 'HTTP/\d\.\d" \d+' | awk '{print $NF}' | sort | uniq -c | sort -rn
# 查看最慢的请求(提取响应时间并排序)
docker logs nginx-web 2>&1 | grep "request_time" | awk '{print $NF}' | sort -rn | head -20
# 导出日志到文件
docker logs nginx-web > /tmp/nginx-logs-$(date +%Y%m%d).log 2>&1
# 多容器日志合并查看(使用Docker Compose)
docker compose logs -f # 所有服务
docker compose logs -f nginx redis # 指定服务
docker compose logs --since 1h # 最近1小时
# JSON格式日志的解析和格式化
docker logs nginx-web 2>&1 | python3 -c "
import sys, json
for line in sys.stdin:
try:
log = json.loads(line)
print(f'[{log.get(\"time\",\"\")}] {log.get(\"level\",\"\").upper()}: {log.get(\"msg\",\"\")}')
except json.JSONDecodeError:
print(line.rstrip())
"
7.5 容器日志格式化与结构化输出
结构化日志是指以JSON等机器可读格式输出的日志,相比纯文本日志,结构化日志更容易被日志系统解析和搜索。
非结构化日志(不推荐):
2024-01-15 10:00:00 INFO User john logged in from 192.168.1.100
2024-01-15 10:01:00 ERROR Failed to connect to database: timeout
结构化日志(推荐):
{"timestamp":"2024-01-15T10:00:00Z","level":"info","message":"User logged in","user":"john","ip":"192.168.1.100"}
{"timestamp":"2024-01-15T10:01:00Z","level":"error","message":"Database connection failed","error":"timeout","host":"db-master"}
各语言结构化日志示例:
# Python - 使用structlog或python-json-logger
import logging
from pythonjsonlogger import jsonlogger
# 配置JSON日志格式
logger = logging.getLogger()
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
'%(timestamp)s %(level)s %(message)s %(module)s %(funcName)s %(lineno)d'
)
logHandler.setFormatter(formatter)
logger.addHandler(logHandler)
logger.setLevel(logging.INFO)
# 输出结构化日志
logger.info("User logged in", extra={
"user": "john",
"ip": "192.168.1.100",
"method": "password"
})
// Java - 使用Logback + JSON编码器
// pom.xml添加依赖:
// <dependency>
// <groupId>net.logstash.logback</groupId>
// <artifactId>logstash-logback-encoder</artifactId>
// <version>7.4</version>
// </dependency>
// logback.xml配置:
// <configuration>
// <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
// <encoder class="net.logstash.logback.encoder.LogstashEncoder">
// <customFields>{"app":"my-service","env":"prod"}</customFields>
// </encoder>
// </appender>
// <root level="INFO">
// <appender-ref ref="STDOUT" />
// </root>
// </configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public void login(String username, String ip) {
MDC.put("user", username);
MDC.put("ip", ip);
MDC.put("action", "login");
logger.info("User logged in successfully");
MDC.clear();
}
}
// Go - 使用slog标准库(Go 1.21+)
package main
import (
"log/slog"
"os"
)
func main() {
// JSON格式输出到stdout
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("User logged in",
slog.String("user", "john"),
slog.String("ip", "192.168.1.100"),
slog.String("action", "login"),
)
}
// Node.js - 使用pino或winston
const pino = require('pino');
const logger = pino({
level: 'info',
// 输出到stdout
});
logger.info({
user: 'john',
ip: '192.168.1.100',
action: 'login'
}, 'User logged in');
7.6 日志级别管理
合理的日志级别管理是日志体系的重要部分。不同的日志级别对应不同的严重程度和处理优先级:
| 级别 | 数值 | 说明 | 示例 | 生产环境 |
|---|---|---|---|---|
| TRACE | 0 | 最详细的跟踪信息 | 方法入参出参 | 关闭 |
| DEBUG | 1 | 调试信息 | SQL语句、缓存命中 | 关闭 |
| INFO | 2 | 正常运行信息 | 用户登录、请求完成 | 开启 |
| WARN | 3 | 警告信息 | 重试成功、降级 | 开启 |
| ERROR | 4 | 错误信息 | 请求失败、异常 | 开启 |
| FATAL | 5 | 致命错误 | 服务无法启动 | 开启 |
日志级别配置示例:
# 通过环境变量配置日志级别(推荐)
docker run -d \
--name my-app \
-e LOG_LEVEL=info \ # 设置日志级别为info
-e LOG_FORMAT=json \ # 设置日志格式为json
my-app:latest
# 在Docker Compose中配置
# docker-compose.yml
services:
app:
image: my-app:latest
environment:
- LOG_LEVEL=info
- LOG_FORMAT=json
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
7.7 多容器日志查看技巧
在多容器环境中,查看和管理日志变得更加复杂。以下是实用的多容器日志管理技巧:
# Docker Compose多容器日志
docker compose logs # 所有服务日志
docker compose logs -f # 实时跟踪所有服务
docker compose logs -f web db # 指定服务
docker compose logs --since 1h # 最近1小时
docker compose logs --tail 100 # 最后100行
# 使用jq处理JSON格式日志
docker compose logs -f web | jq '.'
# 提取JSON日志中的特定字段
docker compose logs web | jq -r 'select(.level=="error") | "\(.timestamp) \(.message)"'
# 同时监控多个容器的日志(带前缀标识)
# 方式1:使用docker logs同时查看多个容器
docker logs -f web &
docker logs -f api &
docker logs -f db &
wait
# 方式2:使用docker compose logs
docker compose logs -f --tail 0 # 从最新行开始跟踪
# 方式3:使用脚本给日志添加容器名前缀
for container in web api db; do
docker logs -f --tail 0 "$container" | sed "s/^/[$container] /" &
done
wait
# 导出所有容器日志到文件
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' "$container" | sed 's/\///')
docker logs "$container" > "/tmp/${name}-$(date +%Y%m%d).log" 2>&1
done
7.8 日志驱动切换与影响
切换日志驱动需要注意其对功能的影响:
# 查看当前支持的日志驱动
docker info --format '{{.LoggingDriver}}'
# 查看所有支持的日志驱动
docker info --format '{{.Plugins.Log}}'
# 切换容器的日志驱动(需要重建容器)
# 注意:不能通过docker update更改日志驱动
# 停止并删除容器,使用新日志驱动重建
docker stop nginx-web
docker rm nginx-web
docker run -d \
--name nginx-web \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag="docker.{{.Name}}" \
nginx:latest
# 全局切换日志驱动(影响所有新容器)
# 编辑 /etc/docker/daemon.json
{
"log-driver": "fluentd",
"log-opts": {
"fluentd-address": "localhost:24224",
"tag": "docker.{{.Name}}"
}
}
日志驱动切换的影响:
| 影响项 | 说明 |
|---|---|
| docker logs命令 | 切换到非json-file/journald/local驱动后,docker logs命令不可用 |
| 日志文件位置 | 不同驱动的日志存储位置不同 |
| 性能影响 | 远程日志驱动(fluentd/gelf)会增加网络开销 |
| 历史日志 | 切换驱动后,之前的日志仍然存在,但通过新驱动无法查看 |
| 容器重建 | 大多数日志驱动变更需要重建容器 |
生产环境的日志驱动选择建议:
开发/测试环境: json-file (简单直接,支持docker logs)
单机生产环境: local (高效的二进制格式,支持轮转)
集中日志收集: fluentd 或 gelf (推送到日志收集系统)
云环境: awslogs/gcplogs (使用云平台日志服务)
第八章 ELK/EFK日志收集
8.1 ELK Stack概述
ELK Stack是Elasticsearch、Logstash、Kibana三个开源项目的首字母缩写,是最流行的日志收集、存储和可视化解决方案。
ELK各组件职责:
┌──────────────────────────────────────────────────────────────┐
│ ELK Stack 架构 │
│ │
│ 数据源 采集层 存储层 展示层 │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │容器日志 │───►│ │───►│ │──►│ │ │
│ │应用日志 │───►│ Logstash │ │Elastic- │ │ Kibana │ │
│ │系统日志 │───►│ /Filebeat│ │ search │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ └────────┘ └──────────┘ └──────────┘ └────────┘ │
│ 收集+解析 存储+索引 搜索+可视化 │
└──────────────────────────────────────────────────────────────┘
各组件说明:
| 组件 | 职责 | 特点 |
|---|---|---|
| Elasticsearch | 日志存储与全文搜索 | 基于Lucene的分布式搜索引擎 |
| Logstash | 日志采集、解析和转发 | 强大的Grok解析能力,但资源消耗较大 |
| Kibana | 日志可视化与搜索 | Web界面,支持图表和仪表盘 |
| Filebeat | 轻量级日志采集器 | 替代Logstash采集,资源消耗低 |
| Fluentd/Fluent Bit | 统一日志采集层 | 更轻量,CRI标准 |
8.2 EFK Stack
EFK Stack是ELK的变种,用Fluentd或Fluent Bit替代Logstash作为日志采集器。EFK更适合容器环境,因为Fluentd对容器日志有原生支持。
ELK vs EFK对比:
| 特性 | ELK (Logstash) | EFK (Fluentd/Fluent Bit) |
|---|---|---|
| 资源消耗 | 较高(JVM,内存500MB+) | 较低(Fluent Bit ~20MB) |
| 容器原生支持 | 一般 | 优秀(原生Docker日志驱动) |
| 插件生态 | 丰富 | 丰富 |
| 解析能力 | 强(Grok) | 强(正则) |
| 部署复杂度 | 中等 | 低(Fluent Bit) |
| 适合场景 | 复杂日志解析 | 容器日志收集 |
8.3 使用Docker Compose部署ELK Stack
以下是完整的ELK Stack Docker Compose部署方案:
# /opt/elk/docker-compose.yml
# ELK Stack完整部署 - Elasticsearch + Logstash + Kibana + Filebeat
version: '3.8'
services:
# ====================================================================
# Elasticsearch - 日志存储与搜索引擎
# ====================================================================
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: elasticsearch
restart: unless-stopped
ports:
- "9200:9200"
- "9300:9300"
environment:
- discovery.type=single-node # 单节点模式
- xpack.security.enabled=false # 禁用安全认证(生产环境需开启)
- ES_JAVA_OPTS=-Xms1g -Xmx1g # JVM堆内存设置
- cluster.name=elk-cluster # 集群名称
- node.name=elasticsearch-1 # 节点名称
- bootstrap.memory_lock=true # 锁定内存
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
networks:
- elk
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
# ====================================================================
# Logstash - 日志处理管道
# ====================================================================
logstash:
image: docker.elastic.co/logstash/logstash:8.11.0
container_name: logstash
restart: unless-stopped
ports:
- "5044:5044" # Filebeat输入端口
- "5000:5000/tcp" # TCP输入端口
- "9600:9600" # 监控API端口
environment:
- LS_JAVA_OPTS=-Xms512m -Xmx512m # JVM堆内存
volumes:
- ./logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml:ro
- ./logstash/pipeline:/usr/share/logstash/pipeline:ro
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
# ====================================================================
# Kibana - 日志可视化界面
# ====================================================================
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
container_name: kibana
restart: unless-stopped
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200 # ES地址
- KIBANA_SYSTEM_PASSWORD=kibana_password
- SERVER_NAME=kibana
- I18N_LOCALE=zh-CN # 中文界面
volumes:
- ./kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml:ro
networks:
- elk
depends_on:
elasticsearch:
condition: service_healthy
# ====================================================================
# Filebeat - 轻量级日志采集器
# ====================================================================
filebeat:
image: docker.elastic.co/beats/filebeat:8.11.0
container_name: filebeat
restart: unless-stopped
user: root
volumes:
# Filebeat配置文件
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
# Docker容器日志目录
- /var/lib/docker/containers:/var/lib/docker/containers:ro
# Docker日志目录(用于json-file驱动)
- /var/run/docker.sock:/var/run/docker.sock:ro
command: filebeat -e -strict.perms=false
networks:
- elk
depends_on:
- logstash
volumes:
elasticsearch_data:
networks:
elk:
driver: bridge
8.4 Filebeat采集容器日志
Filebeat是Elastic官方推出的轻量级日志采集器,专为容器日志采集优化:
# /opt/elk/filebeat/filebeat.yml
# Filebeat配置文件 - 采集Docker容器日志
# ======================== Filebeat 输入 ========================
filebeat.inputs:
# 采集Docker容器日志(通过JSON文件)
- type: container
paths:
- '/var/lib/docker/containers/*/*.log'
# 处理JSON格式日志
processors:
- decode_json_fields:
fields: ['message']
target: ''
overwrite_keys: true
# 添加Docker容器元数据
- add_docker_metadata:
host: "unix:///var/run/docker.sock"
# 添加Kubernetes元数据(如果在K8s中)
# - add_kubernetes_metadata:
# host: "${NODE_NAME}"
# matchers:
# - logs_path:
# logs_path: "/var/lib/docker/containers/"
# 采集应用日志文件(可选)
- type: log
enabled: true
paths:
- /var/log/app/*.log
fields:
app: my-application
env: production
fields_under_root: true
multiline:
pattern: '^\d{4}-\d{2}-\d{2}' # 多行匹配:以日期开头的行
negate: true
match: after
timeout: 5s
# ======================== Filebeat 处理器 ========================
processors:
# 去除不需要的字段
- drop_fields:
fields: ["agent.ephemeral_id", "agent.id", "agent.type", "agent.version"]
# 添加主机信息
- add_host_metadata:
when.not.contains.tags: forwarded
# 添加云元数据
- add_cloud_metadata: ~
# 解析Docker容器名
- script:
lang: javascript
source: >
function process(event) {
var containerName = event.Get("container.name");
if (containerName) {
event.Put("container_name", containerName);
}
}
# ======================== 输出到Logstash ========================
output.logstash:
hosts: ["logstash:5044"]
# 启用负载均衡
loadbalance: true
# 工作线程数
worker: 2
# 压缩
compression_level: 3
# 索引名称
index: filebeat
# 如果直接输出到Elasticsearch(跳过Logstash)
# output.elasticsearch:
# hosts: ["elasticsearch:9200"]
# indices:
# - index: "filebeat-nginx-%{+yyyy.MM.dd}"
# when.contains:
# container.name: "nginx"
# - index: "filebeat-app-%{+yyyy.MM.dd}"
# when.contains:
# container.name: "app"
# ======================== Filebeat 日志 ========================
logging.level: info
logging.to_files: true
logging.files:
path: /var/log/filebeat
name: filebeat
keepfiles: 7
permissions: 0644
# ======================== 监控 ========================
monitoring.enabled: true
monitoring.elasticsearch:
hosts: ["elasticsearch:9200"]
# ======================== 索引生命周期管理 ========================
setup.ilm.enabled: true
setup.ilm.rollover_alias: "filebeat"
setup.ilm.pattern: "{now/d}-000001"
8.5 Fluentd日志采集配置
Fluentd是EFK中的F,作为Docker的原生日志驱动,它可以直接从Docker接收日志:
# /opt/efk/docker-compose.yml
# EFK Stack - Elasticsearch + Fluentd + Kibana
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: elasticsearch
restart: unless-stopped
ports:
- "9200:9200"
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
networks:
- efk
fluentd:
image: fluent/fluentd:v1.16-debian-1
container_name: fluentd
restart: unless-stopped
ports:
- "24224:24224"
- "24224:24224/udp"
volumes:
- ./fluentd/fluent.conf:/fluentd/etc/fluent.conf:ro
- fluentd_data:/fluentd/log
environment:
- FLUENTD_CONF=fluent.conf
networks:
- efk
depends_on:
- elasticsearch
kibana:
image: docker.elastic.co/kibana/kibana:8.11.0
container_name: kibana
restart: unless-stopped
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
networks:
- efk
depends_on:
- elasticsearch
# 应用容器使用fluentd日志驱动
nginx:
image: nginx:latest
container_name: nginx-web
ports:
- "80:80"
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: "docker.nginx" # 日志标签
networks:
- efk
depends_on:
- fluentd
volumes:
elasticsearch_data:
fluentd_data:
networks:
efk:
driver: bridge
Fluentd配置文件:
# /opt/efk/fluentd/fluent.conf
# Fluentd配置文件 - 接收Docker日志并转发到Elasticsearch
# ======================== 数据源配置 ========================
# 接收Docker fluentd驱动推送的日志
<source>
@type forward
@id input_forward
port 24224
bind 0.0.0.0
</source>
# 接收HTTP推送的日志(可选)
<source>
@type http
@id input_http
port 9880
bind 0.0.0.0
<parse>
@type json
</parse>
</source>
# ======================== 日志处理过滤器 ========================
# 解析Nginx日志(使用正则表达式)
<filter docker.nginx>
@type parser
key_name log
<parse>
@type regexp
# Nginx combined日志格式正则表达式
expression /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z
</parse>
</filter>
# 解析JSON格式的应用日志
<filter docker.app.**>
@type parser
key_name log
reserve_data true
<parse>
@type json
</parse>
</filter>
# 添加Docker元数据
<filter docker.**>
@type record_transformer
<record>
# 添加采集时间戳
collected_at ${time}
# 添加主机名
hostname "#{Socket.gethostname}"
</record>
</filter>
# ======================== 输出配置 ========================
# 输出到Elasticsearch
<match docker.**>
@type elasticsearch
@id output_es
# Elasticsearch地址
host elasticsearch
port 9200
# 索引名称(按日期分割)
logstash_format true
logstash_prefix fluentd
logstash_dateformat %Y.%m.%d
# 索引模板
template_name fluentd
template_file /fluentd/etc/fluentd-template.json
# 缓冲配置
<buffer>
@type file
path /fluentd/buffer
flush_interval 5s
chunk_limit_size 8MB
total_limit_size 512MB
flush_thread_count 4
retry_max_interval 30s
retry_timeout 72h
</buffer>
# 请求配置
request_timeout 60s
reload_connections false
resurrect_after 5s
</match>
# 匹配所有未处理的日志(防止日志丢失)
<match **>
@type stdout
</match>
8.6 日志解析与字段提取
日志解析是将非结构化的日志文本转换为结构化字段的过程,这是日志分析的基础。
Logstash Grok解析:
# /opt/elk/logstash/pipeline/logstash.conf
# Logstash管道配置 - 日志解析与转发
input {
# 接收Filebeat数据
beats {
port => 5044
}
# TCP输入(接收应用直接推送的日志)
tcp {
port => 5000
codec => json_lines
tags => ["tcp-input"]
}
}
filter {
# 解析Nginx访问日志
if [container] and [container][name] =~ /nginx/ {
grok {
match => {
"message" => '%{IPORHOST:client_ip} - %{USERNAME:user} \[%{HTTPDATE:timestamp}\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status_code} %{NUMBER:bytes} "%{GREEDYDATA:referer}" "%{GREEDYDATA:user_agent}"'
}
overwrite => ["message"]
}
# 解析User-Agent
useragent {
source => "user_agent"
target => "ua"
}
# 转换字段类型
mutate {
convert => {
"status_code" => "integer"
"bytes" => "integer"
}
}
# 添加时间戳
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
}
# 解析Java应用日志(多行日志)
if [container] and [container][name] =~ /java-app/ {
# 合并多行日志(异常堆栈)
multiline {
pattern => "^\d{4}-\d{2}-\d{2}"
negate => true
what => "previous"
}
grok {
match => {
"message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:log_level} \[%{DATA:thread}\] %{DATA:logger} - %{GREEDYDATA:log_message}"
}
}
# 提取JSON格式的扩展字段
if [log_message] {
json {
source => "log_message"
target => "extra"
skip_on_invalid_json => true
}
}
}
# 通用:添加环境标签
mutate {
add_field => {
"environment" => "production"
}
}
# 移除不需要的字段
mutate {
remove_field => ["agent", "ecs", "input", "log"]
}
}
output {
# 输出到Elasticsearch
elasticsearch {
hosts => ["elasticsearch:9200"]
# 按日志类型分索引
index => "%{[@metadata][beat]}-%{[container][name]}-%{+YYYY.MM.dd}"
# 模板配置
manage_template => false
# 如果索引不存在,自动创建
action => "create"
}
# 输出到stdout(调试用)
# stdout {
# codec => rubydebug
# }
}
常用Grok模式:
| 模式 | 说明 | 匹配示例 |
|---|---|---|
%{IP:ip} | IP地址 | 192.168.1.1 |
%{WORD:word} | 单词 | hello |
%{NUMBER:num} | 数字 | 123, 45.67 |
%{EMAILADDRESS:email} | 邮箱 | user@example.com |
%{URI:url} | URL | http://example.com/path |
%{HTTPDATE:date} | HTTP日期 | 15/Jan/2024:10:00:00 +0800 |
%{LOGLEVEL:level} | 日志级别 | INFO, ERROR |
%{GREEDYDATA:data} | 任意字符 | 匹配剩余所有内容 |
8.7 Kibana日志可视化与搜索
Kibana是ELK/EFK的可视化组件,提供强大的日志搜索和分析能力。
Kibana核心功能:
- Discover:日志搜索与浏览,支持KQL(Kibana Query Language)查询
- Visualize:创建图表(柱状图、饼图、折线图等)
- Dashboard:组合多个可视化面板
- Dev Tools:直接执行Elasticsearch查询
Kibana查询语言(KQL):
# 基本查询
message: "error" # 搜索message字段包含error
status_code: 500 # 精确匹配状态码
status_code >= 400 # 数值比较
client_ip: 192.168.1.100 # 精确匹配IP
# 逻辑运算
message: "error" AND level: "ERROR" # AND
level: "ERROR" OR level: "WARN" # OR
NOT level: "DEBUG" # NOT
# 通配符
message: "error*" # 以error开头
message: "*timeout*" # 包含timeout
# 正则表达式
message: /error.*timeout/
# 组合查询
(status_code: 500 OR status_code: 502) AND NOT client_ip: 192.168.1.100
# 按字段存在性查询
log_level: * # 存在log_level字段
NOT log_level: * # 不存在log_level字段
# 范围查询
@timestamp >= "2024-01-15T10:00:00" AND @timestamp <= "2024-01-15T11:00:00"
bytes >= 1000 AND bytes <= 10000
Kibana配置文件:
# /opt/elk/kibana/config/kibana.yml
# Kibana配置文件
server.name: kibana
server.host: "0.0.0.0"
server.port: 5601
# Elasticsearch连接
elasticsearch.hosts: ["http://elasticsearch:9200"]
# elasticsearch.username: "kibana"
# elasticsearch.password: "password"
# 界面语言
i18n.locale: "zh-CN"
# 索引模式自动发现
kibana.index: ".kibana"
# 日志
logging.verbose: false
logging.json: false
# 安全配置(生产环境开启)
# xpack.security.encryptionKey: "something_at_least_32_characters"
# xpack.security.sessionTimeout: 600000
# xpack.encryptedSavedObjects.encryptionKey: "something_at_least_32_characters"
8.8 ELK日志告警
ELK Stack的日志告警可以通过ElastAlert或Kibana内置的Alerting实现:
使用Kibana Alerting(推荐):
// 在Kibana Stack Management -> Rules and Connectors中配置
// 创建一个告警规则:错误日志超过阈值
{
"name": "错误日志告警",
"rule_type_id": ".es-query",
"params": {
"index": ["filebeat-*"],
"timeField": "@timestamp",
"esQuery": "{\"query\":{\"bool\":{\"must\":[{\"match\":{\"log_level\":\"ERROR\"}}]}}}",
"size": 100,
"thresholdComparator": ">",
"threshold": [10],
"timeWindowSize": 5,
"timeWindowUnit": "m"
},
"actions": [
{
"id": "webhook-dingtalk",
"params": {
"body": "{\"msgtype\":\"text\",\"text\":{\"content\":\"告警:过去5分钟内错误日志超过10条\"}}"
}
}
]
}
8.9 日志索引管理与保留策略
日志数据量会持续增长,必须建立索引生命周期管理(ILM)策略,自动删除过期日志。
Elasticsearch ILM策略配置:
// 通过Kibana Dev Tools执行
// 创建ILM策略:7天后删除日志索引
PUT _ilm/policy/logs_retention_policy
{
"policy": {
"description": "日志保留7天策略",
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "10gb", // 单个索引最大10GB
"max_age": "1d" // 每天滚动一次
},
"set_priority": {
"priority": 100
}
}
},
"warm": {
"min_age": "2d", // 2天后进入warm阶段
"actions": {
"forcemerge": {
"max_num_segments": 1 // 合并段,减少磁盘占用
},
"set_priority": {
"priority": 50
}
}
},
"delete": {
"min_age": "7d", // 7天后删除
"actions": {
"delete": {}
}
}
}
}
}
// 创建索引模板,应用ILM策略
PUT _index_template/logs_template
{
"index_patterns": ["filebeat-*", "fluentd-*"],
"template": {
"settings": {
"index.lifecycle.name": "logs_retention_policy",
"number_of_shards": 1,
"number_of_replicas": 0,
"refresh_interval": "5s"
}
}
}
不同日志类型的保留策略:
| 日志类型 | 保留时间 | 原因 |
|---|---|---|
| 应用错误日志 | 30天 | 需要长期排查问题 |
| 应用访问日志 | 14天 | 用于审计和统计 |
| 系统日志 | 14天 | 用于排查系统问题 |
| 调试日志 | 3天 | 量太大,短期排查 |
| 审计日志 | 90天+ | 合规要求 |
8.10 ELK性能优化与集群部署
Elasticsearch性能优化:
# elasticsearch.yml 集群配置
cluster.name: elk-cluster
node.name: elasticsearch-1
node.roles: [master, data, ingest] # 角色分离(大型集群)
# 集群发现
discovery.seed_hosts: ["es-node1", "es-node2", "es-node3"]
cluster.initial_master_nodes: ["es-node1", "es-node2", "es-node3"]
# 网络配置
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
# 内存配置
bootstrap.memory_lock: true
# 线程池
thread_pool.write.queue_size: 1000
thread_pool.search.queue_size: 1000
# 索引设置
index:
number_of_shards: 3 # 分片数(根据数据量调整)
number_of_replicas: 1 # 副本数(至少1个,保证高可用)
refresh_interval: 5s # 刷新间隔(越大写入性能越好)
JVM调优:
# jvm.options
# 堆内存设置为物理内存的50%,且不超过32GB
-Xms8g
-Xmx8g
# GC策略
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
Logstash性能优化:
# logstash.yml
pipeline.workers: 4 # 工作线程数(等于CPU核心数)
pipeline.batch.size: 125 # 批处理大小
pipeline.batch.delay: 50 # 批处理延迟(ms)
# 缓冲队列
queue.type: persisted # 持久化队列
queue.max_bytes: 1gb # 队列最大大小
# 启动ELK Stack
cd /opt/elk
docker compose up -d
# 检查各组件状态
docker compose ps
curl http://localhost:9200/_cluster/health # ES健康状态
curl http://localhost:5601 # Kibana界面
# 查看索引
curl http://localhost:9200/_cat/indices?v
# 在Kibana中创建索引模式
# 访问 http://localhost:5601 -> Stack Management -> Index Patterns
# 创建索引模式: filebeat-*
# 时间字段: @timestamp
第九章 Loki + Promtail轻量日志方案
9.1 Grafana Loki概述与设计理念
Grafana Loki是Grafana Labs开源的水平可扩展、高可用的多租户日志聚合系统。它的设计灵感来自Prometheus,但专注于日志而非指标。Loki最大的特点是"仅索引元数据"——它不对日志内容建立全文索引,而是只索引日志流的标签(Label)。
Loki的核心设计理念:
┌─────────────────────────────────────────────────────────────┐
│ Loki vs Elasticsearch 索引对比 │
│ │
│ Elasticsearch (全文索引): │
│ ┌──────────────────────────────────────────────┐ │
│ │ 日志行: "User john login failed" │ │
│ │ 索引: │ │
│ │ user → [doc1, doc5, doc12...] │ │
│ │ john → [doc1, doc5, doc12...] │ │
│ │ login → [doc1, doc8, doc15...] │ │
│ │ failed → [doc1, doc3, doc9...] │ │
│ │ 存储开销:大(索引大小可能是日志的2-5倍) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Loki (仅索引元数据): │
│ ┌──────────────────────────────────────────────┐ │
│ │ 日志流: {app="nginx", env="prod"} │ │
│ │ 索引: │ │
│ │ app=nginx, env=prod → [stream_001] │ │
│ │ 日志内容: 压缩存储,不建索引 │ │
│ │ 存储开销:极小(仅为日志大小的1-5%) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Loki的优势:
- 极低的存储成本:不索引日志内容,存储成本仅为ELK的1/10到1/100
- 简单的运维:架构简单,无需像ES那样维护分片和副本
- 与Grafana无缝集成:作为Grafana原生日志数据源,体验优秀
- 与Prometheus一致的标签模型:可以复用Prometheus的标签体系
- LogQL查询语言:类似PromQL,学习成本低
- 水平可扩展:支持读写分离,可以独立扩展
Loki的劣势:
- 全文搜索性能不如ES:由于不索引内容,全文搜索需要扫描日志流
- 不适合复杂日志解析:Loki不做日志解析,解析在查询时进行
- 社区相对较新:生态不如ELK成熟
9.2 Loki vs ELK对比
| 特性 | Loki | ELK (Elasticsearch) |
|---|---|---|
| 索引方式 | 仅索引元数据(标签) | 全文索引 |
| 存储成本 | 极低(1-5%日志大小) | 高(索引2-5倍日志大小) |
| 资源消耗 | 低(单节点1GB内存即可) | 高(至少4GB内存) |
| 全文搜索 | 一般(需扫描日志) | 优秀(倒排索引) |
| 日志解析 | 查询时解析(LogQL) | 采集时解析(Grok) |
| 查询语言 | LogQL(类PromQL) | KQL + Lucene |
| 可视化 | Grafana(原生集成) | Kibana |
| 运维复杂度 | 低 | 高(分片、副本、ILM) |
| 多租户 | 原生支持 | 需额外配置 |
| 适合场景 | 云原生/容器日志 | 复杂日志分析/全文搜索 |
| 部署方式 | 单二进制/容器 | 多组件集群 |
选择建议:
- 日志量极大、预算有限:选Loki,存储成本极低
- 需要复杂日志分析、全文搜索:选ELK
- 已使用Prometheus + Grafana:选Loki,生态一致
- 需要日志告警:两者都支持,Loki通过Grafana Alerting
- 多租户需求:选Loki,原生支持
9.3 Loki安装与配置
Loki架构:
┌──────────────────────────────────────────────────┐
│ Loki 架构 │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Distributor│ │ Ingester│ │
│ │ (写入分发) │ │ (写入处理)│ │
│ └─────┬─────┘ └─────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ Chunk Store (存储) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ 本地 │ │ S3 │ │GCS等 │ │ │
│ │ │文件系统│ │ │ │ │ │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ └──────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Querier │ │ Query │ │
│ │ (查询) │ │ Frontend │ │
│ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────┘
使用Docker部署Loki:
# 创建Loki配置目录
mkdir -p /opt/loki/{config,rules,data}
# 下载默认配置文件
wget -O /opt/loki/config/loki.yml \
https://raw.githubusercontent.com/grafana/loki/main/cmd/loki/loki-local-config.yaml
# 运行Loki容器
docker run -d \
--name=loki \
-p 3100:3100 \
-v /opt/loki/config/loki.yml:/etc/loki/local-config.yaml:ro \
-v /opt/loki/data:/loki \
grafana/loki:2.9.0 \
-config.file=/etc/loki/local-config.yaml
# 验证Loki
curl http://localhost:3100/ready
curl http://localhost:3100/metrics
Loki配置文件详解:
# /opt/loki/config/loki.yml
# Loki完整配置文件
auth_enabled: false # 禁用多租户认证(单租户模式)
server:
http_listen_port: 3100 # HTTP端口
grpc_listen_port: 9096 # gRPC端口
log_level: info # 日志级别
# 常见配置
common:
path_prefix: /loki # 数据存储路径
storage:
filesystem:
chunks_directory: /loki/chunks # 日志块存储
rules_directory: /loki/rules # 规则存储
replication_factor: 1 # 副本数(单节点为1)
ring:
kvstore:
store: inmemory # 单节点使用内存ring
# 查询范围配置
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100 # 缓存最大100MB
# 模式配置(定义索引和日志块如何存储)
schema_config:
configs:
- from: 2024-01-01 # 配置生效日期
store: tsdb # 存储引擎(tsdb比boltdb-shipper更高效)
object_store: filesystem # 对象存储(本地文件系统)
schema: v13 # schema版本
index:
prefix: index_ # 索引前缀
period: 24h # 索引周期(每天一个)
# 存储配置
storage_config:
# TSDB存储配置
tsdb_shipper:
active_index_directory: /loki/tsdb-index # 活跃索引目录
cache_location: /loki/tsdb-cache # 缓存目录
# 文件系统存储
filesystem:
directory: /loki/chunks # 日志块目录
# 限制配置
limits_config:
# 摄取限制
ingestion_rate_mb: 10 # 每秒摄入速率限制(MB)
ingestion_burst_size_mb: 20 # 突发摄入大小(MB)
# 查询限制
max_query_series: 5000 # 最大查询序列数
max_query_parallelism: 16 # 最大查询并行度
max_query_length: 721h # 最大查询时间范围(30天)
# 日志限制
max_streams_per_user: 0 # 每用户最大流数(0=无限)
max_global_streams_per_user: 0
# 保留策略
retention_period: 30d # 日志保留30天
# 阻止旧数据摄入
reject_old_samples: true
reject_old_samples_max_age: 168h # 超过7天的数据拒绝摄入
# 压缩器配置(合并和压缩旧的日志块)
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
retention_enabled: true # 启用保留策略
retention_delete_delay: 2h
retention_delete_worker_count: 150
delete_request_store: filesystem
# 告警规则
ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://alertmanager:9093 # AlertManager地址
ring:
kvstore:
store: inmemory
enable_api: true
# 分析配置
analytics:
reporting_enabled: false # 禁用匿名使用统计
9.4 Promtail日志采集Agent
Promtail是Loki官方的日志采集Agent,专为Loki设计,负责采集日志、添加标签并推送到Loki。
Promtail的工作原理:
┌─────────────────────────────────────────────────────┐
│ Promtail 工作流程 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 日志文件 │──►│ Tail │──►│ Pipeline │ │
│ │ 容器日志 │ │ (跟踪) │ │ (解析) │ │
│ └──────────┘ └──────────┘ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Labels │◄──│ 添加标签+时间戳│ │
│ │ (标签) │ └──────┬───────┘ │
│ └──────────┘ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ Batch │──►│ Push to Loki│ │
│ │ (批处理) │ │ (HTTP推送) │ │
│ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
使用Docker部署Promtail:
# 运行Promtail容器
docker run -d \
--name=promtail \
-v /var/lib/docker/containers:/var/lib/docker/containers:ro \
-v /var/log:/var/log:ro \
-v /opt/promtail/promtail.yml:/etc/promtail/config.yml:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
grafana/promtail:2.9.0 \
-config.file=/etc/promtail/config.yml
9.5 Promtail配置文件详解
# /opt/promtail/promtail.yml
# Promtail完整配置文件
server:
http_listen_port: 9080 # HTTP端口
grpc_listen_port: 0
log_level: info
positions:
filename: /tmp/positions.yaml # 读取位置记录文件(防止重启后重复读取)
# Loki客户端配置
clients:
- url: http://loki:3100/loki/api/v1/push # Loki推送地址
# 批处理配置
batchwait: 1s # 批处理等待时间
batchsize: 1048576 # 批处理大小(1MB)
# 超时配置
timeout: 10s
backoff_config:
min_period: 500ms # 最小重试间隔
max_period: 5m # 最大重试间隔
max_retries: 10 # 最大重试次数
# 多租户(可选)
# tenant_id: team1
# 日志采集配置
scrape_configs:
# ==================== 采集Docker容器日志 ====================
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
filters:
- name: label
values: ["logging=promtail"] # 只采集有logging=promtail标签的容器
# 使用relabel_configs提取Docker容器信息作为Loki标签
relabel_configs:
# 提取容器名
- source_labels: ['__meta_docker_container_name']
regex: '/(.*)'
target_label: 'container_name'
# 提取Docker Compose服务名
- source_labels: ['__meta_docker_container_label_com_docker_compose_service']
target_label: 'service'
# 提取镜像名
- source_labels: ['__meta_docker_container_label_com_docker_compose_project']
target_label: 'project'
# 提取环境
- source_labels: ['__meta_docker_container_label_environment']
target_label: 'environment'
# 管道处理配置
pipeline_stages:
# 解析Docker json-file驱动输出的JSON日志
- docker: {}
# 如果日志本身是JSON,解析JSON字段
- json:
expressions:
level: level
msg: message
timestamp: timestamp
# 设置日志时间戳
- timestamp:
source: timestamp
format: RFC3339Nano
fallback_formats:
- "2006-01-02 15:04:05"
# 提取日志级别作为标签
- labels:
level:
# ==================== 采集系统日志 ====================
- job_name: syslog
static_configs:
- targets:
- localhost
labels:
job: syslog
host: docker-host
__path__: /var/log/syslog
pipeline_stages:
# 解析syslog格式
- regex:
expression: '^(?P<timestamp>\w{3}\s+\d+\s\d{2}:\d{2}:\d{2})\s(?P<host>\S+)\s(?P<process>\S+):\s(?P<message>.*)$'
- timestamp:
source: timestamp
format: "Jan 2 15:04:05"
# ==================== 采集Nginx访问日志 ====================
- job_name: nginx_access
static_configs:
- targets:
- localhost
labels:
job: nginx
log_type: access
__path__: /var/log/nginx/access.log
pipeline_stages:
# 解析Nginx combined日志格式
- regex:
expression: '^(?P<remote_addr>\S+)\s-\s(?P<remote_user>\S+)\s\[(?P<time_local>[^\]]+)\]\s"(?P<method>\S+)\s(?P<request>\S+)\s(?P<protocol>[^"]+)"\s(?P<status>\d+)\s(?P<body_bytes_sent>\d+)\s"(?P<referer>[^"]*)"\s"(?P<user_agent>[^"]*)"'
# 转换字段类型
- template:
source: status
template: '{{ if eq .Value "200" }}OK{{ else if or (eq .Value "404") (eq .Value "500") }}ERROR{{ else }}OTHER{{ end }}'
- labels:
status:
# ==================== 采集应用日志(多行日志处理) ====================
- job_name: app_logs
static_configs:
- targets:
- localhost
labels:
job: application
app: my-spring-app
__path__: /var/log/app/*.log
pipeline_stages:
# 多行日志合并(Java异常堆栈)
- multiline:
firstline: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'
max_wait_time: 3s
# 解析JSON日志
- json:
expressions:
level: level
logger: logger
message: message
thread: thread
# 设置时间戳
- timestamp:
source: timestamp
format: RFC3339
# 添加标签
- labels:
level:
logger:
# 全局限制
limits_config:
readline_rate: 10000 # 每秒最大读取行数
readline_burst: 10000 # 突发读取行数
readline_rate_drop: true # 超过限制时丢弃
9.6 LogQL查询语言
LogQL是Loki的查询语言,语法类似PromQL,但针对日志进行了优化。LogQL查询由两部分组成:日志流选择器和管道操作。
1. 日志流选择器:
# 基本语法: {label="value"}
{job="syslog"} # 选择job标签为syslog的日志流
{container_name="nginx-web"} # 选择特定容器
{job="nginx", level="error"} # 多标签匹配
{job=~"nginx.*"} # 正则匹配
{job!="syslog"} # 排除
{job=~"nginx|redis", level!="debug"} # 组合条件
2. 日志管道操作:
# 过滤日志行
{job="nginx"} |= "error" # 包含error
{job="nginx"} != "debug" # 不包含debug
{job="nginx"} |~ "error.*timeout" # 正则匹配
{job="nginx"} !~ "debug|trace" # 正则不匹配
# 链式过滤
{job="nginx"} |= "error" != "client"
{container_name="my-app"} |= "exception" |~ "NullPointerException"
3. 日志解析:
# json解析器:解析JSON格式日志
{job="app"} | json
# 解析后提取字段
{job="app"} | json | level="error"
# logfmt解析器:解析logfmt格式日志
{job="app"} | logfmt
# regex解析器:正则解析
{job="nginx"} | regexp "(?P<method>\\w+) (?P<path>\\S+) (?P<status>\\d+)"
# pattern解析器:简单模式匹配
{job="app"} | pattern "<ip> <method> <path> <status>"
4. 指标查询:
LogQL不仅可以查询日志,还可以从日志中计算指标:
# 统计日志行数(每秒)
rate({job="nginx"}[5m])
# 按标签分组统计
sum by (status) (rate({job="nginx"} |= "access" | json[5m]))
# 计算错误率
sum(rate({job="app", level="error"}[5m]))
/ sum(rate({job="app"}[5m])) * 100
# 统计P99响应时间(需要日志中有延迟字段)
quantile_over_time(0.99, {job="app"} | json | unwrap latency [5m]) by (endpoint)
# 直方图
sum by (le) (rate({job="app"} | json | unwrap latency [5m]))
5. 常用LogQL查询示例:
# 查看特定容器的最新100条错误日志
{container_name="my-app"} |= "ERROR" | json | line_format "{{.timestamp}} {{.level}} {{.message}}"
# 统计过去1小时每个容器的日志量
sum by (container_name) (count_over_time({container_name=~".+"}[1h]))
# 查看包含异常堆栈的日志(Java)
{job="app"} |~ "Exception\\n\\s+at" | json
# 计算错误率趋势(每分钟)
sum by (container_name) (rate({container_name=~"app.*", level="ERROR"}[1m]))
# 查找响应时间超过1秒的请求
{job="nginx"} | json | duration > 1s
# 按HTTP状态码分组统计请求量
sum by (status) (count_over_time({job="nginx"} | regexp "(?P<status>\\d{3})" [5m]))
# 查看最近5分钟的告警日志
{job="alert"} | json | timestamp > "2024-01-15T10:00:00"
# 带格式的日志输出
{container_name="nginx"} |= "GET"
| regexp "(?P<ip>\\S+) .*(?P<method>GET|POST) (?P<path>\\S+) (?P<status>\\d+)"
| line_format "{{.ip}} {{.method}} {{.path}} -> {{.status}}"
9.7 Grafana中查询Loki日志
在Grafana中查询Loki日志非常简单,因为Loki是Grafana的原生数据源:
1. 在Explore中查询日志:
# 打开Grafana -> Explore -> 选择Loki数据源
# 输入LogQL查询:
{container_name="nginx-web"} |= "error"
# 使用日志面板(Log panel)查看原始日志
# 使用时间序列面板(Time series)查看指标
2. 创建Loki日志Dashboard:
{
"title": "应用日志Dashboard",
"panels": [
{
"title": "错误日志实时流",
"type": "logs",
"datasource": {"type": "loki", "uid": "loki"},
"targets": [{
"expr": "{container_name=~\"app.*\"} |= \"ERROR\"",
"refId": "A"
}],
"options": {
"showTime": true,
"showLabels": true,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": false,
"enableLogDetails": true,
"dedupStrategy": "none",
"sortOrder": "Descending"
},
"gridPos": {"h": 12, "w": 24, "x": 0, "y": 0}
},
{
"title": "日志量趋势(按容器)",
"type": "timeseries",
"datasource": {"type": "loki", "uid": "loki"},
"targets": [{
"expr": "sum by (container_name) (rate({container_name=~\".+\"}[5m]))",
"refId": "A",
"legendFormat": "{{container_name}}"
}],
"fieldConfig": {
"defaults": {"unit": "ops"}
},
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 12}
},
{
"title": "错误率",
"type": "stat",
"datasource": {"type": "loki", "uid": "loki"},
"targets": [{
"expr": "sum(rate({job=\"app\", level=\"error\"}[5m])) / sum(rate({job=\"app\"}[5m])) * 100",
"refId": "A"
}],
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 1},
{"color": "red", "value": 5}
]
}
}
},
"gridPos": {"h": 8, "w": 6, "x": 12, "y": 12}
}
]
}
9.8 Loki多租户与标签设计
多租户模式:
Loki原生支持多租户,通过X-Scope-OrgIDHTTP头来区分租户:
# Loki配置中启用多租户
auth_enabled: true # 设置为true启用认证
# 每个请求需要携带租户ID
# Promtail配置
clients:
- url: http://loki:3100/loki/api/v1/push
tenant_id: team-backend # 后端团队
# 查询时指定租户
curl -H "X-Scope-OrgID: team-backend" http://loki:3100/loki/api/v1/query?query={job="app"}
标签设计最佳实践:
标签设计是Loki使用中最关键的决策,因为Loki只索引标签:
# 好的标签设计(标签基数低)
{job="nginx", environment="prod", datacenter="dc1"}
# 坏的标签设计(标签基数高,会导致大量日志流)
# request_id, session_id, user_id 等高基数字段不应作为标签
{job="nginx", request_id="abc123xyz"} # 错误!每个请求都是不同的日志流
# 标签设计原则:
# 1. 标签值应该是有限且可枚举的
# 2. 标签数量建议控制在10个以内
# 3. 不要用高基数字段(IP、URL、用户ID)作为标签
# 4. 标签应该用于区分日志来源,而非日志内容
推荐的标签方案:
| 标签 | 示例值 | 说明 |
|---|---|---|
| job | nginx, redis, app | 日志来源类型 |
| container_name | nginx-web-01 | 容器名 |
| environment | prod, staging, dev | 环境 |
| service | user-service, order-service | 微服务名 |
| level | info, warn, error | 日志级别(低基数) |
| host | docker-host-1 | 主机名 |
| datacenter | dc1, dc2 | 数据中心 |
9.9 使用Docker Compose部署Loki + Promtail + Grafana
以下是完整的Loki日志栈Docker Compose配置:
# /opt/loki-stack/docker-compose.yml
# Loki日志栈 - Loki + Promtail + Grafana
version: '3.8'
services:
# ====================================================================
# Loki - 日志存储
# ====================================================================
loki:
image: grafana/loki:2.9.0
container_name: loki
restart: unless-stopped
ports:
- "3100:3100"
volumes:
- ./loki/loki.yml:/etc/loki/local-config.yaml:ro
- loki_data:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- loki-stack
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:3100/ready | grep -q 'ready'"]
interval: 10s
timeout: 5s
retries: 5
# ====================================================================
# Promtail - 日志采集
# ====================================================================
promtail:
image: grafana/promtail:2.9.0
container_name: promtail
restart: unless-stopped
ports:
- "9080:9080"
volumes:
- ./promtail/promtail.yml:/etc/promtail/config.yml:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/log:/var/log:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- promtail_data:/tmp
command: -config.file=/etc/promtail/config.yml
networks:
- loki-stack
depends_on:
- loki
# ====================================================================
# Grafana - 日志可视化
# ====================================================================
grafana:
image: grafana/grafana:10.2.0
container_name: grafana-loki
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_INSTALL_PLUGINS=grafana-piechart-panel
networks:
- loki-stack
depends_on:
- loki
volumes:
loki_data:
promtail_data:
grafana_data:
networks:
loki-stack:
driver: bridge
对应的Grafana数据源自动配置:
# /opt/loki-stack/grafana/provisioning/datasources/loki.yml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: true
jsonData:
maxLines: 1000
# 启用日志上下文
derivedFields:
- datasourceUid: jaeger
matcherRegex: "traceID=(\\w+)"
name: TraceID
url: "$${__value.raw}"
# 启动Loki日志栈
cd /opt/loki-stack
docker compose up -d
# 验证
docker compose ps
curl http://localhost:3100/ready
curl http://localhost:9080/targets
# 在Grafana中查询日志
# 访问 http://localhost:3000 -> Explore -> 选择Loki
# 输入: {container_name=~".+"} 查看所有容器日志
9.10 生产环境Loki部署与优化
生产环境Loki架构(微服务模式):
┌──────────────────────────────────────────────────────────────┐
│ 生产环境Loki架构 (微服务模式) │
│ │
│ ┌──────────────┐ │
│ │ Gateway/Nginx│ │
│ └──────┬───────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │Distributor │ │ Querier │ │ Ruler │ │
│ │ (写入入口) │ │ (查询) │ │ (告警规则) │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ ┌────────────┐ │ ┌────────────┐ │
│ │ Ingester │ │ │ Compactor │ │
│ │ (日志写入) │ │ │ (压缩) │ │
│ └─────┬──────┘ │ └────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ 对象存储 (S3/Minio/GCS) │ │
│ │ ┌────────┐ ┌────────┐ │ │
│ │ │ Chunks │ │ Index │ │ │
│ │ └────────┘ └────────┘ │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
生产环境优化要点:
- 使用对象存储:生产环境必须使用S3/Minio/GCS作为存储后端,不要用本地文件系统
- 读写分离:部署独立的Distributor/Ingester(写入)和Querier(读取)
- 缓存配置:启用查询缓存和索引缓存,提高查询性能
- 资源限制:配置合理的摄入和查询限制,防止过载
- 压缩器:启用压缩器,定期合并和压缩旧的日志块
- 监控Loki自身:使用Prometheus监控Loki的运行状态
使用S3作为存储后端:
# Loki配置 - S3存储
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
# 存储到S3
object_store: s3
aws:
s3: s3://loki-bucket # S3 bucket名称
s3forcepathstyle: true # 使用路径风格(Minio兼容)
region: us-east-1 # AWS区域
# 或使用Minio
endpoint: minio:9000 # Minio地址
access_key_id: minioadmin # 访问密钥
secret_access_key: minioadmin # 秘密密钥
第十章 应用监控与告警最佳实践
10.1 应用级监控(RED方法)
RED方法是Tom Wilkie提出的一种微服务监控方法论,专注于服务的外部可观测性。RED代表三个核心指标:
RED = Rate + Errors + Duration
| 指标 | 含义 | 计算方式 | 示例 |
|---|---|---|---|
| Rate(速率) | 每秒请求数 | rate(http_requests_total[5m]) | 1000 req/s |
| Errors(错误) | 错误请求率 | rate(http_requests_total{status=~"5.."}[5m]) | 5 errors/s |
| Duration(延迟) | 请求处理时间 | histogram_quantile(0.99, rate(http_duration_bucket[5m])) | P99 = 200ms |
RED方法在Prometheus中的实现:
# Rate: 每秒请求数(按API分组)
sum by (handler) (rate(http_requests_total[5m]))
# Errors: 错误率(5xx错误)
sum by (handler) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (handler) (rate(http_requests_total[5m])) * 100
# Duration: P99延迟(按API分组)
histogram_quantile(0.99, sum by (handler, le) (rate(http_request_duration_seconds_bucket[5m])))
# Duration: 平均延迟
sum(rate(http_request_duration_seconds_sum[5m]))
/ sum(rate(http_request_duration_seconds_count[5m]))
RED方法Grafana Dashboard建议:
每个服务应该至少有以下几个面板:
- 请求速率(Rate) - 时间序列图,按API路径分组
- 错误率(Errors) - 时间序列图,标记5xx错误
- 请求延迟(Duration) - 热力图或时间序列图,展示P50/P95/P99
- 请求量Top 10 - 条形图,展示最繁忙的API
10.2 USE方法
USE方法(Utilization、Saturation、Errors)是Brendan Gregg提出的资源监控方法论,专注于系统资源的使用情况。
USE = Utilization + Saturation + Errors
| 指标 | 含义 | 适用资源 | 示例 |
|---|---|---|---|
| Utilization(利用率) | 资源使用比例 | CPU、内存、磁盘、网络 | CPU使用率85% |
| Saturation(饱和度) | 资源排队程度 | CPU队列、磁盘I/O队列 | 磁盘I/O等待队列长度 |
| Errors(错误) | 错误计数 | 网络丢包、磁盘错误 | 网络丢包率0.1% |
USE方法在各资源中的应用:
| 资源 | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | 1 - rate(node_cpu_seconds_total{mode="idle"}[5m]) | node_load1 / core_count | - |
| 内存 | 1 - (MemAvailable / MemTotal) | OOM事件 | OOM Kill次数 |
| 磁盘 | disk_used / disk_total | rate(node_disk_io_time_seconds_total[5m]) | I/O错误 |
| 网络 | rate(node_network_receive_bytes_total[5m]) / bandwidth | 网络队列长度 | rate(node_network_receive_errs_total[5m]) |
10.3 Golden Signals
Golden Signals是Google SRE书中提出的四个核心监控信号,被认为是服务监控的黄金标准:
Four Golden Signals = Latency + Traffic + Errors + Saturation
| 信号 | 含义 | 与RED/USE的关系 | 示例 |
|---|---|---|---|
| Latency(延迟) | 请求处理时间 | RED的Duration | P99 = 200ms |
| Traffic(流量) | 请求量 | RED的Rate | 1000 req/s |
| Errors(错误) | 错误请求 | RED的Errors | 0.5% error rate |
| Saturation(饱和度) | 资源饱和程度 | USE的Saturation | CPU 85% |
Golden Signals的告警设计:
# 基于Golden Signals的告警规则
groups:
- name: golden_signals_alerts
rules:
# Latency: P99延迟过高
- alert: HighLatencyP99
expr: |
histogram_quantile(0.99, sum by(le)(rate(http_request_duration_seconds_bucket[5m]))) > 1
for: 5m
labels:
severity: warning
signal: latency
annotations:
summary: "P99延迟超过1秒"
description: "当前P99延迟: {{ $value }}秒"
# Traffic: 请求量突增(可能是DDoS或流量洪峰)
- alert: TrafficSpike
expr: |
sum(rate(http_requests_total[5m])) >
avg(sum(rate(http_requests_total[5m]))[1d:1m]) * 3
for: 5m
labels:
severity: warning
signal: traffic
annotations:
summary: "流量突增(超过日均3倍)"
# Errors: 错误率过高
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) * 100 > 1
for: 5m
labels:
severity: critical
signal: errors
annotations:
summary: "错误率超过1%"
description: "当前错误率: {{ $value }}%"
# Saturation: 资源饱和
- alert: HighSaturation
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 5m
labels:
severity: critical
signal: saturation
annotations:
summary: "内存饱和度超过90%"
10.4 分布式追踪
分布式追踪是微服务可观测性的重要组成部分,它记录请求在多个服务间的调用链路,帮助定位跨服务性能问题。
Jaeger与Docker集成:
# docker-compose.yml - Jaeger分布式追踪
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:1.50
container_name: jaeger
ports:
- "16686:16686" # Jaeger UI
- "14268:14268" # HTTP接收端口
- "6831:6831/udp" # Agent端口
environment:
- COLLECTOR_OTLP_ENABLED=true # 启用OTLP协议
networks:
- tracing
# 示例应用(启用Jaeger追踪)
app:
image: my-app:latest
environment:
- JAEGER_AGENT_HOST=jaeger
- JAEGER_AGENT_PORT=6831
- JAEGER_SAMPLER_TYPE=const
- JAEGER_SAMPLER_PARAM=1
depends_on:
- jaeger
networks:
- tracing
networks:
tracing:
driver: bridge
Zipkin与Docker集成:
# docker-compose.yml - Zipkin分布式追踪
services:
zipkin:
image: openzipkin/zipkin:3.0
container_name: zipkin
ports:
- "9411:9411" # Zipkin UI和API
environment:
- STORAGE_TYPE=mem # 内存存储(生产环境使用ES)
10.5 自定义应用指标暴露
使用Prometheus客户端库暴露自定义应用指标:
Python应用示例:
# app.py - Python Flask应用暴露Prometheus指标
from flask import Flask, request, Response
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
import time
app = Flask(__name__)
# 定义指标
# 1. Counter: 请求总数
REQUEST_COUNT = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status']
)
# 2. Histogram: 请求延迟
REQUEST_LATENCY = Histogram(
'http_request_duration_seconds',
'HTTP request latency',
['method', 'endpoint'],
buckets=[0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10] # 自定义桶
)
# 3. Gauge: 当前活跃连接数
ACTIVE_CONNECTIONS = Gauge(
'app_active_connections',
'Number of active connections'
)
# 中间件:自动记录请求指标
@app.before_request
def before_request():
request.start_time = time.time()
ACTIVE_CONNECTIONS.inc()
@app.after_request
def after_request(response):
latency = time.time() - request.start_time
REQUEST_LATENCY.labels(
method=request.method,
endpoint=request.path
).observe(latency)
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.path,
status=str(response.status_code)
).inc()
ACTIVE_CONNECTIONS.dec()
return response
# 暴露/metrics端点
@app.route('/metrics')
def metrics():
return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)
@app.route('/api/users')
def get_users():
time.sleep(0.1) # 模拟处理时间
return {"users": []}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
Go应用示例:
// main.go - Go应用暴露Prometheus指标
package main
import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// Counter
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"method", "path", "status"},
)
// Histogram
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration",
Buckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10},
},
[]string{"method", "path"},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
prometheus.MustRegister(httpRequestDuration)
}
func metricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 使用自定义ResponseWriter捕获状态码
wrapped := &responseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(wrapped, r)
duration := time.Since(start).Seconds()
httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, string(rune(wrapped.status))).Inc()
httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond)
w.Write([]byte(`{"users":[]}`))
})
mux.Handle("/metrics", promhttp.Handler())
// 应用中间件
http.ListenAndServe(":8080", metricsMiddleware(mux))
}
type responseWriter struct {
http.ResponseWriter
status int
}
10.6 告警设计原则与反模式
告警设计六大原则:
-
每个告警都应该可执行:收到告警后,运维人员应该知道该做什么。如果不知道如何处理,这个告警就是噪音。
-
避免告警风暴:一个故障可能触发大量告警,需要通过告警分组和抑制规则来减少噪音。
-
区分严重级别:使用severity标签区分Critical(需要立即处理)和Warning(可以延后处理)。
-
告警应该描述症状而非原因:"订单服务错误率5%"比"数据库连接数100"更有意义。
-
设置合理的持续时间(for):瞬时抖动不应该触发告警,持续异常才需要告警。
-
定期审查告警:定期回顾告警历史,移除无效告警,调整阈值。
告警反模式:
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| 告警过多 | 告警疲劳,忽视重要告警 | 只对需要人工干预的情况告警 |
| 阈值固定 | 不同时段的基线不同 | 使用动态阈值或异常检测 |
| 无分组 | 告警风暴淹没关键信息 | 使用AlertManager分组 |
| 无抑制 | 级联故障产生大量重复告警 | 配置inhibit_rules |
| 只告警不恢复 | 不知道故障何时恢复 | 启用send_resolved |
| 告警无文档 | 不知道如何处理 | 在annotations中包含处理步骤 |
10.7 监控SLO/SLI设计
SLO(Service Level Objective)和SLI(Service Level Indicator)是现代SRE实践的核心概念:
概念定义:
- SLI(服务等级指标):量化服务质量的指标,如"成功请求比例"
- SLO(服务等级目标):SLI的目标值,如"99.9%的请求在200ms内成功完成"
- SLA(服务等级协议):与客户签订的合同中承诺的服务等级
SLI/SLO设计示例:
# Prometheus Recording Rules - SLO指标计算
groups:
- name: slo_metrics
interval: 30s
rules:
# SLI: 请求成功率(过去5分钟)
- record: slo:request_success_rate:5m
expr: |
sum(rate(http_requests_total{status!~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
# SLI: 请求延迟P99(过去5分钟)
- record: slo:request_latency_p99:5m
expr: |
histogram_quantile(0.99, sum by(le)(rate(http_request_duration_seconds_bucket[5m])))
# SLO: 可用性燃烧率(过去1小时)
# 如果1小时内的错误预算消耗超过2倍,触发告警
- record: slo:burn_rate:1h
expr: |
(1 - slo:request_success_rate:5m) / (1 - 0.999)
# SLO: 30天可用性
- record: slo:availability:30d
expr: |
1 - (
sum(rate(http_requests_total{status=~"5.."}[30d]))
/ sum(rate(http_requests_total[30d]))
)
SLO告警规则:
# 基于SLO的告警 - 多窗口多燃烧率
groups:
- name: slo_alerts
rules:
# 快速告警:5分钟窗口 + 1小时窗口
# 5分钟错误率超过14.4倍预算(快速消耗)
- alert: SLOBurnRateFast
expr: |
(1 - slo:request_success_rate:5m) * 100 > 0.1 * 14.4
and
(1 - slo:request_success_rate:1h) * 100 > 0.1 * 14.4
for: 2m
labels:
severity: critical
annotations:
summary: "SLO错误预算快速消耗"
description: "5分钟和1小时窗口的错误率均超过14.4倍预算"
# 慢速告警:6小时窗口 + 3天窗口
- alert: SLOBurnRateSlow
expr: |
(1 - slo:request_success_rate:6h) * 100 > 0.1 * 3
and
(1 - slo:request_success_rate:3d) * 100 > 0.1 * 3
for: 15m
labels:
severity: warning
annotations:
summary: "SLO错误预算缓慢消耗"
description: "6小时和3天窗口的错误率均超过3倍预算"
10.8 容器监控常见问题排查
问题1:容器CPU使用率显示异常
# 现象:docker stats显示CPU使用率超过100%
# 原因:Docker CPU使用率是相对于单核的百分比
# 如果容器使用了2个CPU,最高可达200%
# 排查:
# 查看容器CPU限制
docker inspect --format='CPU Limit: {{.HostConfig.NanoCpus}}' container_name
# NanoCpus / 1e9 = CPU核心数
# 查看CPU使用详情
cat /sys/fs/cgroup/cpu/docker/<id>/cpu.stat
问题2:容器OOM但内存使用率不高
# 现象:容器被OOM Kill,但监控显示内存使用率不高
# 原因:Docker的内存统计包含/不包含Cache取决于配置
# 排查:
# 查看容器OOM事件
docker events --filter event=oom
# 查看容器的内存限制
docker inspect --format='Memory: {{.HostConfig.Memory}}' container_name
# 查看cgroup内存统计
cat /sys/fs/cgroup/memory/docker/<id>/memory.stat | grep -E "^(rss|cache|swap)"
# rss = 实际物理内存使用
# cache = 文件缓存
# swap = 交换分区使用
问题3:Prometheus采集目标Down
# 排查步骤:
# 1. 检查Target状态
# 访问 http://localhost:9090/targets
# 2. 手动抓取目标指标
curl http://target:9100/metrics
# 3. 检查网络连通性
docker exec prometheus wget -qO- http://target:9100/metrics
# 4. 检查Docker网络
docker network inspect monitoring
# 5. 查看Prometheus日志
docker logs prometheus | grep -i error
问题4:Grafana数据源连接失败
# 排查步骤:
# 1. 在Grafana中测试数据源连接
# 2. 检查Docker网络
docker exec grafana ping prometheus
# 3. 检查URL配置
# 确保使用Docker内部网络名称(如prometheus:9090)
# 而非localhost:9090
# 4. 检查防火墙规则
iptables -L -n
问题5:cAdvisor不显示某些容器
# 排查步骤:
# 1. 确认容器在运行
docker ps | grep container_name
# 2. 检查cAdvisor权限
# cAdvisor需要特权模式或足够的权限来读取cgroup
docker run --privileged ... cadvisor
# 3. 检查cgroup版本
# cAdvisor对cgroup v2的支持需要较新版本
stat /sys/fs/cgroup/cgroup.controllers # 如果存在则为v2
# 4. 检查cAdvisor配置
# 确保没有排除该容器
docker logs cadvisor 2>&1 | grep -i error
10.9 日志管理最佳实践清单
以下是日志管理的最佳实践清单,可用于团队自查:
日志输出规范:
- 应用日志输出到stdout/stderr,不写文件
- 使用JSON结构化日志格式
- 包含时间戳、日志级别、消息等必要字段
- 包含请求ID/追踪ID用于关联日志
- 不记录敏感信息(密码、Token、个人信息)
- 生产环境日志级别设为INFO或以上
- 异常日志包含完整堆栈信息
日志收集规范:
- 配置日志轮转(max-size + max-file)
- 使用集中式日志收集(Loki/ELK)
- 日志采集器(Promtail/Filebeat)高可用部署
- 日志传输使用压缩减少网络开销
- 配置日志缓冲,防止日志丢失
日志存储规范:
- 不同类型日志设置不同保留期
- 配置自动清理策略(ILM/retention)
- 监控日志存储使用量
- 定期备份关键日志
日志查询规范:
- 建立常用日志查询模板
- 为日志建立合理的标签/索引
- 定期审查日志质量(格式一致性)
- 建立日志告警(错误日志突增等)
监控规范:
- 覆盖Golden Signals四个维度
- 使用RED方法监控微服务
- 使用USE方法监控基础设施
- 告警包含足够的上下文信息
- 定期演练告警响应流程
- 建立SLO/SLI体系
- 监控系统自身也在监控范围内
10.10 本章总结与下一期预告
本文总结:
本文全面深入地讲解了Docker监控与日志管理的完整知识体系,涵盖了以下核心内容:
监控体系:
- Docker内置监控:掌握了docker stats、docker events、docker inspect等内置工具的使用
- cAdvisor:学会了部署和配置cAdvisor进行容器资源监控
- Prometheus:深入理解了Prometheus的架构、配置、PromQL查询语言和告警规则
- Grafana:学会了创建可视化Dashboard、配置告警通知和管理用户权限
- 完整监控栈:通过Docker Compose一键部署了cAdvisor + Node Exporter + Prometheus + Grafana + AlertManager的完整监控栈
日志体系:
- Docker日志驱动:理解了Docker日志机制,掌握了各种日志驱动的配置
- ELK/EFK Stack:学会了部署ELK Stack进行日志收集、解析和可视化
- Loki + Promtail:掌握了轻量级日志方案Loki的部署、配置和LogQL查询
- 结构化日志:学会了在各语言中输出JSON结构化日志
最佳实践:
- RED/USE/Golden Signals:掌握了三种主流监控方法论
- SLO/SLI设计:学会了设计服务等级目标和指标
- 告警设计:理解了告警设计原则和反模式
- 问题排查:掌握了容器监控常见问题的排查方法
结语
监控和日志是系统可观测性的两大支柱。没有监控,你不知道系统当前的状态;没有日志,你不知道系统过去发生了什么。本文从Docker内置工具讲起,逐步构建了从cAdvisor到Prometheus + Grafana的完整监控体系,从Docker日志驱动到ELK/EFK和Loki的完整日志方案。
在实际工作中,建议从以下几个方面着手:
- 先有监控,再有优化:不要等出问题才想起需要监控,系统上线第一天就应该有基本监控
- 从简单开始,逐步完善:先用docker stats和基本Grafana看板,再引入Prometheus和AlertManager
- 告警要少而精:宁可少告警,也不要告警疲劳。每个告警都应该有明确的响应动作
- 日志要结构化:从一开始就使用JSON格式日志,为后续的日志分析打下基础
- 定期回顾和优化:监控和日志系统需要持续优化,定期审查告警有效性、存储使用量等
希望本文能帮助你构建完善的Docker可观测性体系,让系统运行状态尽在掌握。
更多推荐
所有评论(0)