跳到主要内容

监控、日志与可观测性

前面几篇把集群搭建、工作负载、网络、存储都过了一遍。集群跑起来之后,下一个绕不开的问题就是可观测性(Observability):集群现在什么状态?哪里出了问题?为什么这个 Pod 起不来?

云原生语境下,可观测性靠三根支柱撑着:Metrics(指标)、Logs(日志)、Traces(链路追踪),外加 K8s 特有的 Event(事件)。这篇把围绕它们的主流工具串一遍——Metrics Server、Prometheus、Alertmanager、Grafana、Loki、OpenTelemetry、Vector、Filebeat、cAdvisor——看完能对整套体系有个全景认识,知道每个组件解决什么问题、彼此怎么配合。

可观测性四链路总览

Metrics Server​

Metrics Server 是 K8s 官方**资源指标管道(resource metrics pipeline)**里的汇总组件,专门采集一类数据:资源实际用量——节点和 Pod 的 CPU、内存当前用了多少。

数据链路如下:

Metrics Server 数据流

最常依赖它的两个功能:

  • kubectl top:人肉查看节点和 Pod 的资源用量。
  • HPA(Horizontal Pod Autoscaler):按 CPU/内存利用率自动增减副本数,它判断「当前利用率」的数据就来自 Metrics Server。没装 Metrics Server,HPA 直接不工作。

安装很简单,官方提供了一个 YAML:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

测试集群(kind、minikube、kubeadm 自建)里 kubelet 用的是自签证书,Metrics Server 默认校验 TLS,抓取时会报 x509 错误。解决办法是给它加 --kubelet-insecure-tls 参数,跳过对 kubelet 证书的校验:

kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
仅限测试环境

--kubelet-insecure-tls 会让 Metrics Server 不再校验 kubelet 证书,有中间人攻击风险,只能在学习、测试集群里用。生产集群要么给 kubelet 配可信证书,要么用 --kubelet-certificate-authority 指定 CA。

装好后验证:

kubectl top nodes
kubectl top pods -A
NAME            CPU(cores)   MEMORY(bytes)
control-plane 158m 1496Mi
node-1 96m 1024Mi
node-2 87m 960Mi
它只报「现在」

Metrics Server 只在内存里保存当前值,不存历史、不做告警。想要历史曲线和告警能力,得靠后面的 Prometheus 体系。

kube-state-metrics​

kube-state-metrics(常简写 KSM)关注的不是「资源用了多少」,而是「对象是什么状态」。它监听 API Server 里的各种资源对象,把它们的状态翻译成 Prometheus 格式的指标暴露出来:

  • Deployment:期望几个副本、现在可用几个、滚动更新卡没卡住。
  • Pod:处于哪个 Phase(Pending/Running/Failed)、重启了几次、requests/limits 配了多少。
  • Node:Ready 条件是否成立、有没有 DiskPressure、可分配资源多少。
  • 其他:Job 完成情况、CronJob 上次调度时间、PVC 绑定状态……

指标长这样:

kube_deployment_status_replicas{deployment="nginx",namespace="default"} 3
kube_deployment_status_replicas_available{deployment="nginx",namespace="default"} 2
kube_pod_container_status_restarts_total{namespace="default",pod="nginx-xxx",container="nginx"} 5
kube_node_status_condition{node="node-1",condition="Ready",status="true"} 1

与 Metrics Server 的区别​

两个名字都带 metrics,干的活完全不同,别搞混:

对比项Metrics Serverkube-state-metrics
采集内容CPU/内存等资源用量Deployment、Pod、Node 等对象状态
数据来源各节点 kubelet 的 Summary APIAPI Server(watch 资源对象)
暴露方式K8s 聚合 API(metrics.k8s.io)自身 /metrics 端点(Prometheus 格式)
典型消费者kubectl top、HPA、VPAPrometheus + Grafana/告警
回答的问题「这个 Pod 现在用了多少 CPU」「这个 Deployment 的 3 个副本是不是都健康」
历史数据不保存不保存(由 Prometheus 抓取后保存)
两个都要装

