跳转至

Kubernetes 监控体系

作者:JiangChong | 撰写时间:2026年07月

学完本文你将能够: ✅ 理解 K8s 监控体系的整体架构和指标管道 ✅ 使用 Prometheus 和 Grafana 搭建集群监控 ✅ 配置 HPA 实现水平自动扩缩容 ✅ 了解 VPA 和 Vertica 专属监控方案

前置阅读:请先读 Kubernetes 核心架构Kubernetes Pod 与 Deployment 实战。本文沿用系列统一的实验环境:Kind 集群 k8s-playground

0 本文概览

05 篇讲完了「谁能做什么」的安全控制,现在来解决另一个问题:集群跑起来了,但它健康状况怎么样?Pod 内存是不是快爆了?为什么请求突然变慢?HPA(HorizontalPodAutoscaler,水平自动扩缩容)到底触发了没有?

kubectl top node       → 节点 CPU/内存够不够?           → metrics-server
看历史趋势 + 设告警       → Prometheus + Grafana         → 指标管道
流量涨了自动加 Pod       → HPA(水平扩缩)                 → 自动伸缩
Pod 规格不合理           → VPA(VerticalPodAutoscaler,垂直扩缩)→ 资源优化

本文部分组件(Prometheus、Grafana、Loki)需要 Helm 部署,完整 Grafana + Loki + AlertManager 栈预计 2-4GB,Kind 单节点不做要求。Prometheus 单实例(~500MB)、metrics-server 和 HPA 均可在 k8s-playground 中实操。

什么是 Helm? 你可以理解为 K8s 的包管理器——就像 apt install nginx 一键安装软件,helm install prometheus 一键部署 Prometheus 及其所有依赖(ConfigMap、Deployment、Service、RBAC 等十几个 YAML 文件)。Helm Chart 就是这些 YAML 的打包模板。本文为了在 Kind 中跑通,直接用 kubectl apply 手写了最简配置,不走 Helm。

如果要用 Helm 在 Kind 中部署监控栈,需要做三件事:

  1. 装 Helmcurl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
  2. 重建 Kind 集群:Helm Chart 部署的 Prometheus 也需要从 kubelet 抓指标,和 metrics-server 一样需要 --kubelet-insecure-tls。在 kind-config.yaml 里写入:
    kind: Cluster
    apiVersion: kind.x-k8s.io/v1alpha4
    nodes:
    - role: control-plane
      kubeadmConfigPatches:
      - |
        kind: ClusterConfiguration
        apiServer:
          extraArgs:
            "kubelet-insecure-tls": "true"
    
  3. 重建集群kind delete cluster --name k8s-playground && kind create cluster --name k8s-playground --config ~/kind-config.yaml(⚠️ 会清掉现有所有实验数据)。然后 helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace --set prometheus.server.persistentVolume.enabled=false

本文没有选择这条路,因为用单实例 Prometheus(后面 3.2 节)已经足够理解指标采集和查询的核心流程,不需要为了一套 UI 重建整个实验环境。

1. 监控全貌

一个完整的 K8s 监控体系,通常被概括为可观测性三大支柱

┌──────────────────────────────────────────┐
│           可观测性三大支柱                  │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐  │
│  │  指标     │ │   日志   │ │   链路    │  │
│  │ (Metrics)│ │  (Logs)  │ │ (Traces) │  │
│  └──────────┘ └──────────┘ └──────────┘  │
│        │            │            │       │
│    ┌───▼───┐    ┌───▼───┐    ┌───▼───┐   │
│    │Prom   │    │Loki/  │    │Jaeger │   │
│    │etheus │    │EFK    │    │Tempo  │   │
│    └───┬───┘    └───────┘    └───────┘   │
│        │                                 │
│    ┌───▼───┐                             │
│    │Grafana│  ← 可视化层                  │
│    └───────┘                             │
└──────────────────────────────────────────┘

指标(Metrics) — 回答「发生了什么」。比如 CPU 使用率、内存占用量、Pod 重启次数。这些是随时间变化的数字,可以设阈值告警("CPU > 90% 通知我"),也是 HPA 自动扩缩容的依据。Prometheus 负责存储和查询这些数字,Grafana 负责把它们画成折线图和仪表盘。

日志(Logs) — 回答「为什么发生」。指标告诉你 CPU 飙了,但不知道是谁触发的。日志里有请求路径、参数、错误堆栈。Pod 重启后 kubectl logs 只能看到当前的输出,旧日志随 Pod 消失——所以需要 Loki 或 EFK 把日志集中存起来。

链路追踪(Traces) — 回答「卡在哪一步」。一个请求经过 Ingress → Service A → Service B → 数据库,链路追踪能告诉你 B 花了 3 秒而 A 只花了 50ms,瓶颈一目了然。分布式系统中尤其重要。

