Nginx 接口耗时 Prometheus + Grafana 监控实施方案

Nginx 接口耗时 Prometheus + Grafana 监控实施方案

汇报人:周晓玥


一、方案概述

1.1 监控目标

将 Nginx 代理的批量任务接口耗时数据($request_time$upstream_response_time、HTTP 状态码)接入 Prometheus + Grafana 监控体系,实现:

  • 各中心接口的请求耗时(P50/P95/P99)实时监控
  • 后端 upstream 响应耗时与 Nginx 代理延迟的对比
  • 异常接口自动告警(耗时突增、成功率下降)
  • 历史趋势分析(按小时/天/周维度聚合)

1.2 核心数据流

#mermaid-svg-zpix4lJW9g3rx1yU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zpix4lJW9g3rx1yU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zpix4lJW9g3rx1yU .error-icon{fill:#552222;}#mermaid-svg-zpix4lJW9g3rx1yU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zpix4lJW9g3rx1yU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .marker.cross{stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zpix4lJW9g3rx1yU p{margin:0;}#mermaid-svg-zpix4lJW9g3rx1yU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label text{fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label span{color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label span p{background-color:transparent;}#mermaid-svg-zpix4lJW9g3rx1yU .label text,#mermaid-svg-zpix4lJW9g3rx1yU span{fill:#333;color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .node rect,#mermaid-svg-zpix4lJW9g3rx1yU .node circle,#mermaid-svg-zpix4lJW9g3rx1yU .node ellipse,#mermaid-svg-zpix4lJW9g3rx1yU .node polygon,#mermaid-svg-zpix4lJW9g3rx1yU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .rough-node .label text,#mermaid-svg-zpix4lJW9g3rx1yU .node .label text,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label,#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label{text-anchor:middle;}#mermaid-svg-zpix4lJW9g3rx1yU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .rough-node .label,#mermaid-svg-zpix4lJW9g3rx1yU .node .label,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label,#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label{text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .node.clickable{cursor:pointer;}#mermaid-svg-zpix4lJW9g3rx1yU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .arrowheadPath{fill:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zpix4lJW9g3rx1yU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zpix4lJW9g3rx1yU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster text{fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster span{color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zpix4lJW9g3rx1yU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU rect.text{fill:none;stroke-width:0;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape p,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label rect,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zpix4lJW9g3rx1yU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zpix4lJW9g3rx1yU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 告警通知
Grafana 节点
Prometheus 节点
Nginx 节点 (2台)
每15s scrape
每15s scrape
规则评估
数据源
仪表盘
通知
通知
Nginx Master

9530端口
nginx-module-vts

指标暴露 :8080/metrics
Nginx Slave

9530端口
nginx-module-vts

指标暴露 :8080/metrics
Prometheus Server

指标采集 + 存储
Alertmanager

告警管理
Grafana Server

可视化 + 告警
企业微信
邮件

1.3 指标采集链路

复制代码
Nginx请求 → nginx-module-vts 统计 → /metrics端点(JSON) 
  → Prometheus scrape → TSDB存储 
  → Grafana查询 → 仪表盘 + 告警

二、服务器规划

2.1 节点清单

角色 主机名 IP地址 配置 操作系统
Nginx Master ngx-batch-01 10.1.92.61 4C8G / 100G SSD CentOS 7.9
Nginx Slave ngx-batch-02 10.1.92.62 4C8G / 100G SSD CentOS 7.9
Prometheus mon-prom-01 10.1.92.100 4C16G / 200G SSD CentOS 7.9
Grafana mon-grafana-01 10.1.92.101 4C8G / 100G SSD CentOS 7.9

注:若资源有限,Prometheus 和 Grafana 可合并部署在同一节点。

2.2 端口规划

服务 端口 说明 防火墙策略
Nginx 业务端口 9530 批量任务流量入口 允许 Control-M Agent 网段
nginx-module-vts metrics 8080 Prometheus 指标暴露 允许 Prometheus 节点 IP
Prometheus Web UI 9090 Prometheus 管理界面 允许运维网段
Grafana Web UI 3000 Grafana 管理界面 允许运维网段
Alertmanager 9093 告警管理 允许运维网段

2.3 存储规划

项目 配置 说明
Prometheus TSDB 存储路径 /data/prometheus 建议至少 200G,SSD
Prometheus 数据保留 30d 生产环境建议 30-90 天
Grafana 数据路径 /var/lib/grafana 不建议存放大文件
Nginx 日志路径 /var/log/nginx 建议配置 logrotate 定期切割

