24 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-07-07 15:48 |
Kubernetes 可观测性 — 日志、监控与健康检查
概述
可观测性(Observability)是理解系统内部状态的能力,在 Kubernetes 中通常由三大支柱构成:Logging(日志)、Metrics(指标) 和 Tracing(链路追踪)。日志告诉你「发生了什么」,指标告诉你「系统状态如何」,追踪告诉你「请求经过了哪里」。三者相互补充,缺一不可。在 K8s 环境中,由于 Pod 生命周期短暂、服务动态伸缩,传统的 SSH 进机器查日志方式不再适用,必须依赖系统化的可观测性方案。本文将从健康检查探针入手,逐步覆盖日志采集、指标监控和事件管理的完整实践。
健康检查(Probe)深入
Kubernetes 通过探针(Probe)机制持续监测容器的健康状态,自动完成故障隔离与自愈。这是 K8s 可观测性的第一道防线——不需要外部工具,原生能力就能实现基本的服务可用性保障。
三种探针详解
K8s 提供三种探针,各有分工:
| 探针 | 作用 | 失败后果 | 典型场景 |
|---|---|---|---|
| livenessProbe | 检测容器是否存活 | 杀死容器并重启(遵循 restartPolicy) | 应用死锁、无限循环 |
| readinessProbe | 检测容器是否就绪接收流量 | 从 Service 的 Endpoints 中摘除 | 依赖未就绪、预热加载中 |
| startupProbe | 检测容器是否完成启动 | 杀死容器并重启 | 慢启动应用(如 Java、大型模型加载) |
配置参数说明
所有探针共享以下参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
initialDelaySeconds |
int | 0 | 容器启动后多久开始第一次探测 |
periodSeconds |
int | 10 | 探测间隔(秒) |
timeoutSeconds |
int | 1 | 探测超时时间(秒) |
failureThreshold |
int | 3 | 连续失败多少次后判定为不健康 |
successThreshold |
int | 1 | 连续成功多少次后恢复(liveness 固定为 1) |
[!tip] startupProbe 与 livenessProbe 的协作 当
startupProbe配置后,livenessProbe和readinessProbe会在 startupProbe 成功之后才生效。这意味着你可以用一个宽松的 startupProbe(比如failureThreshold: 30、periodSeconds: 10,即允许最多 300 秒启动时间),而 livenessProbe 保持较严格的配置,避免慢启动应用被误杀。
探测方式
K8s 支持四种探测机制,选择哪种取决于应用特性和暴露的端口。
exec — 执行命令探测
适用于:自定义健康检查逻辑、需要检查内部状态的场景(如数据库连接池)。
livenessProbe:
exec:
command:
- /bin/sh
- -c
- "pg_isready -U postgres"
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
容器内执行指定命令,退出码为 0 表示健康,非 0 表示不健康。适用于需要检查进程内部状态(如自定义脚本检测连接池)的场景。
httpGet — HTTP 请求探测
适用于:Web 服务、API 服务,最常用的探测方式。
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Custom-Header
value: "k8s-probe"
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
向容器的指定端口和路径发起 HTTP GET 请求,2xx/3xx 状态码视为健康。建议为健康检查设计轻量级的专用端点,避免在 / 上做复杂业务校验。
tcpSocket — TCP 端口探测
适用于:非 HTTP 协议的 TCP 服务(如数据库、Redis、gRPC 服务端口)。
readinessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
尝试建立 TCP 连接,连接成功即视为健康。适合 MySQL、Redis 等数据库服务,或尚不支持 HTTP 健康检查端点的遗留服务。
gRPC — gRPC 健康检查探测
适用于:gRPC 微服务(K8s 1.24+ GA)。
readinessProbe:
grpc:
port: 50051
# 可选:指定服务名
# service: "my-grpc-service"
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
利用 gRPC 标准的 Health Checking Protocol 进行探测,比 httpGet 更自然地适配 gRPC 服务。需要应用实现 grpc.health.v1.Health 服务。
探针设计原则
[!tip] 原则一:慢启动应用必须用 startupProbe Java 应用、机器学习模型加载、大型缓存预热等场景,启动时间可能超过 livenessProbe 的
initialDelaySeconds。此时应该用 startupProbe 兜底,而不是无限增大initialDelaySeconds——后者会导致所有重启都白白等待固定时长。
# 典型的慢启动应用探针配置
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 允许最多 30 次失败
periodSeconds: 10 # 每 10 秒检查一次
# 总共允许 30 × 10 = 300 秒启动时间
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3 # 启动后 3 次失败即重启
[!tip] 原则二:readiness ≠ liveness,职责不能混
- readinessProbe:「我准备好接收请求了吗?」——可以是暂时性的(如依赖 Redis 还没连上),摘除流量后恢复即可重新加入。
- livenessProbe:「我还活着吗?」——失败意味着进程级故障(死锁、崩溃),需要重启。
- 常见错误:把依赖检查(如数据库连接)放在 livenessProbe 里。数据库短暂不可达会导致 Pod 被重启,反而加重问题。
[!tip] 原则三:健康端点要轻量 livenessProbe 的
/healthz端点不应该检查下游依赖(数据库、Redis 等),否则下游抖动会导致 Pod 被误杀。readinessProbe 可以做适度的依赖检查,但也要控制超时。
[!warning] 避免探针与应用抢占资源 exec 探针会 fork 子进程,在资源受限的容器中可能加剧 OOM。高并发场景下优先使用 httpGet 或 tcpSocket。
探针决策流程图
flowchart TD
A[应用需要健康检查] --> B{启动时间 > 30 秒?}
B -->|是| C[配置 startupProbe]
B -->|否| D{需要检查依赖?}
C --> D
D -->|是, 如 DB/Redis| E[readinessProbe 检查依赖]
D -->|否| F[readinessProbe 基础端口/路径]
E --> G{livenessProbe 如何配置?}
F --> G
G --> H[仅检查进程存活<br>不检查下游依赖]
H --> I{协议类型?}
I -->|HTTP| J[httpGet /healthz]
I -->|gRPC| K[grpc health check]
I -->|TCP/数据库| L[tcpSocket port]
I -->|自定义逻辑| M[exec command]
日志管理
K8s 日志架构
在 Kubernetes 中,日志的生命周期跨越三个层级:容器层、节点层、集群层。理解这个链路是做好日志管理的前提。
flowchart LR
subgraph 容器层
A[应用进程] -->|stdout / stderr| B[容器运行时<br>CRI]
end
subgraph 节点层
B -->|写入文件| C["/var/log/pods/"]
C --> D[kubelet 日志轮转]
end
subgraph 集群层
D --> E[日志采集 Agent<br>Fluentd / Promtail / Filebeat]
E --> F[存储后端<br>Elasticsearch / Loki / S3]
F --> G[查询 UI<br>Kibana / Grafana]
end
A -.->|Sidecar 模式| H[日志 Sidecar 容器]
H --> F
K8s 推荐的最佳实践是:应用将日志输出到 stdout/stderr,由 kubelet 负责写入节点磁盘,再由集群级采集工具统一收集。这样做解耦了应用和日志基础设施,应用无需关心日志的存储和传输。
节点级日志
目录结构
kubelet 将每个容器的 stdout/stderr 写入节点上的日志文件,路径格式为:
/var/log/pods/<namespace>_<pod-name>_<pod-uid>/<container-name>/<restart-count>.log
实际示例:
/var/log/pods/default_nginx-7d8b49557c-abc123_5d1f3a2e-1234-5678-abcd-ef0123456789/
├── nginx/
│ ├── 0.log # 第 0 次启动的日志
│ └── 1.log # 第 1 次重启后的日志
└── nginx/
└── 0.log -> /var/log/containers/nginx-xxx.log # 符号链接
/var/log/containers/ 目录下的文件是指向 /var/log/pods/ 的符号链接,文件名包含容器名和容器 ID,方便按容器名查找。
日志轮转(Log Rotation)
kubelet 通过以下参数控制日志轮转(配置在 kubelet 的 --container-log-* 参数中):
| 参数 | 说明 | 默认值 |
|---|---|---|
--container-log-max-size |
单个日志文件最大大小 | 10Mi |
--container-log-max-files |
每个容器保留的日志文件数 | 5 |
在 KubeletConfiguration 中配置:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
containerLogMaxSize: "50Mi"
containerLogMaxFiles: 10
[!warning] 节点磁盘空间 日志量大的应用(如高并发网关)可能快速占满磁盘。务必设置合理的轮转策略,并配合监控节点磁盘使用率(
node_filesystem_avail_bytes指标)。
集群级方案
方案对比
| 维度 | EFK(Elasticsearch + Fluentd + Kibana) | Loki + Promtail + Grafana |
|---|---|---|
| 存储模型 | 全文索引,存储成本高 | 标签索引 + 压缩块存储,成本低 |
| 查询语言 | KQL / Lucene | LogQL(类 PromQL 语法) |
| 全文搜索 | 原生支持,性能好 | 不支持原生全文搜索,需配合标签 |
| 资源消耗 | 高(JVM 内存、磁盘 IO) | 低(Go 二进制,无全文索引) |
| 与 Metrics 集成 | 需额外对接 Prometheus | 天然与 Prometheus/Grafana 生态融合 |
| 适用场景 | 大规模日志分析、全文检索需求强 | 资源敏感、与 Prometheus 混合部署 |
| 社区趋势 | 成熟但逐渐被替代 | CNCF 毕业项目,增长迅速 |
EFK 架构
flowchart LR
A[Pod stdout/stderr] --> B[节点日志文件]
B --> C[Fluentd DaemonSet]
C --> D[Elasticsearch]
D --> E[Kibana]
E --> F[用户查询]
Fluentd 以 DaemonSet 形式部署在每个节点上,tail 节点日志文件并推送到 Elasticsearch。
Loki + Promtail 架构
flowchart LR
A[Pod stdout/stderr] --> B[节点日志文件]
B --> C[Promtail DaemonSet]
C --> D[Loki]
D --> E[Grafana]
E --> F[用户查询]
Promtail 同样以 DaemonSet 部署,但它只提取标签(namespace、pod、container)和日志行,发送给 Loki 进行存储。Loki 的核心设计理念是「like Prometheus, but for logs」——只对标签建索引,日志内容以压缩块存储。
结构化日志
非结构化日志(纯文本)在大规模集群中难以高效查询。结构化日志以 JSON 格式输出,便于日志系统自动解析和过滤。
JSON 日志输出示例(Go / zap)
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("request processed",
zap.String("method", "GET"),
zap.String("path", "/api/v1/users"),
zap.Int("status", 200),
zap.Duration("latency", 150*time.Millisecond),
zap.String("request_id", "abc-123"),
)
输出:
{"level":"info","ts":1720340880.123,"msg":"request processed","method":"GET","path":"/api/v1/users","status":200,"latency":0.15,"request_id":"abc-123"}
结构化日志最佳实践
- 统一字段命名:团队内约定日志字段名(如
request_id而非reqId/rid),便于跨服务聚合。 - 必须包含的字段:时间戳(
ts)、日志级别(level)、消息(msg)、请求 ID(request_id)、服务名(service)。 - 避免在日志中输出敏感信息:密码、Token、身份证号等必须脱敏。
- 日志级别控制:生产环境默认
info,可通过环境变量动态调整为debug以便排查问题。
[!tip] kubectl 查看结构化日志 使用
kubectl logs结合jq可以快速过滤 JSON 日志:kubectl logs my-pod -c my-container | jq 'select(.level == "error")' kubectl logs my-pod -c my-container | jq 'select(.latency > 0.5)'
指标监控
Metrics Server
Metrics Server 是 Kubernetes 内置的轻量级指标采集组件,提供 CPU 和内存使用数据,是 kubectl top 和 HPA(基于资源指标)的基础设施。
工作原理
flowchart LR
A[kubelet] -->|Summary API| B[Metrics Server]
B -->|Metrics API| C[kubectl top]
B -->|Metrics API| D[HPA Controller]
- 每个 kubelet 暴露 Summary API(
/stats/summary),包含该节点上所有 Pod 的 CPU/内存使用数据。 - Metrics Server 定期(默认 15 秒)从所有 kubelet 拉取数据,聚合后通过 Metrics API(
metrics.k8s.io)暴露。
安装
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
验证:
kubectl top nodes
kubectl top pods -n default
局限性
- 只提供 CPU 和内存指标,不支持磁盘、网络等。
- 数据只保留短期窗口(约 1 分钟),不支持历史查询。
- 不支持自定义指标——需要 Prometheus 等外部系统。
[!info] Metrics Server vs Prometheus Metrics Server 是 K8s 内置的「最小可用」指标方案,适合
kubectl top和基于资源的 HPA。如果你需要更丰富的指标、历史数据和告警,必须部署 Prometheus。
Prometheus + Grafana
Prometheus 是 CNCF 毕业项目,是 Kubernetes 生态中最主流的监控方案。Grafana 作为可视化层,提供丰富的仪表盘。
架构概览
flowchart LR
subgraph K8s 集群
A[应用 /export_metrics] -->|pull| B[Prometheus Server]
C[kube-state-metrics] -->|pull| B
D[Node Exporter] -->|pull| B
E[ServiceMonitor CRD] -.->|配置发现| B
end
B --> F[Grafana]
B --> G[Alertmanager]
G --> H[邮件 / Slack / 钉钉]
B --> I[长期存储<br>Thanos / VictoriaMetrics]
核心组件:
- Prometheus Server:时序数据库 + 采集引擎,定期从 targets 拉取(pull)指标。
- kube-state-metrics:将 K8s 对象状态(Pod、Deployment、Node 等)转换为 Prometheus 指标。
- Node Exporter:采集节点级系统指标(CPU、内存、磁盘、网络)。
- ServiceMonitor CRD:由 Prometheus Operator 提供,以声明式方式配置采集目标。
- Alertmanager:告警路由、去重、分组、静默。
ServiceMonitor 示例
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app
namespace: monitoring
labels:
release: prometheus # 匹配 Prometheus 实例的标签选择器
spec:
namespaceSelector:
matchNames:
- default
selector:
matchLabels:
app: my-app # 匹配目标 Service 的标签
endpoints:
- port: metrics # Service 中定义的端口名
path: /metrics
interval: 15s
[!info] ServiceMonitor 的工作方式 Prometheus Operator 会 watch ServiceMonitor 资源的变化,自动更新 Prometheus 的采集配置。运维人员不再需要手写
prometheus.yml的 scrape 配置,实现了采集配置的 GitOps 管理。
常见 K8s 监控指标
| 指标名称 | 含义 | 告警建议 |
|---|---|---|
container_cpu_usage_seconds_total |
容器累计 CPU 使用时间 | 持续 > 80% requests 告警 |
container_memory_working_set_bytes |
容器内存工作集 | 接近 limits 时告警 |
kube_pod_restart_total |
Pod 重启次数 | 短时间内重启 > 3 次告警 |
kube_pod_status_phase |
Pod 状态(Pending/Running/Failed) | Pending > 5 分钟告警 |
kube_node_status_condition |
节点状态(Ready/NotReady) | NotReady 告警 |
kube_deployment_status_replicas_available |
Deployment 可用副本数 | 小于期望副本数告警 |
node_filesystem_avail_bytes |
节点可用磁盘空间 | < 10% 剩余告警 |
HPA 自定义指标
HPA 默认支持基于 CPU/Memory 的扩缩容,但实际场景中,基于请求速率(QPS)、队列长度、延迟等自定义指标更有意义。
前置条件
-
安装 Prometheus Adapter(将 Prometheus 指标转换为 K8s Custom Metrics API):
helm install prometheus-adapter prometheus-community/prometheus-adapter \ --namespace monitoring \ --set prometheus.url=http://prometheus-server.monitoring.svc -
确认 Custom Metrics API 可用:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
HPA 配置示例(基于 HTTP 请求速率)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 20
metrics:
# 自定义指标:每 Pod 每秒请求数
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100" # 每个 Pod 目标 100 QPS
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却 5 分钟,避免抖动
policies:
- type: Percent
value: 25
periodSeconds: 60
[!tip] 扩缩容行为配置
behavior字段(K8s 1.18+)允许精细控制扩缩容速度。建议扩容激进(快速应对流量高峰)、缩容保守(stabilizationWindowSeconds设大,避免频繁抖动导致连接中断)。
事件(Events)
Kubernetes Events 是集群中发生的重大状态变更记录,是排查问题的第一手信息。
查看事件
# 当前 namespace 的事件,按时间排序
kubectl get events --sort-by='.lastTimestamp'
# 所有 namespace 的事件
kubectl get events -A
# 只看 Warning 级别事件
kubectl get events --field-selector type=Warning
# 查看特定 Pod 的事件
kubectl describe pod my-pod
常见事件类型
| Reason | 类型 | 含义 |
|---|---|---|
Scheduled |
Normal | Pod 成功调度到节点 |
Pulling / Pulled |
Normal | 正在拉取 / 已拉取镜像 |
Created / Started |
Normal | 容器已创建 / 已启动 |
Unhealthy |
Warning | 健康检查失败 |
BackOff |
Warning | 重启退避(CrashLoopBackOff) |
FailedScheduling |
Warning | 调度失败(资源不足、亲和性冲突等) |
OOMKilling |
Warning | 容器因内存不足被杀死 |
Evicted |
Warning | Pod 被驱逐(磁盘压力、内存压力等) |
事件 TTL 与持久化
[!warning] Events 会被自动清理 默认情况下,Events 的 TTL(Time To Live)为 1 小时。这意味着如果一个 Pod 在 2 小时前 OOMKilled,你通过
kubectl describe pod看不到相关事件。
解决方法:
-
调整 TTL(K8s 1.19+):
# 在 kube-apiserver 中设置 --event-ttl=24h -
持久化到日志系统:将 Events 采集到 Elasticsearch/Loki 等系统中长期保存。
- 使用 kube-state-metrics 的
kube_event_*指标采集到 Prometheus。 - 使用专门的 Events 采集工具(如
event-exporter)推送到日志系统。
- 使用 kube-state-metrics 的
-
event-exporter 示例:
apiVersion: apps/v1 kind: Deployment metadata: name: event-exporter namespace: monitoring spec: template: spec: containers: - name: event-exporter image: ghcr.io/resmoio/kubernetes-event-exporter:latest args: - -conf=/data/config.yaml volumeMounts: - name: config mountPath: /data volumes: - name: config configMap: name: event-exporter-config
常见陷阱与最佳实践
陷阱一:日志量爆炸
问题:高并发服务的 debug 日志未关闭,一天产生几十 GB 日志,磁盘告警或 Elasticsearch 索引膨胀。
解决方案:
- 生产环境默认
info级别,通过环境变量或配置中心动态调整。 - 使用 Fluentd/Promtail 的 filter 功能丢弃无用日志(如 kube-system 的健康检查日志)。
- 设置 Elasticsearch 的 ILM(Index Lifecycle Management)策略,自动过期删除旧索引。
# Fluentd filter 示例:丢弃 kube-system 命名空间的日志
<filter kubernetes.**>
@type grep
<exclude>
key $.kubernetes.namespace_name
pattern /^kube-system$/
</exclude>
</filter>
陷阱二:探针超时与应用启动时间不匹配
问题:Java 应用启动需要 60 秒,但 livenessProbe 的 initialDelaySeconds 只设了 30 秒,导致每次重启都进入 CrashLoopBackOff。
解决方案:使用 startupProbe 代替盲目增大 initialDelaySeconds(详见探针设计原则一)。
陷阱三:监控指标标签爆炸
问题:在 Prometheus 指标中使用 user_id、request_path 等高基数(high cardinality)标签,导致时间序列数暴涨,Prometheus 内存 OOM。
解决方案:
- 高基数字段(如 request_id、user_id)放到日志中,不作为指标标签。
request_path应归一化(如/api/v1/users/123→/api/v1/users/:id)。- 使用 Prometheus 的
metric_relabel_configs丢弃不需要的标签。
# Prometheus 配置:限制标签基数
metric_relabel_configs:
- source_labels: [__name__]
regex: "container_.*"
action: keep
- regex: "pod_name"
action: labeldrop
陷阱四:readinessProbe 失败但 livenessProbe 正常
问题:readinessProbe 依赖 Redis,Redis 短暂不可达,Pod 被从 Endpoints 摘除。但 livenessProbe 正常,Pod 不会被重启。流量被摘除后 Pod 中积压的请求超时,用户报错。
解决方案:
- readinessProbe 的依赖检查要设置合理的超时(如 2 秒内无响应即判定失败)。
- 在应用内部实现熔断机制,不要完全依赖 K8s 探针。
- 确保 readinessProbe 恢复时,Pod 能快速重新加入 Endpoints(
successThreshold: 1)。
陷阱五:kubectl top 数据不准
问题:kubectl top pods 显示的 CPU 使用率为 0 或与实际不符。
原因:
- Metrics Server 启动后需要约 1-2 分钟才能采集到数据。
- 容器的 CPU request 未设置,百分比无法计算。
- Metrics Server 的
--kubelet-insecure-tls未配置导致与 kubelet 通信失败。
解决方案:
# 检查 Metrics Server 是否正常运行
kubectl get deployment metrics-server -n kube-system
# 检查是否有错误日志
kubectl logs -n kube-system deployment/metrics-server
# 使用 --resource 选项查看原始数值而非百分比
kubectl top pods --containers
最佳实践汇总
| 领域 | 实践 |
|---|---|
| 日志 | stdout 输出、JSON 结构化、统一字段命名、合理轮转 |
| 探针 | startupProbe 兜底慢启动、liveness 不查依赖、readiness 做适度依赖检查 |
| 监控 | Prometheus + Grafana 标准栈、ServiceMonitor 声明式配置 |
| 告警 | 分级告警(Warning/Critical)、去重分组、值班轮转 |
| 指标 | 避免高基数标签、区分 RED(Rate/Error/Duration)指标与 USE(Utilization/Saturation/Errors)指标 |
| 事件 | 配置持久化、重要事件告警 |