本文重点在指标这条线:从 cAdvisor 原始数据 → metrics-server 聚合 → Prometheus 存储 → Grafana 可视化 → HPA 自动伸缩。日志聚合见第 7 节,链路追踪见第 8 节。


2. 指标管道(Metrics Pipeline)

2.1 kubelet / cAdvisor — 底层指标源

每个节点上的 kubelet 集成了 cAdvisor 组件,自动采集该节点上所有容器的 CPU、内存、网络、磁盘等原始指标。

  • Metrics Server 通过 kubelet 的 /metrics/resource 端点获取资源指标(CPU/Memory)的聚合数据,这是 kubectl top 和 HPA 的数据来源
  • Prometheus 通过 kubelet 的 /metrics/cadvisor 端点获取容器级原始指标(container_* 前缀),可做更细粒度的分析

2.2 metrics-server — 集群级聚合器

metrics-server 是 K8s 的轻量级核心指标管道,从每个 kubelet 收集资源使用数据,供 kubectl top、HPA 和 VPA 使用。

k8s-playground 中安装:

Kind 集群的 kubelet 使用自签证书,直接装 metrics-server 会因为 TLS 验证失败而无法采集数据。需要加 --kubelet-insecure-tls 参数:

  1. 一键部署 metrics-server(这个 YAML 包含 Deployment、Service、ServiceAccount、RBAC 等全套资源)
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
    kubectl patch deployment metrics-server -n kube-system \
      --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
    
    如果镜像拉不下来(国内网络),步骤 1 改为以下替代流程(步骤 2、3 照常执行):
    # ① docker pull 走 DaoCloud 加速器(02 篇已配)
    docker pull docker.m.daocloud.io/registry.k8s.io/metrics-server/metrics-server:v0.7.2           # ①
    
    # ② 导入 Kind 节点
    docker save docker.m.daocloud.io/registry.k8s.io/metrics-server/metrics-server:v0.7.2 \         # ②
      | docker exec -i k8s-playground-control-plane ctr -n k8s.io images import -
    
    # ③ kubectl set image 换镜像(会触发滚动更新,冲掉之前的 --kubelet-insecure-tls)
    kubectl set image deployment/metrics-server -n kube-system \                                    # ③
      metrics-server=docker.m.daocloud.io/registry.k8s.io/metrics-server/metrics-server:v0.7.2
    
    # ④ 重新 patch --kubelet-insecure-tls
    kubectl patch deployment metrics-server -n kube-system \                                        # ④
      --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
    
  2. 等待 metrics-server Pod 就绪
    kubectl wait --for=condition=ready pod -l k8s-app=metrics-server -n kube-system --timeout=120s
    # pod/metrics-server-5fc89784df-bwlrf condition met
    
  3. 验证 —— 能看到节点和 Pod 的资源用量了!
    kubectl top node
    # NAME                           CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
    # k8s-playground-control-plane   369m         4%     1033Mi          8%
    
    kubectl top pod -n dev
    # NAME       CPU(cores)   MEMORY(bytes)
    # bot-test   0m           7Mi
    

--kubelet-insecure-tls 仅在 Kind/Minikube 等测试环境使用,生产环境必须用正经 CA 签发的 kubelet 证书。

数据管道:

kubelet (cAdvisor) ──► metrics-server ──► kubectl top / HPA / VPA

注意: metrics-server 只存内存快照,无历史数据。需要历史趋势和告警就必须上 Prometheus。

# 清理(后面的实验不需要 metrics-server 时执行)
kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

3. Prometheus — 指标存储与告警

Prometheus 是 CNCF 毕业项目,也是 K8s 监控的事实标准。

3.1 架构概览

ServiceMonitor  ──► 发现 target
应用 / 组件 ──► 暴露 /metrics ──► Prometheus Server
                                    ┌──────┴──────┐
                                    │             │
                                 TSDB        AlertManager
                                    │             │
                                 Grafana ────► 通知渠道
                                    │      (钉钉/PagerDuty/邮件)
                            ┌───────┴───────┐
                            │  Dashboard    │

3.2 动手实操:在 Kind 中部署最简 Prometheus

完整 kube-prometheus-stack(Prometheus + Grafana + AlertManager + node-exporter)资源占用 2-4GB,Kind 跑不动。但只跑 Prometheus 本身就够了——它能抓取指标、存储时序数据、提供 Web UI 查询。下面在 k8s-playground 中部署最精简版:

# 1. 创建 namespace 和 RBAC(Prometheus 需要读 kubelet metrics 端点)
kubectl create namespace monitoring
# namespace/monitoring created

kubectl create clusterrolebinding prometheus-default --clusterrole=cluster-admin \
  --serviceaccount=monitoring:default