两者是互补关系:kubectl top 和 HPA 依赖 Metrics Server,「副本数不对」「Pod 一直 Pending」这类告警依赖 kube-state-metrics。生产集群两个都是标配。

Prometheus​

Prometheus 是云原生监控的事实标准,也是 CNCF 第二个毕业项目(第一个是 K8s 本身)。它是一套完整的监控系统:抓取 + 存储 + 查询 + 告警评估一体。

拉模型架构​

Prometheus 最核心的设计是拉模型(Pull):由 Prometheus 按配置的间隔,主动到各目标(target)的 /metrics HTTP 端点上拉取指标,而不是等目标推过来。

Prometheus 架构

拉模型的几个好处:

  • 目标挂了立刻知道:拉不到数据,up 指标直接归零,天然带健康检查。
  • 配置集中:抓谁、多久抓一次都在 Prometheus 侧定义。
  • 采样频率可控:不怕客户端突发推送打爆服务端。

应用怎么暴露 /metrics?三种方式:

  • 自己埋点:引入对应语言的 client library(Go/Java/Python 都有),在应用里暴露 /metrics。
  • 用 exporter:第三方系统(MySQL、Redis、nginx……)有现成 exporter,把它们的数据翻译成 Prometheus 格式。
  • 短命任务(Job/CronJob)来不及被拉:主动推到 Pushgateway 中转。

K8s 里的服务发现​

K8s 里 Pod 朝生暮死,手工维护抓取目标列表不现实。Prometheus 内置了 K8s 服务发现 kubernetes_sd_configs:通过 API Server 自动发现集群里的节点、Service、Pod、Endpoints,再配合 relabel_configs 筛选、改写标签,生成抓取目标:

scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod # 自动发现所有 Pod
relabel_configs:
# 只抓带 prometheus.io/scrape: "true" 注解的 Pod
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"

这样新起一个 Pod,只要打了约定注解,就会被自动纳入监控,不用改任何配置文件。

生产推荐:prometheus-operator​

手写一大坨 scrape_config 加 relabel 规则,维护成本很高。生产环境普遍用 prometheus-operator,或者干脆装 kube-prometheus-stack 这个 Helm chart 一把梭:Prometheus + Alertmanager + Grafana + kube-state-metrics + node-exporter + 一堆默认告警规则和面板全齐。

Operator 的思路是把「抓谁」也变成 K8s 资源——它定义了 ServiceMonitor、PodMonitor 等 CRD,我们声明式地描述抓取需求,Operator 监听这些对象并自动生成 Prometheus 配置。一个 ServiceMonitor 示例:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: demo-app
namespace: monitoring
labels:
release: prometheus # 要和 Prometheus 实例的 serviceMonitorSelector 对上
spec:
selector:
matchLabels:
app: demo-app # 选中带这个标签的 Service
endpoints:
- port: metrics # Service 中名为 metrics 的端口
interval: 30s # 抓取间隔

PodMonitor 同理,只不过直接选 Pod,适合没有对应 Service 的组件(比如 DaemonSet 形态的采集器)。配好 CR 之后,Prometheus 会自动开始抓取对应目标,全程不用重启、不用手改配置。

Alertmanager​

Prometheus 负责评估告警规则、产生告警,但「怎么通知人」这件事交给了 Alertmanager。它收到告警后做四件事:

概念解决的问题一句话说明
分组 Grouping告警轰炸把相似告警合并成一条通知,比如同一台机器上的 20 条告警合成一条
路由 Routing发给谁按告警 label 匹配路由树,critical 发给值班,warning 发到群里
抑制 Inhibition重复告警高级别告警触发时压掉相关的低级别告警:节点都宕了,就不必再报「该节点上某 Pod 挂了」
静默 Silence维护期误报发布、维护窗口内按匹配器临时屏蔽一批告警,过期自动恢复