三、完整实施步骤

阶段一:Nginx 节点改造(ngx-batch-01 / ngx-batch-02)

Step 1:安装 nginx-module-vts 模块

知识点nginx-module-vts(Virtual Host Traffic Status)是一个第三方 Nginx 模块,可实时统计 Nginx 的请求量、耗时、状态码等指标,并通过 HTTP 接口暴露 Prometheus 格式的 metrics。

方案 A:重新编译 Nginx(推荐)

bash 复制代码
# 1. 下载 nginx-module-vts 模块
cd /usr/local/src
git clone https://github.com/vozlt/nginx-module-vts.git

# 2. 查看当前 Nginx 版本和编译参数
nginx -V
# 记录完整编译参数,例如:
# --prefix=/usr/local/nginx --add-module=../nginx-upsync-module ...

# 3. 重新编译 Nginx(追加 vts 模块)
cd /usr/local/src/nginx-1.24.0
./configure \
    --prefix=/usr/local/nginx \
    --add-module=../nginx-upsync-module \
    --add-module=../nginx-module-vts \    # 新增
    --with-http_realip_module \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-stream
make

# ⚠️ 注意:只执行 make,不要 make install(避免覆盖现有配置)
# 4. 备份原 Nginx 二进制
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak.$(date +%Y%m%d)

# 5. 替换二进制(平滑升级)
cp objs/nginx /usr/local/nginx/sbin/nginx

# 6. 平滑升级(不中断服务)
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)
sleep 3
kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)
sleep 3

# 7. 验证模块是否加载成功
nginx -V 2>&1 | grep nginx-module-vts
# 预期输出: --add-module=../nginx-module-vts

方案 B:动态模块加载(Nginx 1.11.5+)

bash 复制代码
# 编译为动态模块
./configure --add-dynamic-module=../nginx-module-vts
make modules

# 复制到模块目录
cp objs/ngx_http_vhost_traffic_status_module.so /usr/local/nginx/modules/

# 在 nginx.conf 顶部加载
# load_module modules/ngx_http_vhost_traffic_status_module.so;
Step 2:配置 nginx.conf

在现有 Nginx 配置中追加 vts 监控配置:

nginx 复制代码
# =============================================
# nginx.conf 主配置文件
# =============================================

# 若使用动态模块
# load_module modules/ngx_http_vhost_traffic_status_module.so;