# clusterrolebinding.rbac.authorization.k8s.io/prometheus-default created

# 2. 创建 Prometheus ConfigMap(告诉它去抓哪些 /metrics)
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: monitoring
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']
    - job_name: 'kubelet-cadvisor'
      scheme: https
      tls_config:
        insecure_skip_verify: true
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      static_configs:
      - targets: ['k8s-playground-control-plane:10250']
EOF
# configmap/prometheus-config created

# 3. 部署 Prometheus
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      containers:
      - name: prometheus
        image: prom/prometheus:v3.0.0
        args: ["--config.file=/etc/prometheus/prometheus.yml"]
        ports:
        - containerPort: 9090
        volumeMounts:
        - name: config
          mountPath: /etc/prometheus
      volumes:
      - name: config
        configMap:
          name: prometheus-config
EOF
# deployment.apps/prometheus created

# 如果拉不下来(国内网络),本机 crane pull + scp(方法同下面 Grafana 的步骤 1 中的注释)

# 4. 验证(等 Pod Running)
kubectl wait --for=condition=ready pod -l app=prometheus -n monitoring --timeout=60s
kubectl expose deployment prometheus --port=9090 -n monitoring
kubectl port-forward -n monitoring svc/prometheus 9090:9090

然后用 curl 验证 Prometheus 已经抓到了数据(无桌面的服务器环境用 curl 代替浏览器):

curl -s 'http://localhost:9090/api/v1/query?query=kubelet_running_containers' \
  | python3 -m json.tool | head -20
# 返回 JSON 数据说明 Prometheus 已经从 kubelet 抓到了容器指标

# 有桌面环境的可以用浏览器打开 http://localhost:9090,在 Graph 页面输入 kubelet_running_containers 点 Execute

Prometheus 查询界面

这只是验证流程。生产环境部署用 Operator + ServiceMonitor(见 3.3 节),能自动发现新 target、管理告警规则。

组件 采集内容 重要性
kubelet 节点和容器资源指标 必选
kube-state-metrics K8s 对象状态(Pod、Deployment、Node 等) 必选
node-exporter 节点 OS 级别指标(磁盘、网络、CPU 负载) 必选
kube-apiserver API Server 请求延迟和错误率
kube-scheduler 调度决策指标 中等
kube-controller-manager 控制器调谐指标 中等
etcd etcd 磁盘 fsync 延迟 关键

Prometheus 的工作方式是拉取(Scrape):每隔一段时间(默认 15 秒),它去每个组件的 /metrics 端点抓取当前的指标数据。这与传统的"组件主动上报"(Push)模式不同。

3.3 ServiceMonitor — 自动发现 target

前面 3.2 节用的是 static_configs 手动指定 kubelet IP——这在生产环境中不可维护:每次新增一个 Vertica 实例、每次 Pod IP 变化,都得改 ConfigMap 重载 Prometheus。

ServiceMonitor 解决这个问题: 它是 Prometheus Operator 提供的一种 CRD(Custom Resource Definition),告诉 Prometheus「去带 app: vertica-exporter 标签的 Service 上抓 /metrics」。Operator 自动发现新 Pod,Prometheus 配置自动更新,完全不需要手动改 ConfigMap。

apiVersion: monitoring.coreos.com/v1    # Prometheus Operator 提供的 CRD
kind: ServiceMonitor                     # 告诉 Operator 发现了新 target
metadata:
  name: vertica-monitor
  namespace: monitoring                  # ServiceMonitor 本身所在的 namespace
spec:
  namespaceSelector:                     # 去哪些 namespace 找 target
    matchNames:
    - vertica                            # 只在 vertica namespace 找
  selector:                              # 找带哪些 label 的 Service
    matchLabels:
      app: vertica-exporter              # 匹配 app=vertica-exporter 的 Service
  endpoints:                             # 怎么抓这个 target
  - port: metrics                        # 抓 Service 上叫 metrics 的端口
    interval: 15s                        # 每 15 秒抓一次
    scrapeTimeout: 10s                   # 单次抓取超时 10 秒

和我们 3.2 节的手动配置对比:

3.2 节 static_configs ServiceMonitor
发现方式 手写 IP + 端口 按标签自动发现
新增 target 改 ConfigMap + 重载 自动
需要 Operator 是(prometheus-operator)
适用场景 测试/固定 target 生产环境

ServiceMonitor 需要 Prometheus Operator 支持(Kind 中没有,生产环境通过 kube-prometheus-stack Helm Chart 部署)。3.2 节的手动方式让你理解了底层原理——Operator 本质上是帮你自动生成 scrape_configs

动手验证:手动模拟 ServiceMonitor 的效果

在现有 Prometheus 上加一个新 target,看看 static_configs 和 ServiceMonitor 的对应关系:

