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容器ID12位短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
.CPUPercCPU使用率
.MemUsage内存使用量/限制
.MemPerc内存使用率
.NetIO网络I/O
.BlockIO磁盘I/O
.PIDs进程数

2.3 docker top查看容器进程

docker top命令用于查看容器内部运行的进程,类似于在容器内执行topps命令。

# 查看指定容器内的进程
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 socketcAdvisor通过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_totalCPU总使用时间(纳秒)单调递增,需要用rate计算速率
cpu_usage_user用户态CPU时间高值表示应用计算密集
cpu_usage_system内核态CPU时间高值表示系统调用频繁
cpu_usage_per_cpu每核CPU使用时间用于查看CPU亲和性
cpu_cfs_periodsCFS调度周期数基准值
cpu_cfs_throttled_periods被节流的周期数节流率高说明CPU配额不足
cpu_cfs_throttled_seconds被节流的总时间累计的节流时间

内存相关指标:

指标说明关注点
memory_usage总内存使用(含缓存)包含Page Cache
memory_working_set工作集内存OOM判断的依据,最关键
memory_rss常驻内存集实际占用的物理内存
memory_cache页面缓存可回收,不是真正内存压力
memory_swapSwap使用量应该为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_interval1s采集间隔,生产建议10-15s
--storage_duration5m内存数据保留时间
--docker_onlyfalse只监控Docker容器
--disable_metrics禁用指定指标采集
--allow_dynamic_housekeepingtrue动态调整采集频率

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"

生产环境注意事项:

  1. 资源限制:cAdvisor本身需要设置资源限制,防止其消耗过多主机资源
  2. 采集频率:生产环境建议将housekeeping_interval设置为10-15秒,减少开销
  3. 禁用无用指标:使用--disable_metrics禁用不需要的指标,减少数据量
  4. 安全:cAdvisor暴露了容器详细信息,生产环境应限制访问(通过网络策略或反向代理)
  5. 持久化: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定义了四种指标类型,每种类型适用于不同的监控场景:

  1. Counter(计数器):只增不减的指标,如HTTP请求总数、错误总数。Counter的值只能增加(或在重启时归零),不能减少。适合用rate()函数计算变化速率。
# 示例:HTTP请求总数
http_requests_total{method="GET", status="200"} 12345
  1. Gauge(仪表盘):可增可减的指标,如当前内存使用量、CPU使用率、队列长度。Gauge反映的是当前状态的瞬时值。
# 示例:当前内存使用量
container_memory_usage_bytes{container="nginx"} 26738688
  1. 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个
  1. 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_PORTHTTP端口3000
GF_SMTP_ENABLED启用SMTPfalse
GF_AUTH_ANONYMOUS_ENABLED允许匿名访问false

5.2 Grafana数据源配置

Grafana支持通过Web界面或配置文件(provisioning)添加数据源。

方式一:Web界面添加Prometheus数据源

  1. 登录Grafana -> 左侧菜单点击"Connections" -> “Data Sources”
  2. 点击"Add data source" -> 选择"Prometheus"
  3. 填写配置:
    • URL: http://prometheus:9090(同一Docker网络)或 http://host.docker.internal:9090
    • Access: Server(default)
    • Scrape interval: 15s
  4. 点击"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的步骤:

  1. 左侧菜单 -> “Dashboards” -> “New Dashboard”
  2. 点击"Add visualization"
  3. 选择数据源(Prometheus)
  4. 在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,通过下拉菜单切换监控对象。

创建变量:

  1. 进入Dashboard设置 -> “Variables” -> “Add variable”
  2. 配置变量:
# 变量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 AlertingPrometheus 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:

DashboardID说明
Docker Monitoring193基于cAdvisor的Docker监控
Docker and system monitoring179Docker+主机监控
Node Exporter Full1860Node Exporter完整监控
Prometheus Stats3662Prometheus自身监控
Kubernetes cluster monitoring315K8s集群监控
Container Stats11558容器统计