http {
    # =============================================
    # VTS 模块全局配置
    # =============================================
    
    # 开启 vts 统计功能
    vhost_traffic_status_zone;
    
    # 按过滤维度分组统计(关键:按接口路径分组)
    vhost_traffic_status_filter_by_host on;
    vhost_traffic_status_filter_by_set_key $location_name location::$1;
    
    # =============================================
    # 原有 upstream 和 location 配置保持不变
    # =============================================
    
    upstream batch_center {
        upsync 10.1.230.33:8848/nacos/v1/ns/instance/list?serviceName=corpor-batch ...;
        # ... 原有配置 ...
    }
    
    # 为每个 location 指定过滤标签
    server {
        listen 9530;
        
        # 设置 location 标签用于 vts 分组
        set $location_name "default";
        
        location /corpor-batch/ {
            set $location_name "corpor-batch";
            vhost_traffic_status_filter_by_set_key $location_name location::$1;
            
            proxy_pass http://batch_center;
            proxy_read_timeout 1800s;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
        
        location /corpor-trans/ {
            set $location_name "corpor-trans";
            vhost_traffic_status_filter_by_set_key $location_name location::$1;
            
            proxy_pass http://trans_center;
            proxy_read_timeout 1800s;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
        
        # ... 其他 location 类似 ...
        
        # =============================================
        # VTS 监控接口(仅限内网访问)
        # =============================================
        location /vts_status {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format html;
            allow 10.1.0.0/16;
            allow 127.0.0.1;
            deny all;
        }
        
        # Prometheus metrics 格式接口
        location /metrics {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format prometheus;
            allow 10.1.92.100;   # Prometheus 节点 IP
            allow 10.1.92.101;   # Grafana 节点 IP
            allow 127.0.0.1;
            deny all;
        }
        
        # 为 Prometheus 提供一个独立端口(可选,简化网络策略)
        # 在另一个 server 块中监听独立端口
    }
}

# =============================================
# 独立 metrics 端口(推荐:业务与监控端口分离)
# =============================================
server {
    listen 8080;
    server_name localhost;
    
    # 监控端口只暴露 metrics
    location /metrics {
        vhost_traffic_status_display;
        vhost_traffic_status_display_format prometheus;
        allow 10.1.92.100;
        allow 10.1.92.101;
        allow 127.0.0.1;
        deny all;
    }
    
    location /vts_status {
        vhost_traffic_status_display;
        vhost_traffic_status_display_format html;
        allow 10.1.0.0/16;
        allow 127.0.0.1;
        deny all;
    }
}
Step 3:验证 Nginx 指标暴露
bash 复制代码
# 重启 Nginx(或 reload)
nginx -s reload

# 验证模块统计启用
curl http://127.0.0.1:8080/vts_status
# 应返回 HTML 格式的流量统计页面

# 验证 Prometheus metrics 格式
curl http://127.0.0.1:8080/metrics
# 应返回类似以下内容:

Prometheus metrics 关键指标说明

复制代码
# 请求总数(按状态码和 location 分组)
nginx_vts_server_requests_total{host="*",filter="location::corpor-batch"} 15234

# 请求速率
nginx_vts_server_request_seconds_total{...} 1256.789

# upstream 连接数
nginx_vts_upstream_request_seconds_total{...}

# 各状态码请求数(2xx / 3xx / 4xx / 5xx)
nginx_vts_server_requests_total{host="*",code="2xx",filter="location::corpor-batch"} 15100
nginx_vts_server_requests_total{host="*",code="5xx",filter="location::corpor-batch"} 34

# 平均响应时间(需计算)
# rate(nginx_vts_server_request_seconds_total[1m]) / rate(nginx_vts_server_requests_total[1m])

注意点 :nginx-module-vts 暴露的是请求总耗时请求总数 ,需要 PromQL 做除法计算平均耗时 。无法直接暴露 P95/P99 分位值,分位值需要在 Prometheus 中通过 histogram_quantile 计算。


阶段二:Prometheus 部署(mon-prom-01)

Step 1:安装 Prometheus
bash 复制代码
# 1. 创建用户
useradd --no-create-home --shell /bin/false prometheus

# 2. 下载 Prometheus(选择 LTS 稳定版)
cd /usr/local/src
wget https://github.com/prometheus/prometheus/releases/download/v2.52.0/prometheus-2.52.0.linux-amd64.tar.gz
tar -zxvf prometheus-2.52.0.linux-amd64.tar.gz

# 3. 安装
mkdir -p /etc/prometheus /data/prometheus
cp prometheus-2.52.0.linux-amd64/prometheus /usr/local/bin/
cp prometheus-2.52.0.linux-amd64/promtool /usr/local/bin/
cp -r prometheus-2.52.0.linux-amd64/consoles /etc/prometheus/
cp -r prometheus-2.52.0.linux-amd64/console_libraries /etc/prometheus/

chown -R prometheus:prometheus /etc/prometheus /data/prometheus
chmod +x /usr/local/bin/prometheus /usr/local/bin/promtool
Step 2:配置 prometheus.yml
yaml 复制代码
# /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s          # 采集间隔
  scrape_timeout: 10s           # 采集超时
  evaluation_interval: 15s      # 告警规则评估间隔
  external_labels:
    env: production
    project: corporate-batch

# =============================================
# 告警规则文件
# =============================================
rule_files:
  - /etc/prometheus/rules/nginx_batch_alerts.yml

# =============================================
# 采集目标
# =============================================
scrape_configs:
  # Nginx Master 节点
  - job_name: 'nginx-batch-master'
    scrape_interval: 15s
    metrics_path: '/metrics'
    static_configs:
      - targets: ['10.1.92.61:8080']
        labels:
          instance: 'nginx-master'
          cluster: 'batch-cluster'
    
  # Nginx Slave 节点
  - job_name: 'nginx-batch-slave'
    scrape_interval: 15s
    metrics_path: '/metrics'
    static_configs:
      - targets: ['10.1.92.62:8080']
        labels:
          instance: 'nginx-slave'
          cluster: 'batch-cluster'
    
  # Prometheus 自身监控
  - job_name: 'prometheus'
    scrape_interval: 30s
    static_configs:
      - targets: ['localhost:9090']
Step 3:配置告警规则
yaml 复制代码
# /etc/prometheus/rules/nginx_batch_alerts.yml
groups:
  - name: nginx_batch_alerts
    interval: 30s
    rules:
      # =============================================
      # 1. 接口平均耗时过高告警
      # =============================================
      - alert: NginxUpstreamAvgLatencyHigh
        expr: |
          (
            rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
            /
            rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
          ) > 600
        for: 5m
        labels:
          severity: warning
          component: nginx-batch
        annotations:
          summary: "Nginx upstream 平均耗时过高"
          description: "Location {{ $labels.filter }} 最近5分钟 upstream 平均耗时 {{ $value | humanizeDuration }},可能影响批量任务执行"
      
      # =============================================
      # 2. 5xx 错误率告警
      # =============================================
      - alert: Nginx5xxErrorRateHigh
        expr: |
          (
            sum(rate(nginx_vts_server_requests_total{code="5xx",filter=~"location::.*"}[5m])) by (filter)
            /
            sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
          ) > 0.01
        for: 3m
        labels:
          severity: critical
          component: nginx-batch
        annotations:
          summary: "Nginx 5xx 错误率超过 1%"
          description: "Location {{ $labels.filter }} 5xx 错误率 {{ $value | humanizePercentage }},可能存在后端服务故障"
      
      # =============================================
      # 3. 上游后端实例不可用告警
      # =============================================
      - alert: NginxUpstreamDown
        expr: |
          nginx_vts_upstream_server_up{backend=~".*"} == 0
        for: 2m
        labels:
          severity: critical
          component: nginx-batch
        annotations:
          summary: "Nginx upstream 后端实例不可用"
          description: "后端 {{ $labels.backend }} 健康检查失败超过 2 分钟"
      
      # =============================================
      # 4. 请求量异常下降告警
      # =============================================
      - alert: NginxRequestRateDrop
        expr: |
          (
            rate(nginx_vts_server_requests_total{filter=~"location::.*"}[10m])
            /
            rate(nginx_vts_server_requests_total{filter=~"location::.*"}[10m] offset 1h)
          ) < 0.1
        for: 10m
        labels:
          severity: warning
          component: nginx-batch
        annotations:
          summary: "Nginx 请求量异常下降"
          description: "Location {{ $labels.filter }} 请求量较 1 小时前下降超过 90%"
      
      # =============================================
      # 5. Prometheus 采集目标不可达告警
      # =============================================
      - alert: PrometheusTargetDown
        expr: up{job=~"nginx-batch.*"} == 0
        for: 2m
        labels:
          severity: critical
          component: prometheus
        annotations:
          summary: "Prometheus 采集目标不可达"
          description: "采集目标 {{ $labels.instance }} 不可达超过 2 分钟"
Step 4:配置 Alertmanager
yaml 复制代码
# /etc/prometheus/alertmanager.yml
global:
  resolve_timeout: 5m

# 企业微信告警
route:
  group_by: ['alertname', 'component']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'wechat'
  routes:
    - match:
        severity: critical
      receiver: 'wechat-critical'
      repeat_interval: 30m

receivers:
  - name: 'wechat'
    wechat_configs:
      - corp_id: 'your_corp_id'
        to_party: '运维组'
        agent_id: '1000002'
        api_secret: 'your_secret'
        send_resolved: true
  
  - name: 'wechat-critical'
    wechat_configs:
      - corp_id: 'your_corp_id'
        to_user: '@all'
        agent_id: '1000002'
        api_secret: 'your_secret'
        send_resolved: true

  - name: 'email'
    email_configs:
      - to: 'ops-team@corp.com'
        from: 'alertmanager@corp.com'
        smarthost: 'smtp.corp.com:25'
        send_resolved: true
Step 5:启动 Prometheus
bash 复制代码
# 1. 创建 systemd 服务
cat > /etc/systemd/system/prometheus.service << 'EOF'
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
    --config.file=/etc/prometheus/prometheus.yml \
    --storage.tsdb.path=/data/prometheus \
    --storage.tsdb.retention.time=30d \
    --storage.tsdb.retention.size=150GB \
    --web.listen-address=0.0.0.0:9090 \
    --web.enable-lifecycle \
    --web.enable-admin-api \
    --storage.tsdb.wal-compression
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10s
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

# 2. 启动
systemctl daemon-reload
systemctl enable prometheus
systemctl start prometheus
systemctl status prometheus

# 3. 验证
curl http://127.0.0.1:9090/api/v1/targets | python -m json.tool | grep "health"
# 预期看到所有 target 状态为 "up"
Step 6:启动 Alertmanager
bash 复制代码
# 创建 systemd 服务
cat > /etc/systemd/system/alertmanager.service << 'EOF'
[Unit]
Description=Alertmanager
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/alertmanager \
    --config.file=/etc/prometheus/alertmanager.yml \
    --storage.path=/data/alertmanager \
    --web.listen-address=0.0.0.0:9093
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target
EOF

mkdir -p /data/alertmanager
chown -R prometheus:prometheus /data/alertmanager

systemctl daemon-reload
systemctl enable alertmanager
systemctl start alertmanager

阶段三:Grafana 部署(mon-grafana-01)

Step 1:安装 Grafana
bash 复制代码
# 1. 添加 Grafana 官方 YUM 源
cat > /etc/yum.repos.d/grafana.repo << 'EOF'
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
EOF

# 2. 安装
yum install -y grafana

# 3. 启动
systemctl daemon-reload
systemctl enable grafana-server
systemctl start grafana-server
systemctl status grafana-server

# 4. 开放防火墙
firewall-cmd --permanent --add-port=3000/tcp
firewall-cmd --reload
Step 2:配置数据源
bash 复制代码
# 1. 登录 Grafana(默认账户 admin/admin,首次登录会要求修改密码)
# http://10.1.92.101:3000

# 2. 添加 Prometheus 数据源
# Configuration → Data Sources → Add data source → Prometheus
# URL: http://10.1.92.100:9090
# Scrape interval: 15s
# 点击 Save & Test

也可通过 API 配置:

bash 复制代码
curl -X POST http://admin:admin@10.1.92.101:3000/api/datasources \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Prometheus",
    "type": "prometheus",
    "url": "http://10.1.92.100:9090",
    "access": "proxy",
    "isDefault": true
  }'
Step 3:仪表盘设计

在 Grafana 中创建以下仪表盘(或者导入预置 JSON):

仪表盘一:Nginx 批量任务总览

面板 类型 PromQL 描述
全局 QPS Stat sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) 所有批量任务每秒请求数
5xx 错误率 Stat (sum(rate(nginx_vts_server_requests_total{code="5xx"}[5m])) / sum(rate(nginx_vts_server_requests_total[5m]))) * 100 5xx 错误百分比
请求量时序 Graph sum(rate(nginx_vts_server_requests_total{filter="$location"}[5m])) by (code) 按状态码的请求量趋势
各中心平均耗时 Table 见下方说明 各 Location 平均 upstream 耗时