# 1. 给 nginx Pod 加一个最简单的 /metrics(用 nginx 内置的 stub_status)
kubectl exec bot-test -n dev -- /bin/sh -c \
  "cat > /etc/nginx/conf.d/status.conf << 'EOF'
server { listen 8080; location /metrics { stub_status; } }
EOF
nginx -s reload"

# 2. 创建 Service,打上标签
kubectl expose pod bot-test -n dev --port=8080 --name=nginx-metrics
kubectl label service nginx-metrics -n dev app=nginx-exporter

# 3. 更新 Prometheus ConfigMap,加一条 static_configs
kubectl patch configmap prometheus-config -n monitoring --type merge -p '
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
    scrape_configs:
    - job_name: "prometheus"
      static_configs:
      - targets: ["localhost:9090"]
    - job_name: "kubelet-cadvisor"
      scheme: https
      tls_config:
        insecure_skip_verify: true
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      static_configs:
      - targets: ["k8s-playground-control-plane:10250"]
    - job_name: "nginx-exporter"          # ← 新增的 job
      static_configs:
      - targets: ["nginx-metrics.dev.svc.cluster.local:8080"]
'

# 4. 重载 Prometheus 配置
kubectl rollout restart deployment prometheus -n monitoring
kubectl wait --for=condition=ready pod -l app=prometheus -n monitoring --timeout=60s

# 5. 验证 —— 新 target 已出现
curl -s 'http://localhost:9090/api/v1/targets' | python3 -m json.tool | grep -A3 'nginx-exporter'

# ⚠️ 注意:nginx stub_status 输出的不是 Prometheus 格式(是纯文本),
# 所以 target 会出现但 scrape 状态会显示为 DOWN(scrape error)。
# 生产环境中需要 nginx-prometheus-exporter 作为适配层转换格式。
# 这里的实验目的只是展示 static_configs 的 target 发现机制,
# 不影响对 ServiceMonitor 工作原理的理解。

上面步骤 3 的 scrape_configs 如果改用 ServiceMonitor 表达,就是:

# 不需要手写 static_configs,Operator 自动扫描 dev namespace 里
# 所有带 app=nginx-exporter 标签的 Service
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: nginx-monitor
  namespace: monitoring
spec:
  namespaceSelector:
    matchNames: [dev]
  selector:
    matchLabels:
      app: nginx-exporter
  endpoints:
  - port: "8080"
    interval: 15s

对比:static_configs 要手动写 targets: [IP:Port];ServiceMonitor 只需要写 selector.matchLabels,Operator 自动扫到匹配的 Service 并解析其 ClusterIP + Port。

# 清理 ServiceMonitor 演示
kubectl delete service nginx-metrics -n dev
kubectl label pod bot-test -n dev app-

4. Grafana — 可视化

k8s-playground 中部署 Grafana(~200MB,连已有 Prometheus):

# 1. 部署 Grafana
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:11.3.0
        ports:
        - containerPort: 3000
        env:
        - name: GF_AUTH_ANONYMOUS_ENABLED
          value: "true"           # 测试环境免登录
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: monitoring
spec:
  selector:
    app: grafana
  ports:
  - port: 3000
EOF

# 如果拉不下来(国内 Docker Hub 超时),用 crane(02 篇已装)在本机拉 + scp 传 Debian:
#   MacBook:  crane pull grafana/grafana:11.3.0 grafana.tar
#             scp grafana.tar k8s@<debian-ip>:~/
#   Debian:   docker load -i grafana.tar
#             docker save grafana/grafana:11.3.0 \
#               | docker exec -i k8s-playground-control-plane ctr -n k8s.io images import -

# 2. 等待就绪 + 暴露端口
kubectl wait --for=condition=ready pod -l app=grafana -n monitoring --timeout=120s
kubectl expose deployment grafana --port=3000 -n monitoring 2>/dev/null
kubectl port-forward -n monitoring svc/grafana --address=0.0.0.0 3001:3000

浏览器打开 http://<debian-ip>:3001,配置和数据源见下节。

4.1 连接 Prometheus + 用 Explore 画图表

Grafana 11 界面直达数据源(需要使用admin/admin登陆):http://<debian-ip>:3001/datasources → Add new data source → Prometheus → URL 填 http://prometheus.monitoring.svc.cluster.local:9090 → Save & test(应返回绿色成功)。

然后验证:左侧菜单 → 🧭 Explore → 选 Prometheus → 输入框填 up → 右上角 Run query → 看到绿线说明数据源正常。

我们只部署了最简 Prometheus(kubelet 指标),没有 node_exporter 和 kube-state-metrics。Grafana Dashboard 市场(如 ID 1860315)依赖这些额外 exporter 的指标,在当前环境中会全部空白。用 Explore 自建图表对我们的环境更实用:

在 Explore 里输入以下 PromQL,每试一个点 Run query,看到数据后点 Add to dashboard 保存:

up                                                  → 哪些 target 在抓(2 条线 = prometheus + kubelet)
kubelet_running_containers                          → 集群运行中的容器数
rate(kubelet_started_containers_total[5m])          → 过去 5 分钟每秒启动容器速率
prometheus_http_requests_total                      → Prometheus 收到的 HTTP 请求总数

Grafana Explore 界面

完整监控栈(Helm 部署 kube-prometheus-stack)会自带 50+ 现成 Dashboard。本文选最简方案是为了在 Kind 中跑通核心流程——Explore 和 Dashboard 本质上查的是同一套 PromQL,学会 Explore 就不会被缺少 exporter 卡住。

# 清理
kubectl delete deployment grafana prometheus -n monitoring
kubectl delete service grafana prometheus -n monitoring
kubectl delete configmap prometheus-config -n monitoring
kubectl delete clusterrolebinding prometheus-default
kubectl delete ns monitoring

4.2 告警规则(Alert Rules)

以下是一些 K8s 核心告警规则的 PromQL 示例:

# 告警规则配置片段
groups:
- name: kubernetes-critical
  rules:
  # Pod 反复重启
  - alert: KubePodCrashLooping
    expr: rate(kube_pod_container_status_restarts_total[5m]) * 60 > 5
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 反复重启"

  # 节点不可用
  - alert: NodeNotReady
    expr: kube_node_status_condition{condition="Ready",status="true"} == 0
    for: 5m
    labels:
      severity: critical

  # CPU throttling 阈值高
  # 这条 PromQL 的含义:`rate(...[5m])` 计算过去 5 分钟的平均每秒增长率;`cfs_throttled_seconds` 是容器被 CPU 限流的总秒数。所以整体含义是:过去 5 分钟,容器的 CPU 每秒被限流了多少秒。
  - alert: ContainerCPUThrottling
    expr: rate(container_cpu_cfs_throttled_seconds_total[5m]) / rate(container_cpu_cfs_periods_total[5m]) > 0.2
    for: 10m
    labels:
      severity: warning

  # 磁盘空间不足
  - alert: NodeDiskPressure
    expr: node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} < 0.1
    for: 5m
    labels:
      severity: critical

  # etcd fsync 延迟
  - alert: EtcdSlowDisk
    expr: histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) > 0.5
    for: 5m
    labels:
      severity: critical

5. HPA(HorizontalPodAutoscaler)— 水平自动扩缩容

HPA(HorizontalPodAutoscaler,水平 Pod 自动扩缩容器)根据监控指标自动调整 Pod 副本数。前提是 metrics-server 已安装(上一步 2.2 刚配好)。

5.1 动手实操:给 nginx Deployment 加自动扩缩

# 1. 创建一个 Deployment + 设定 CPU 资源请求(HPA 必须基于 requests 计算百分比)
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hpa
  namespace: dev
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-hpa
  template:
    metadata:
      labels:
        app: nginx-hpa
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        resources:
          requests:
            cpu: "100m"       # HPA 的利用率 = 实际用量 / 这个值
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
EOF

# 2. 创建 HPA:目标 CPU 利用率 50%,副本数 1-5
kubectl autoscale deployment nginx-hpa --cpu=50% --min=1 --max=5 -n dev
# horizontalpodautoscaler.autoscaling/nginx-hpa autoscaled

# 3. 查看 HPA 状态(初始 TARGETS 显示 <unknown>/50% 是正常的,等 30 秒刷新)
kubectl get hpa -n dev
# NAME        REFERENCE              TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
# nginx-hpa   Deployment/nginx-hpa   cpu: 0%/50%   1         5         1          69s

# 4. 生成负载 —— 在另一个终端跑一个 busybox 持续请求 nginx
kubectl expose deployment nginx-hpa --port=80 -n dev
kubectl run load-generator --image=busybox:1.36 -n dev -- /bin/sh -c \
  "while true; do for i in 1 2 3 4 5; do wget -q -O- http://nginx-hpa; done; sleep 0.2"