导入Dashboard的步骤:

  1. 左侧菜单 -> “Dashboards” -> “Import”
  2. 输入Dashboard ID(如193)或上传JSON文件
  3. 选择数据源(Prometheus)
  4. 点击"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 监控指标与告警阈值设计

合理的告警阈值是有效监控的关键。阈值过低会导致告警疲劳,过高则可能遗漏故障。

推荐的告警阈值:

指标WarningCritical说明
容器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.  │                 │
│   └──────────┘  └──────────┘  └──────────┘                 │
└──────────────────────────────────────────────────────────────┘

高可用要点:

  1. Prometheus双副本:部署两个独立的Prometheus实例,各自采集完整数据。通过Thanos或VictoriaMetrics进行去重和统一查询
  2. AlertManager集群:部署多个AlertManager实例,通过Gossip协议同步告警状态,避免重复告警
  3. Grafana共享数据库:多个Grafana实例共享MySQL/PostgreSQL数据库,通过负载均衡对外提供服务
  4. 远程存储:使用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│
   │(默认)    │ │        │ │(远程日志)   │
   └──────────┘ └────────┘ └────────────┘

关键理解:

  1. Docker只捕获容器的stdout和stderr输出,不处理应用写到文件中的日志
  2. 日志驱动决定了Docker如何处理这些输出
  3. docker logs命令只能查看使用json-file或journald驱动的容器日志
  4. 每个容器可以独立配置日志驱动和日志选项
# 查看当前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 journalsystemd系统
fluentd推送到Fluentd日志收集转发
fluentd-prefixed带前缀的Fluentd日志收集转发
gelfGraylog Extended Log FormatGraylog日志系统
awslogs推送到AWS CloudWatchAWS云环境
gcplogs推送到GCP LoggingGCP云环境
splunk推送到SplunkSplunk日志系统
etwlogsWindows事件跟踪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 日志级别管理

合理的日志级别管理是日志体系的重要部分。不同的日志级别对应不同的严重程度和处理优先级:

级别数值说明示例生产环境
TRACE0最详细的跟踪信息方法入参出参关闭
DEBUG1调试信息SQL语句、缓存命中关闭
INFO2正常运行信息用户登录、请求完成开启
WARN3警告信息重试成功、降级开启
ERROR4错误信息请求失败、异常开启
FATAL5致命错误服务无法启动开启

日志级别配置示例:

# 通过环境变量配置日志级别(推荐)
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}URLhttp://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核心功能:

  1. Discover:日志搜索与浏览,支持KQL(Kibana Query Language)查询
  2. Visualize:创建图表(柱状图、饼图、折线图等)
  3. Dashboard:组合多个可视化面板
  4. 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对比