各中心平均耗时 PromQL

promql 复制代码
# 按 Location 分组的平均 upstream 响应时间(秒)
(
  rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
  /
  rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
)

# 转换为毫秒
(
  rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
  /
  rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
) * 1000

仪表盘二:单中心任务耗时详情

使用 Grafana 变量 $location 动态切换:

promql 复制代码
# 变量定义
label_values(nginx_vts_server_requests_total{filter=~"location::.*"}, filter)

# 上游耗时速率(请求秒数/秒)
rate(nginx_vts_upstream_request_seconds_total{filter="$location"}[1m])

# 请求速率
rate(nginx_vts_upstream_requests_total{filter="$location"}[1m])

# 上游平均响应时间(毫秒)
(
  rate(nginx_vts_upstream_request_seconds_total{filter="$location"}[5m])
  / 
  rate(nginx_vts_upstream_requests_total{filter="$location"}[5m])
) * 1000

仪表盘三:Nginx 节点健康状态

面板 PromQL 描述
Nginx 节点存活 up{job=~"nginx-batch.*"} 1=存活 0=不可达
上游后端存活 nginx_vts_upstream_server_up 各后端实例健康状态
上游后端权重 nginx_vts_upstream_server_weight 当前权重
当前连接数 nginx_vts_main_connections{filter="active"} 活跃连接数
连接数变化 rate(nginx_vts_main_connections{filter="accepted"}[1m]) 新建连接速率
Step 4:Grafana 告警配置