# 5. 等 1-2 分钟后再看 HPA —— 副本数应该涨了
kubectl get hpa -n dev -w
# NAME        REFERENCE              TARGETS        MINPODS   MAXPODS   REPLICAS   AGE
# nginx-hpa   Deployment/nginx-hpa   cpu: 33%/50%   1         5         1          11m
# nginx-hpa   Deployment/nginx-hpa   cpu: 91%/50%   1         5         1          11m
# nginx-hpa   Deployment/nginx-hpa   cpu: 29%/50%   1         5         2          11m
# nginx-hpa   Deployment/nginx-hpa   cpu: 21%/50%   1         5         2          11m
# nginx-hpa   Deployment/nginx-hpa   cpu: 44%/50%   1         5         2          12m
# nginx-hpa   Deployment/nginx-hpa   cpu: 46%/50%   1         5         2          12m
# nginx-hpa   Deployment/nginx-hpa   cpu: 39%/50%   1         5         2          12m
# nginx-hpa   Deployment/nginx-hpa   cpu: 22%/50%   1         5         2          12m
# nginx-hpa   Deployment/nginx-hpa   cpu: 43%/50%   1         5         2          13m
# nginx-hpa   Deployment/nginx-hpa   cpu: 46%/50%   1         5         2          13m
# nginx-hpa   Deployment/nginx-hpa   cpu: 38%/50%   1         5         2          13m
# nginx-hpa   Deployment/nginx-hpa   cpu: 20%/50%   1         5         2          13m
# nginx-hpa   Deployment/nginx-hpa   cpu: 34%/50%   1         5         2          14m
# nginx-hpa   Deployment/nginx-hpa   cpu: 46%/50%   1         5         2          14m
# nginx-hpa   Deployment/nginx-hpa   cpu: 36%/50%   1         5         2          14m
# nginx-hpa   Deployment/nginx-hpa   cpu: 20%/50%   1         5         2          14m
# nginx-hpa   Deployment/nginx-hpa   cpu: 31%/50%   1         5         2          15m
# nginx-hpa   Deployment/nginx-hpa   cpu: 40%/50%   1         5         2          15m
# nginx-hpa   Deployment/nginx-hpa   cpu: 43%/50%   1         5         2          15m
# nginx-hpa   Deployment/nginx-hpa   cpu: 22%/50%   1         5         2          15m
# nginx-hpa   Deployment/nginx-hpa   cpu: 26%/50%   1         5         2          16m
# nginx-hpa   Deployment/nginx-hpa   cpu: 43%/50%   1         5         2          16m
# nginx-hpa   Deployment/nginx-hpa   cpu: 47%/50%   1         5         2          16m
# nginx-hpa   Deployment/nginx-hpa   cpu: 23%/50%   1         5         2          16m
# nginx-hpa   Deployment/nginx-hpa   cpu: 24%/50%   1         5         2          17m
# nginx-hpa   Deployment/nginx-hpa   cpu: 41%/50%   1         5         2          17m

当 CPU 降到 50% 以下后,HPA 会逐步缩容回 1 个副本。整个过程自动发生,不需要人工介入——这就是 HPA 的核心价值。

# 清理
kubectl delete hpa nginx-hpa -n dev
kubectl delete deployment nginx-hpa -n dev
kubectl delete pod load-generator -n dev

5.2 CPU/Memory 指标

上面的实操用的是 CPU 利用率,HPA 也支持同时监控多个指标。以下展示一个更复杂的 HPA 配置(注意这是 Vertica 场景的示例,依赖 VerticaDB Operator,不在当前 playground 中):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vertica-query
  namespace: vertica
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: StatefulSet
    name: vertica-sc
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70   # Pod CPU 平均使用率达到 70% 触发扩容
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

5.3 自定义指标(Custom Metrics)

HPA v2 API 支持四种指标类型:

指标类型 来源 典型场景
Resource Metrics Server(CPU/Memory) 基础扩缩容
Pods Prometheus Adapter(Pod 级自定义指标) 应用 QPS、连接数
Object Prometheus Adapter(K8s 对象级指标) Ingress 请求率、Service 延迟
External 外部系统(消息队列长度、云监控指标) Kafka lag、SQS 队列深度

多指标取最大值: 当 HPA 配置了多个指标(如同时用 CPU 和 Memory),HPA 为每个指标分别计算期望副本数,然后取最大值。这确保所有约束都能满足。

使用 Prometheus Adapter 接入任意 Prometheus 指标:

metrics:
- type: Pods
  pods:
    metric:
      name: vertica_query_concurrency
    target:
      type: AverageValue
      averageValue: 50

HPA 工作原理:

期望副本数 = ceil(当前副本数 × (当前指标值 / 目标指标值))

例如:当前 3 个 Pod,CPU 平均使用率 80%,目标 70%:

3 × (80 / 70) = 3.43 → ceil(3.43) = 4

容忍度(Tolerance): HPA 内置 10% 的容忍度tolerance: 0.1),只有当期望副本数与当前副本数的偏差超过 10% 时才会真正执行扩缩容。例如当前 3 个 Pod,期望 3.2 个(偏差 6.7%),HPA 不会触发扩容。该机制避免了指标微小波动导致的频繁扩缩。