路由配置示例:

route:
receiver: default # 兜底接收器
group_by: ['alertname', 'namespace']
group_wait: 30s # 新告警组等 30s 再发,凑齐同组告警
group_interval: 5m # 同组有新告警时的补发间隔
repeat_interval: 4h # 同一告警未恢复时的重复提醒间隔
routes:
- matchers:
- severity="critical"
receiver: oncall # 严重告警走值班渠道
continue: true # 同时继续匹配后面的路由
- matchers:
- team="infra"
receiver: infra-team # infra 团队的告警发给对应群

receivers:
- name: default
webhook_configs:
- url: http://alert-gateway:8080/alert
- name: oncall
webhook_configs:
- url: http://oncall-gateway:8080/critical
- name: infra-team
email_configs:
- to: infra@example.com

接收器(receiver)方面:原生支持 webhook、邮件、Slack、PagerDuty 等;钉钉、企业微信没有原生支持,一般通过 webhook 接一个转发机器人(如开源的 prometheus-webhook-dingtalk)来落地。

Grafana​

Grafana 是可观测性体系的可视化层:前面采集的指标、日志,最终都在这里看图、排查。

接入数据源​

Grafana 本身不存数据,靠**数据源(Data Source)**对接后端。装好后在 Connections → Data sources 里添加:

  • Prometheus:URL 填集群内地址,如 http://prometheus-server.monitoring(用 kube-prometheus-stack 装的话已自动配好)。
  • Loki:URL 填 http://loki.monitoring:3100,日志查询用。

添加后就可以在 Explore 里临时跑 PromQL / LogQL 查询,不建面板也能排查问题。

导入现成仪表盘​

社区在 grafana.com/dashboards 上贡献了海量现成面板,复制 Dashboard ID 就能导入(Dashboards → New → Import → 填 ID)。最经典的比如 1860(Node Exporter Full),单机的 CPU/内存/磁盘/网络一个面板全看清。

K8s 监控常用的几个:

Dashboard ID面板用途
1860Node Exporter Full节点级 CPU/内存/磁盘/网络全景
315Kubernetes cluster monitoring老牌集群概览,简洁直观
6417Kubernetes Cluster (Prometheus)命名空间/Pod 维度的资源用量
13332kube-state-metrics v2对象状态:副本、重启、Pending 一览
ID 对不上就按名字搜

第三方面板可能更新或下架,ID 失效时直接去 grafana.com/dashboards 按面板名搜索同名替代品。用 kube-prometheus-stack 的话,它自带的几十块官方面板(Kubernetes / Views 系列)会自动配好,更推荐先用它们。

Loki​

Loki 是 Grafana Labs 开源的日志系统,口号是「Like Prometheus, but for logs」。它为 K8s 日志场景量身设计:容器日志天然带命名空间、Pod 名、容器名这些元数据,Loki 就把这些元数据当作索引的主轴。

只索引 label,不索引全文​

这是 Loki 和 ELK 最本质的区别:

  • Elasticsearch:对日志正文建全文倒排索引,检索快,但索引体积大、写入开销高。
  • Loki:只给 label 建索引,日志正文按块(chunk)压缩存储,不建全文索引。写入快、存储省,代价是纯粹的关键词检索要靠扫数据块。

也就是说,Loki 的查询永远是「先用 label 圈定范围,再在范围内过滤」。

LogQL 基础查询​

LogQL 语法和 PromQL 一脉相承,一条查询由两部分组成:日志流选择器 + 过滤管道:

# 圈定范围:prod 命名空间、app 标签为 nginx 的全部日志
{namespace="prod", app="nginx"}

# 管道过滤:只要含 error 的行
{namespace="prod", app="nginx"} |= "error"

# 正则过滤:匹配 5xx 状态码
{namespace="prod", app="nginx"} |~ "status=5.."

# 统计:每分钟错误日志条数(指标查询)
sum(count_over_time({namespace="prod", app="nginx"} |= "error" [1m]))