在 Grafana 中也可以设置告警(比 Prometheus Alertmanager 更适合业务人员):

yaml 复制代码
# Grafana 告警渠道配置
# Alerting → Notification channels → Add channel

# 企业微信
Type: WeCom (企业微信)
Name: WeChat-Ops
Webhook URL: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx

# 邮件
Type: Email
Name: Email-Ops
Addresses: ops-team@corp.com

四、架构总览

#mermaid-svg-0DkGP3dT7gcaKQv7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0DkGP3dT7gcaKQv7 .error-icon{fill:#552222;}#mermaid-svg-0DkGP3dT7gcaKQv7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0DkGP3dT7gcaKQv7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .marker.cross{stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0DkGP3dT7gcaKQv7 p{margin:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label text{fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label span{color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label span p{background-color:transparent;}#mermaid-svg-0DkGP3dT7gcaKQv7 .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 span{fill:#333;color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node rect,#mermaid-svg-0DkGP3dT7gcaKQv7 .node circle,#mermaid-svg-0DkGP3dT7gcaKQv7 .node ellipse,#mermaid-svg-0DkGP3dT7gcaKQv7 .node polygon,#mermaid-svg-0DkGP3dT7gcaKQv7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .rough-node .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .rough-node .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label{text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node.clickable{cursor:pointer;}#mermaid-svg-0DkGP3dT7gcaKQv7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .arrowheadPath{fill:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster text{fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster span{color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0DkGP3dT7gcaKQv7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape p,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label rect,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0DkGP3dT7gcaKQv7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 被监控业务
通知层
可视化层
指标存储与告警层
数据采集层
负载均衡
负载均衡
负载均衡
scrape /metrics
scrape /metrics
规则评估
数据源
告警通知
告警通知
仪表盘告警
仪表盘告警
Nginx Master