5.4 HPA 使用注意事项

  • --horizontal-pod-autoscaler-sync-period:HPA 评估周期(默认 15s)
  • behavior.scaleDown.stabilizationWindowSeconds:缩容稳定窗口(默认 300s / 5min),防止指标抖动导致频繁缩容
  • 不要同时使用 HPA 和 VPA 对同一指标扩缩容 — 两者会产生冲突
  • 以上 HPA 实操针对无状态服务(如 nginx),扩缩容只是加减 Pod 副本。对于有状态数据库(如 Vertica):扩容需要新节点加入集群协调,缩容需要 drain 数据,直接使用 HPA 可能引发数据一致性问题。建议使用 Operator 管理的自定义扩缩容策略。

6. VPA(VerticalPodAutoscaler)— 垂直自动扩缩容

HPA 解决的是「副本数不够」——流量大了加 Pod。但还有一个问题 HPA 解决不了:每个 Pod 的 CPU/Memory 配额该设多少? 设大了浪费资源,设小了被 OOM 杀掉或被限流。手动调?每个服务的负载模式不同、每天每时都变,根本调不过来。

VPA(VerticalPodAutoscaler,垂直 Pod 自动扩缩容器)自动做这件事:根据 Pod 的历史资源使用情况推荐最优值,甚至可以自动重建 Pod 应用新配额。

HPA vs VPA 一句话: HPA 横向加 Pod(水平),VPA 纵向改 Pod 大小(垂直)。两者不能同时管同一个资源的同一个指标——HPA 改副本数,VPA 改 Pod 规格,互相干扰。

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: vertica-vpa
spec:
  targetRef:                              # VPA 的目标对象
    apiVersion: apps/v1
    kind: StatefulSet
    name: vertica-sc
  updatePolicy:
    updateMode: "Off"                     # 先设 Off 只看推荐不动手,确认合理后改 Auto
  resourcePolicy:
    containerPolicies:
    - containerName: vertica              # 对哪个容器生效
      minAllowed:                         # 安全下限(避免 VPA 建议太小导致 OOM)
        cpu: "2"
        memory: 8Gi
      maxAllowed:                         # 安全上限(避免 VPA 建议太大浪费资源)
        cpu: "16"
        memory: 64Gi

四种模式:

模式 行为 适用场景
Off 仅生成推荐值,不动 Pod 先观察、评估
Initial 只在新 Pod 创建时设推荐值 保守策略
Auto 自动重建 Pod 以应用新值 信任 VPA 后启用
Recreate 同 Auto,运行中的 Pod 也立即重建 激进策略

VPA 的 Recommender 组件依赖 metrics-server 的历史数据(至少运行 24 小时后才能给出可靠推荐),Updater 负责实际执行 Pod 重建,Admission Controller 在 Pod 创建时拦截并注入推荐资源值。Kind 单节点部署 VPA 可行但意义不大——推荐结果需要大量历史数据支撑。

查看 VPA 推荐(部署后):

kubectl describe vpa vertica-vpa -n vertica
# Recommendation:
#   Container Recommendations:
#     Container Name:  vertica
#     Lower Bound:     cpu: 200m, memory: 4Gi
#     Target:          cpu: 500m, memory: 8Gi      ← 推荐值
#     Upper Bound:     cpu: 1, memory: 16Gi

7. 日志聚合

指标告诉你「发生了什么」(CPU 飙升),但查原因需要日志——「哪个请求触发的?参数是什么?」。K8s 中 kubectl logs 有两个致命短板:

  • Pod 重启即销毁:Pod 被 OOM 杀掉后重新调度,旧 Pod 的日志跟着一起消失——你永远看不到它崩溃前最后输出了什么
  • 跨 Pod 搜索无力:一个请求经过了 Ingress → Service A → Service B,分别在三处输出日志,kubectl logs 只能逐个 Pod 翻

日志聚合把分散的日志统一收集、存储、提供搜索——Pod 重启了不丢,跨 Pod 一行命令搜出来。

7.1 EFK / Loki Stack

方案 组件 特点 适用
EFK Elasticsearch + Fluent Bit + Kibana 全文索引每一行日志,查询快但吃资源 大厂,预算足
Loki Loki + Promtail + Grafana 只索引标签(namespace/pod/label),日志内容不建索引 中小集群,与现有 Grafana 集成

Promtail 以 DaemonSet 运行在每个节点上,tail 每个容器的日志文件,推送给 Loki。Loki 和 Prometheus 共享 Grafana 界面——同一个页面上看指标图表的下面就是相关日志,值班排查不再需要切三个窗口。

helm upgrade --install loki grafana/loki-stack \
  --namespace monitoring \
  --set promtail.enabled=true \
  --set grafana.enabled=false

Loki 和 Grafana 一样,完整部署需要 ~1GB+ 资源,Kind 环境不做实操要求。核心概念:日志经过 DaemonSet(Promtail)→ 中央存储(Loki)→ Grafana 搜索界面。