采集端用官方 Alloy(或老牌的 Promtail)以 DaemonSet 跑在每个节点上,收集容器日志、按 K8s 元数据打 label 后推给 Loki,最后在 Grafana 里用 LogQL 查询。

控制住 label 基数

label 是索引,别把高基数的值做成 label(比如 user_id、request_id、trace_id)。每个不同的 label 组合都是一条独立日志流,基数爆炸会把索引撑爆、把查询拖垮。这类值应该留在日志正文里,用过滤管道查。

VictoriaLogs​

VictoriaLogs 是 VictoriaMetrics 团队出品的日志系统,定位是更省资源的 Loki 兼容替代品:

  • 兼容 Loki 的推送 API(采集端基本不用改),查询用自己的 LogsQL,同时提供 Loki 查询接口的基础兼容。
  • 同样负载下 CPU、内存、磁盘占用明显更低,官方基准里常比 Loki 省出数倍资源。
  • 默认对正文建全文索引,关键词搜索比 Loki 快,算是把「索引成本」和「检索速度」的取舍往回掰了一点。
  • 单二进制就能跑,小规模场景部署、运维都简单。

如果 Loki 的资源占用让你头疼,或者想要更快的关键词检索,VictoriaLogs 值得一试;Grafana 有对应的数据源插件,查询体验一致。

OpenTelemetry​

前面这些工具各管一摊:metrics 用 Prometheus、logs 用 Loki、traces 用 Jaeger……每根支柱一套 SDK、一个 agent,接入和维护都很割裂。OpenTelemetry(OTel) 要解决的正是这个割裂:用一套标准统一 traces / metrics / logs 三支柱的采集。

它提供三样东西:

  • 统一 SDK:各语言一套 API,应用埋点一次,三种信号都能产出。
  • OTLP 协议:遥测数据的统一传输协议,与后端解耦。
  • Collector:接收 → 处理 → 导出的数据管道,把遥测数据转发到任意后端(Prometheus、Loki、Jaeger、Tempo……),厂商中立。

Collector 的两种部署模式​

在 K8s 里,Collector(官方有 opentelemetry-operator 和 Helm chart)有两种典型部署形态,经常组合使用:

对比项Agent 模式Gateway 模式
部署方式DaemonSet,每个节点一个Deployment,集群级几个副本
职责采集本节点数据:容器日志文件、主机指标、本机 Pod 上报的 OTLP 数据汇总各 Agent 上报的数据,做跨节点处理(采样、批处理、鉴权),统一导出后端
好处靠近数据源,能直接读本机日志文件出口统一,后端凭据集中管理

OTel Collector 两级架构

新项目的默认选择

新建系统的遥测接入,直接按 OpenTelemetry 标准埋点和采集是目前社区的主流方向,避免被单一厂商的数据格式绑死。

Vector​

Vector 是 Datadog 开源的高性能数据采集管道,用 Rust 编写,主打快、省内存,一个工具同时收 logs 和 metrics。K8s 里一般以 DaemonSet 部署(官方有 Helm chart),是节点日志采集的新派选择。

三段式配置:sources / transforms / sinks​

Vector 的配置就是一条数据管道,由三种组件拼接:

  • sources:数据从哪进来(kubernetes_logs、文件、socket……)。
  • transforms:中间怎么处理(过滤、解析字段、改写、采样,用内置的 VRL 语言)。
  • sinks:处理完发到哪(Loki、Elasticsearch、Kafka、S3……)。

每个组件起个名字,用 inputs 把上游组件串起来,管道就成型了:

sources:
kubernetes_logs: # 采集本节点容器日志
type: kubernetes_logs

transforms:
only_prod: # 只保留 prod 命名空间的日志
type: filter
inputs: [kubernetes_logs]
condition: .kubernetes.pod_namespace == "prod"

sinks:
to_loki: # 发到 Loki,并打上标签
type: loki
inputs: [only_prod]
endpoint: http://loki.monitoring:3100
labels:
namespace: "{{ kubernetes.pod_namespace }}"
app: "{{ kubernetes.pod_labels.app }}"