10.1.92.61:9530
Nginx Slave

10.1.92.62:9530
nginx-module-vts

:8080/metrics
nginx-module-vts

:8080/metrics
Prometheus Server

10.1.92.100:9090

TSDB 存储 30d
Alertmanager

:9093

告警分组/去重/路由
Grafana

10.1.92.101:3000

仪表盘 + 告警
企业微信
邮件
批量中心

corpor-batch 64条任务
交易中心

corpor-trans 23条
其他中心

14条


五、关键 PromQL 查询清单

5.1 接口耗时类

promql 复制代码
# 1. 各中心上游平均耗时(毫秒)
(
  rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
  /
  rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
) * 1000

# 2. 指定中心最近 1 小时平均耗时趋势(毫秒)
(
  rate(nginx_vts_upstream_request_seconds_total{filter="location::corpor-batch"}[5m])
  /
  rate(nginx_vts_upstream_requests_total{filter="location::corpor-batch"}[5m])
) * 1000

# 3. Nginx 端到端耗时 vs 上游耗时对比
# 端到端: (rate(nginx_vts_server_request_seconds_total[5m]) / rate(nginx_vts_server_requests_total[5m])) * 1000
# 上游:   (rate(nginx_vts_upstream_request_seconds_total[5m]) / rate(nginx_vts_upstream_requests_total[5m])) * 1000
# 代理延迟: 端到端 - 上游

5.2 请求量类

promql 复制代码
# 4. 各中心 QPS
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) by (filter)

# 5. 按状态码的 QPS
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) by (code)

# 6. 总请求量(过去 1 小时)
sum(increase(nginx_vts_server_requests_total{filter=~"location::.*"}[1h]))

5.3 错误率类

promql 复制代码
# 7. 各中心 5xx 错误率(百分比)
(
  sum(rate(nginx_vts_server_requests_total{code="5xx",filter=~"location::.*"}[5m])) by (filter)
  /
  sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
) * 100

# 8. 各中心 4xx 错误率(百分比)
(
  sum(rate(nginx_vts_server_requests_total{code="4xx",filter=~"location::.*"}[5m])) by (filter)
  /
  sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
) * 100

5.4 健康检查类

promql 复制代码
# 9. 采集目标存活状态
up{job=~"nginx-batch.*"}

# 10. 上游后端存活状态(按后端 server 分组)
nginx_vts_upstream_server_up

# 11. 上游后端当前权重
nginx_vts_upstream_server_weight

六、知识点与注意事项

6.1 nginx-module-vts 工作原理

概念 说明
统计时机 每次请求结束时,vts 模块通过 Nginx 的 log_subrequest 阶段钩子收集指标
内存开销 所有统计(计数器/累加器)存储在共享内存中,各 Worker 进程共享,内存占用约 2MB~10MB
对象分级 server(server 级别)→ filter(过滤标签级别)→ upstream(上游后端级别)→ upstream server(单后端实例级别)
指标类型 仅暴露 Counter(计数器),无直接 Histogram/Summary,分位值需通过 PromQL 间接计算
性能影响 < 1% CPU 开销,基本可忽略