8. 链路追踪(Traces)

指标告诉你「哪里慢了」,日志告诉你「发生了什么错误」,但当请求经过 Ingress → Service A → Service B → Service C → Database 这样一条长链时,你需要知道每一步各花了多少时间——这就是链路追踪。

例如:一次 API 请求花了 500ms,Prometheus 只能告诉你「这个接口 P99 延迟 500ms」,但不知道是 Service A 慢还是 Database 慢。Traces 把它拆成 A: 20ms → B: 30ms → DB: 430ms → 「原来是数据库查询占了大部分时间」。

每条 trace 由多个 span 组成(一个 span = 一次调用),共享一个 traceID。在 Jaeger UI 里看到的瀑布图就是 spans 按时间排列的可视化结果。

K8s 生态中常用的方案:

方案 来源 特点 与 Grafana 集成
Jaeger Uber 开源 CNCF 毕业,UI 最成熟,社区最大 需单独部署
Tempo Grafana 出品 与 Loki/Prometheus 统一界面,存对象存储成本低 ✅ 原生
Zipkin Twitter 开源 最老牌,轻量级 需单独部署

链路追踪需要应用代码埋点(在每次 HTTP/gRPC 调用时创建 span 并传递 traceID),不像指标和日志那样基础设施层就能采集。目前行业标准库是 OpenTelemetry(OTel)——一套 SDK 同时输出 traces + metrics + logs 到任意后端(Jaeger、Tempo、Zipkin 都支持)。本文不做实操。


9. Vertica on K8s 监控

前面讲的都是 K8s 通用监控。Vertica 有自己的专属指标——节点状态、查询耗时、ROS 容器数——这些需要 Vertica Exporter 来暴露。下面回到本系列的核心场景。

9.1 Vertica Prometheus Exporter

Vertica 官方提供 Prometheus Exporter,作为 Sidecar 容器和 Vertica Pod 部署在一起,通过 localhost 连接 Vertica 进程,暴露 /metrics 端点。再加上 3.3 节的 ServiceMonitor,整个链路是:

Vertica Pod (vertica-exporter sidecar, port 9724)
Service (带标签 app: vertica-exporter)
ServiceMonitor (自动发现标签) → Prometheus scrape → Grafana Dashboard
# VerticaDB CR 配置(需 VerticaDB Operator 支持)
spec:
  sidecars:
  - name: vertica-exporter
    image: vertica/prometheus-exporter:latest
    env:
    - name: VERTICA_HOST
      value: "localhost"        # 和 Vertica 进程在同一个 Pod,localhost 直连
    ports:
    - containerPort: 9724
      name: metrics             # ServiceMonitor 按这个端口名匹配

9.2 关键监控指标

类别 指标 告警条件示例
集群状态 vertica_node_up = 0 → 节点(集群)宕机
查询性能 vertica_query_runtime_seconds P99 > 60s → 长查询阻塞
存储 vertica_storage_used_bytes > 80% 配额 → 扩容
并发 vertica_active_sessions > resource_pool 上限 → 排队
资源池 vertica_resource_pool_memory_used > 90% → OOM 风险
ROS vertica_ros_containers_count > 1024 → pushback,Tuple Mover 跟不上

结合 Kubernetes RBAC 权限与安全 中的 RBAC 配置,确保 Prometheus 有权限跨 Namespace 采集 Vertica 指标。Prometheus 直接通过 kubelet API(端口 10250)抓取容器指标,认证依赖 bearer token(由 ServiceAccount 自动挂载),不需要额外的 pods/proxy RBAC 权限——只需确保 ServiceAccount 所在的 Namespace 能访问目标 kubelet 即可(3.2 节用了 cluster-admin,生产环境建议改为最小权限的 ClusterRole)。


10. 总结

层次 工具 用途 本文实操
核心指标 metrics-server 基础 CPU/Memory,供 HPA、kubectl top ✅ 已部署
指标存储 Prometheus 全量指标、历史数据 ✅ 已部署 + Explore
可视化 Grafana 折线图、仪表盘 ✅ 已部署 + 连 Prometheus
自动扩缩 HPA + VPA 水平和垂直弹性伸缩 HPA ✅ / VPA 概念
日志 Loki / EFK 日志聚合与搜索 概念
链路 Jaeger / Tempo 分布式追踪 概念
数据库监控 Vertica Exporter + ServiceMonitor Vertica 专属指标 概念

三条核心 PromQL 记住就够用:

rate(metric_total[5m])          # 计算每秒增长率
histogram_quantile(0.99, ...)   # 计算 P99 分位数
sum(rate(...)) by (pod)         # 按 Pod 聚合

→ 继续阅读 Kubernetes 二进制部署实战 了解如何从零搭建生产级 K8s 集群。