数据流向一目了然:kubernetes_logs → only_prod → to_loki。

Filebeat​

Filebeat 是 Elastic 家的老牌日志采集器,ELK 链路的第一环:Filebeat →(Logstash)→ Elasticsearch → Kibana。

K8s 里的传统方案是:Filebeat 以 DaemonSet 部署,挂载宿主机的 /var/log/containers 目录(K8s 容器日志的落盘位置),用 autodiscover 感知节点上新起的 Pod,给每条日志自动附加命名空间、Pod 名等 K8s 元数据后输出:

filebeat.autodiscover:
providers:
- type: kubernetes
node: ${NODE_NAME}
hints.enabled: true

output.elasticsearch:
hosts: ["http://elasticsearch:9200"]

和 Vector 简单对比:Filebeat 胜在成熟稳定、和 Elastic 生态集成顺滑,是传统 ELK 栈的标配;Vector 更轻更快、后端不绑死,属于新一代通用采集管道——日志进 ES 用 Filebeat 顺手,想进 Loki/Kafka/S3 等多后端则 Vector 更灵活。

cAdvisor​

cAdvisor(Container Advisor)是 Google 开源的容器资源监控组件,但它不需要单独安装——已经内嵌在每个节点的 kubelet 里,是整条资源指标链路的源头:

  • 统计本节点上每个容器的 CPU、内存、网络、文件系统用量。
  • Metrics Server 拿到的数据,本质上就来自 kubelet(其背后是 cAdvisor)。
  • kubelet 在 /metrics/cadvisor 路径上把 cAdvisor 指标以 Prometheus 格式暴露出来。

让 Prometheus 抓取所有节点的 cAdvisor 指标,一个 job 就够(配合前面讲的 K8s 服务发现):

scrape_configs:
- job_name: cadvisor
kubernetes_sd_configs:
- role: node # 自动发现所有节点
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- target_label: __metrics_path__ # 默认抓 /metrics,改成 cAdvisor 路径
replacement: /metrics/cadvisor
生产环境用现成的

kube-prometheus-stack 默认已经配好 cAdvisor、kubelet、kube-state-metrics 的抓取任务和常用面板,一般不用手写这段配置。cAdvisor 指标基数很高,要自定义的话,记得用 metric_relabel_configs 丢弃用不上的指标系列。

Event​

除了指标和日志,K8s 还有第三样排障利器:Event(事件)。集群里发生的每件事——Pod 被调度到哪个节点、镜像拉取失败、探针检测不过、副本扩缩——都会生成一条 Event 记录,这是排障时的第一手信息。

Pod 起不来、状态一直 Pending、反复重启时,先看事件:

# 看全集群事件,按时间排序(最新的在最后)
kubectl get events -A --sort-by='.lastTimestamp'

# 只看异常事件
kubectl get events -A --field-selector type=Warning
NAMESPACE   LAST SEEN   TYPE      REASON      OBJECT                MESSAGE
default 5m Normal Scheduled pod/nginx-7c9d5-xxx Successfully assigned default/nginx-7c9d5-xxx to node-1
default 5m Warning Failed pod/nginx-7c9d5-xxx Failed to pull image "nginx:1.29-badtag": not found
default 4m Warning BackOff pod/nginx-7c9d5-xxx Back-off pulling image "nginx:1.29-badtag"

排查具体对象时更常用 kubectl describe——它的输出末尾自带该对象关联的 Events,不用单独查:

kubectl describe pod nginx-7c9d5-xxx -n default
事件默认只保留 1 小时

Event 存在 etcd 里,默认 TTL 只有 1 小时(由 API Server 的 --event-ttl 参数控制),过期就被清掉。所以出了问题要趁早看;需要长期留存做审计的话,得额外部署事件导出器(如 kubernetes-event-exporter)把事件转发到外部存储。