6.2 vts 模块的局限性及应对

局限性 表现 应对方案
无原生分位值 只能计算平均值,无法直接获得 P95/P99 启用 Nginx $request_time 日志 + Loki/mtail 补充分位值计算
Counter 重置 Nginx reload 后计数器归零 Prometheus rate() 函数自动处理 Counter 重置
长时任务统计 超长连接(>30min)的耗时计入该时间窗口 通过 Grafana 变量选择合适时间窗口
无法按接口粒度过细 vts filter 维度过多会增加内存开销 按 Location 中心粒度统计即可,不做过细的 filter

6.3 编译模块注意事项

  1. 版本匹配:nginx-module-vts 必须与 Nginx 版本匹配,使用前查看模块的 README 确认兼容版本
  2. 编译依赖 :需要 gccmakepcre-developenssl-develzlib-devel
  3. 平滑升级 :使用 kill -USR2 信号实现二进制热替换,避免中断服务
  4. 保留旧 bin :升级前务必备份 nginx 二进制和配置
  5. 与 upsync 模块共存:nginx-module-vts 与 nginx-upsync-module 无冲突,可以同时编译

6.4 Prometheus 配置注意事项

  1. scrape_interval 不宜过短:15s 是推荐值,过短会增加 Nginx 和 Prometheus 压力
  2. 保留时间:30d 保留时间,建议存储空间 ≥ 150GB(取决于指标量)
  3. WAL 压缩--storage.tsdb.wal-compression 减少磁盘 I/O
  4. 生命周期管理--web.enable-lifecycle 允许 API 热重载配置(curl -X POST http://localhost:9090/-/reload
  5. 远程写入:生产环境建议配置 remote_write 到长期存储(如 VictoriaMetrics、Thanos)

6.5 Grafana 配置注意事项

  1. 数据源缓存 :建议设置 Min time interval 为采集间隔(15s),避免查询粒度小于实际数据精度
  2. 模板变量 :使用 $location 变量实现仪表盘复用,无需为每个中心单独创建面板
  3. 查询超时 :复杂 PromQL 查询可能超时,建议在面板中设置合理的 Max data points
  4. 版本管理:将仪表盘 JSON 导出到 Git 仓库,实现配置版本管理

七、可能存在的问题及解决方案

7.1 编译问题

问题 现象 解决方案
模块不兼容 nginx: [emerg] unknown directive "vhost_traffic_status_zone" 确认模块已正确编译进 Nginx;检查 nginx -V 输出
编译错误 make 报错缺少依赖 yum install -y gcc make pcre-devel openssl-devel zlib-devel
多模块冲突 与已有第三方模块冲突 调整 ./configure 中模块加载顺序,先加载 vts 再加载 upsync
Nginx 版本不匹配 版本不一致导致编译失败 nginx -v 查看当前版本,下载对应版本的 Nginx 源码

7.2 运行时问题

问题 现象 解决方案
metrics 接口 403 curl /metrics 返回 403 检查 allow/deny 规则,确认 Prometheus 节点 IP 在白名单
vts 无数据 /vts_status 页面显示空白或 0 确认 vhost_traffic_status_zone; 在 http 块中;检查是否有实际请求经过
内存泄漏 Nginx 内存持续增长 vts 共享内存大小默认足够,若大量 filter 标签可限制 vhost_traffic_status_filter_max_node
reload 丢指标 Nginx reload 后 Counter 归零 Prometheus rate() 函数自动处理 reset,3 个采集周期后恢复正常

7.3 Prometheus 问题

问题 现象 解决方案
target down Prometheus 显示 target 状态为 DOWN 检查网络连通性、防火墙规则、Nginx metrics 端口是否监听
磁盘空间不足 Prometheus OOM 或 crash 调整 --storage.tsdb.retention.size;增加磁盘空间;清理旧 WAL
大量时间序列 查询变慢,内存飙升 减少 filter 标签维度;使用 relabel_configs 丢弃不必要的标签
告警不生效 规则配了但不触发 检查 evaluation_interval;用 Prometheus UI 的 Alerts 页面验证规则表达式

7.4 Grafana 问题

问题 现象 解决方案
数据为空 面板显示 "No data" 检查 Prometheus 数据源连接;验证 PromQL 语法;确认时间范围有数据
查询超时 面板加载很慢或超时 减少查询时间范围或降低 Step 精度;拆分复杂面板
分位值不准 计算的耗时显示为 0 或异常 确认 Counter 累加正常;使用 increase() 代替 rate() 对比验证
Grafana 仪表盘不自动刷新 数据显示陈旧 检查面板右上角自动刷新设置;确认浏览器标签页保持活跃

7.5 高可用问题

问题 现象 解决方案
Prometheus 单点故障 Prometheus 宕机,监控中断 部署 2 台 Prometheus,配合 Thanos/Cortex 实现高可用
Grafana 单点故障 Grafana 宕机,仪表盘不可见 使用数据库(MySQL/PostgreSQL)后端 + 多实例部署
Nginx 节点故障 某节点 metrics 不可达 Prometheus 可以区分不同节点指标,不影响其他节点监控

八、验证清单

8.1 部署后验证

bash 复制代码
# 1. 验证 Nginx vts 模块
curl http://10.1.92.61:8080/metrics | head -20
# 应输出 Prometheus 格式的指标,包含 nginx_vts_ 开头

# 2. 验证 Prometheus 采集
curl http://10.1.92.100:9090/api/v1/targets 2>/dev/null | python -m json.tool | grep -E '"job"|"health"'
# 所有 target 的 health 应为 "up"

# 3. 验证指标可查询
curl -s 'http://10.1.92.100:9090/api/v1/query?query=nginx_vts_server_requests_total' | python -m json.tool | head -30

# 4. 验证 Grafana 数据源
curl http://admin:admin@10.1.92.101:3000/api/datasources/1 | python -m json.tool

# 5. 模拟批量请求验证耗时指标
for i in {1..100}; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    --connect-timeout 30 --max-time 7200 \
    "http://10.1.92.60:9530/corpor-batch/xxx" &
done
wait

# 等待 30s 后查看 vts 数据
curl http://10.1.92.61:8080/metrics | grep 'location::corpor-batch'

8.2 验收标准

验收项 通过标准
Nginx metrics 正常暴露 curl /metrics 返回 200 + 包含 nginx_vts_* 指标
Prometheus 采集正常 Targets 页面所有节点状态为 UP
Grafana 数据可见 仪表盘可正确显示各中心耗时趋势图表
告警规则正常 手动触发告警条件后能在 Alertmanager/企业微信收到通知
性能影响可控 Nginx CPU 增加 < 1%,内存增加 < 10MB

九、总结

本方案通过 nginx-module-vts + Prometheus + Grafana 三层架构实现 Nginx 接口耗时的完整监控体系:

  1. nginx-module-vts :在 Nginx 侧零侵入地采集请求量、耗时、状态码等指标,通过 /metrics 接口暴露
  2. Prometheus:定时拉取 metrics,存储时序数据,执行告警规则
  3. Grafana:通过 PromQL 查询构建可视化仪表盘,实现实时监控和历史趋势分析
  4. Alertmanager:告警分组、去重、路由到企业微信/邮件

核心数据流Nginx 请求 → vts 统计 → /metrics → Prometheus scrape → Grafana 可视化 + Alertmanager 告警

相关推荐
IVEN_2 小时前
记一次诡异的前端白屏故障:Nginx Proxy Cache 内存缓存"幽灵"事件
前端·nginx
asyxchenchong8883 小时前
最新Hermes Agent 技能封装与科研自动化:以 Meta-Analysis 为例-实现从文献检索到绘图的一站式工作流
运维·人工智能·自动化
tianyuanwo3 小时前
项目内自我管理:一名OS领域DevOps的破局之路
运维·devops
三十..3 小时前
Redis 核心原理与高可用架构实践
运维·数据库·redis
jinglong.zha5 小时前
LScript-从零基础到商业变现的AI自动化学习平台
运维·学习·自动化
Adorable老犀牛6 小时前
Telegraf:InfluxData 出品的指标采集代理
运维·telegraf
北塔软件6 小时前
北塔软件智能体平台 | 不只监控,更是AI时代的数据资产
运维·人工智能·知识库·北塔软件
AOwhisky6 小时前
学习自测与解析:MySQL第五、六、七期核心知识点详解
运维·数据库·笔记·学习·mysql·云计算
无限进步_6 小时前
从零实现一个迷你Shell——深入理解Linux命令行解释器
linux·运维·服务器·开发语言·c++·chrome