特性LokiELK (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. 标签应该用于区分日志来源,而非日志内容

推荐的标签方案:

标签示例值说明
jobnginx, redis, app日志来源类型
container_namenginx-web-01容器名
environmentprod, staging, dev环境
serviceuser-service, order-service微服务名
levelinfo, warn, error日志级别(低基数)
hostdocker-host-1主机名
datacenterdc1, 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  │      │                          │
│    │  └────────┘  └────────┘      │                          │
│    └──────────────────────────────┘                          │
└──────────────────────────────────────────────────────────────┘

生产环境优化要点:

  1. 使用对象存储:生产环境必须使用S3/Minio/GCS作为存储后端,不要用本地文件系统
  2. 读写分离:部署独立的Distributor/Ingester(写入)和Querier(读取)
  3. 缓存配置:启用查询缓存和索引缓存,提高查询性能
  4. 资源限制:配置合理的摄入和查询限制,防止过载
  5. 压缩器:启用压缩器,定期合并和压缩旧的日志块
  6. 监控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建议:

每个服务应该至少有以下几个面板:

  1. 请求速率(Rate) - 时间序列图,按API路径分组
  2. 错误率(Errors) - 时间序列图,标记5xx错误
  3. 请求延迟(Duration) - 热力图或时间序列图,展示P50/P95/P99
  4. 请求量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方法在各资源中的应用:

资源UtilizationSaturationErrors
CPU1 - rate(node_cpu_seconds_total{mode="idle"}[5m])node_load1 / core_count-
内存1 - (MemAvailable / MemTotal)OOM事件OOM Kill次数
磁盘disk_used / disk_totalrate(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的DurationP99 = 200ms
Traffic(流量)请求量RED的Rate1000 req/s
Errors(错误)错误请求RED的Errors0.5% error rate
Saturation(饱和度)资源饱和程度USE的SaturationCPU 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 告警设计原则与反模式

告警设计六大原则:

  1. 每个告警都应该可执行:收到告警后,运维人员应该知道该做什么。如果不知道如何处理,这个告警就是噪音。

  2. 避免告警风暴:一个故障可能触发大量告警,需要通过告警分组和抑制规则来减少噪音。

  3. 区分严重级别:使用severity标签区分Critical(需要立即处理)和Warning(可以延后处理)。

  4. 告警应该描述症状而非原因:"订单服务错误率5%"比"数据库连接数100"更有意义。

  5. 设置合理的持续时间(for):瞬时抖动不应该触发告警,持续异常才需要告警。

  6. 定期审查告警:定期回顾告警历史,移除无效告警,调整阈值。

告警反模式:

反模式问题正确做法
告警过多告警疲劳,忽视重要告警只对需要人工干预的情况告警
阈值固定不同时段的基线不同使用动态阈值或异常检测
无分组告警风暴淹没关键信息使用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监控与日志管理的完整知识体系,涵盖了以下核心内容:

监控体系:

  1. Docker内置监控:掌握了docker stats、docker events、docker inspect等内置工具的使用
  2. cAdvisor:学会了部署和配置cAdvisor进行容器资源监控
  3. Prometheus:深入理解了Prometheus的架构、配置、PromQL查询语言和告警规则
  4. Grafana:学会了创建可视化Dashboard、配置告警通知和管理用户权限
  5. 完整监控栈:通过Docker Compose一键部署了cAdvisor + Node Exporter + Prometheus + Grafana + AlertManager的完整监控栈

日志体系:

  1. Docker日志驱动:理解了Docker日志机制,掌握了各种日志驱动的配置
  2. ELK/EFK Stack:学会了部署ELK Stack进行日志收集、解析和可视化
  3. Loki + Promtail:掌握了轻量级日志方案Loki的部署、配置和LogQL查询
  4. 结构化日志:学会了在各语言中输出JSON结构化日志

最佳实践:

  1. RED/USE/Golden Signals:掌握了三种主流监控方法论
  2. SLO/SLI设计:学会了设计服务等级目标和指标
  3. 告警设计:理解了告警设计原则和反模式
  4. 问题排查:掌握了容器监控常见问题的排查方法

结语

监控和日志是系统可观测性的两大支柱。没有监控,你不知道系统当前的状态;没有日志,你不知道系统过去发生了什么。本文从Docker内置工具讲起,逐步构建了从cAdvisor到Prometheus + Grafana的完整监控体系,从Docker日志驱动到ELK/EFK和Loki的完整日志方案。

在实际工作中,建议从以下几个方面着手:

  1. 先有监控,再有优化:不要等出问题才想起需要监控,系统上线第一天就应该有基本监控
  2. 从简单开始,逐步完善:先用docker stats和基本Grafana看板,再引入Prometheus和AlertManager
  3. 告警要少而精:宁可少告警,也不要告警疲劳。每个告警都应该有明确的响应动作
  4. 日志要结构化:从一开始就使用JSON格式日志,为后续的日志分析打下基础
  5. 定期回顾和优化:监控和日志系统需要持续优化,定期审查告警有效性、存储使用量等

希望本文能帮助你构建完善的Docker可观测性体系,让系统运行状态尽在掌握。

更多推荐