678 lines
24 KiB
Markdown
678 lines
24 KiB
Markdown
---
|
||
tags: [k8s, observability, logging, monitoring, devops]
|
||
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 — 执行命令探测
|
||
|
||
适用于:自定义健康检查逻辑、需要检查内部状态的场景(如数据库连接池)。
|
||
|
||
```yaml
|
||
livenessProbe:
|
||
exec:
|
||
command:
|
||
- /bin/sh
|
||
- -c
|
||
- "pg_isready -U postgres"
|
||
initialDelaySeconds: 15
|
||
periodSeconds: 10
|
||
timeoutSeconds: 5
|
||
failureThreshold: 3
|
||
```
|
||
|
||
容器内执行指定命令,退出码为 0 表示健康,非 0 表示不健康。适用于需要检查进程内部状态(如自定义脚本检测连接池)的场景。
|
||
|
||
#### httpGet — HTTP 请求探测
|
||
|
||
适用于:Web 服务、API 服务,最常用的探测方式。
|
||
|
||
```yaml
|
||
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 服务端口)。
|
||
|
||
```yaml
|
||
readinessProbe:
|
||
tcpSocket:
|
||
port: 3306
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
timeoutSeconds: 3
|
||
failureThreshold: 3
|
||
```
|
||
|
||
尝试建立 TCP 连接,连接成功即视为健康。适合 MySQL、Redis 等数据库服务,或尚不支持 HTTP 健康检查端点的遗留服务。
|
||
|
||
#### gRPC — gRPC 健康检查探测
|
||
|
||
适用于:gRPC 微服务(K8s 1.24+ GA)。
|
||
|
||
```yaml
|
||
readinessProbe:
|
||
grpc:
|
||
port: 50051
|
||
# 可选:指定服务名
|
||
# service: "my-grpc-service"
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 10
|
||
timeoutSeconds: 5
|
||
failureThreshold: 3
|
||
```
|
||
|
||
利用 gRPC 标准的 [Health Checking Protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) 进行探测,比 httpGet 更自然地适配 gRPC 服务。需要应用实现 `grpc.health.v1.Health` 服务。
|
||
|
||
### 探针设计原则
|
||
|
||
> [!tip] 原则一:慢启动应用必须用 startupProbe
|
||
> Java 应用、机器学习模型加载、大型缓存预热等场景,启动时间可能超过 livenessProbe 的 `initialDelaySeconds`。此时应该用 startupProbe 兜底,而不是无限增大 `initialDelaySeconds`——后者会导致所有重启都白白等待固定时长。
|
||
|
||
```yaml
|
||
# 典型的慢启动应用探针配置
|
||
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。
|
||
|
||
#### 探针决策流程图
|
||
|
||
```mermaid
|
||
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 中,日志的生命周期跨越三个层级:容器层、节点层、集群层。理解这个链路是做好日志管理的前提。
|
||
|
||
```mermaid
|
||
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` 中配置:
|
||
|
||
```yaml
|
||
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 架构
|
||
|
||
```mermaid
|
||
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 架构
|
||
|
||
```mermaid
|
||
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)
|
||
|
||
```go
|
||
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"),
|
||
)
|
||
```
|
||
|
||
输出:
|
||
|
||
```json
|
||
{"level":"info","ts":1720340880.123,"msg":"request processed","method":"GET","path":"/api/v1/users","status":200,"latency":0.15,"request_id":"abc-123"}
|
||
```
|
||
|
||
#### 结构化日志最佳实践
|
||
|
||
1. **统一字段命名**:团队内约定日志字段名(如 `request_id` 而非 `reqId` / `rid`),便于跨服务聚合。
|
||
2. **必须包含的字段**:时间戳(`ts`)、日志级别(`level`)、消息(`msg`)、请求 ID(`request_id`)、服务名(`service`)。
|
||
3. **避免在日志中输出敏感信息**:密码、Token、身份证号等必须脱敏。
|
||
4. **日志级别控制**:生产环境默认 `info`,可通过环境变量动态调整为 `debug` 以便排查问题。
|
||
|
||
> [!tip] kubectl 查看结构化日志
|
||
> 使用 `kubectl logs` 结合 `jq` 可以快速过滤 JSON 日志:
|
||
> ```bash
|
||
> 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(基于资源指标)的基础设施。
|
||
|
||
#### 工作原理
|
||
|
||
```mermaid
|
||
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`)暴露。
|
||
|
||
#### 安装
|
||
|
||
```bash
|
||
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
|
||
```
|
||
|
||
验证:
|
||
|
||
```bash
|
||
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 作为可视化层,提供丰富的仪表盘。
|
||
|
||
#### 架构概览
|
||
|
||
```mermaid
|
||
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 示例
|
||
|
||
```yaml
|
||
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)、队列长度、延迟等自定义指标更有意义。
|
||
|
||
#### 前置条件
|
||
|
||
1. 安装 Prometheus Adapter(将 Prometheus 指标转换为 K8s Custom Metrics API):
|
||
```bash
|
||
helm install prometheus-adapter prometheus-community/prometheus-adapter \
|
||
--namespace monitoring \
|
||
--set prometheus.url=http://prometheus-server.monitoring.svc
|
||
```
|
||
|
||
2. 确认 Custom Metrics API 可用:
|
||
```bash
|
||
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
|
||
```
|
||
|
||
#### HPA 配置示例(基于 HTTP 请求速率)
|
||
|
||
```yaml
|
||
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 是集群中发生的重大状态变更记录,是排查问题的第一手信息。
|
||
|
||
### 查看事件
|
||
|
||
```bash
|
||
# 当前 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` 看不到相关事件。
|
||
|
||
解决方法:
|
||
|
||
1. **调整 TTL**(K8s 1.19+):
|
||
```bash
|
||
# 在 kube-apiserver 中设置
|
||
--event-ttl=24h
|
||
```
|
||
|
||
2. **持久化到日志系统**:将 Events 采集到 Elasticsearch/Loki 等系统中长期保存。
|
||
- 使用 kube-state-metrics 的 `kube_event_*` 指标采集到 Prometheus。
|
||
- 使用专门的 Events 采集工具(如 `event-exporter`)推送到日志系统。
|
||
|
||
3. **event-exporter 示例**:
|
||
```yaml
|
||
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)策略,自动过期删除旧索引。
|
||
|
||
```yaml
|
||
# 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` 丢弃不需要的标签。
|
||
|
||
```yaml
|
||
# 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 通信失败。
|
||
|
||
**解决方案**:
|
||
|
||
```bash
|
||
# 检查 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)指标 |
|
||
| 事件 | 配置持久化、重要事件告警 |
|
||
|
||
---
|
||
|
||
## 延伸阅读
|
||
|
||
- [[k8s-03-pod]] — Pod 生命周期、调度与资源管理
|
||
- [[k8s-02-architecture]] — K8s 控制平面与节点组件架构
|
||
- [Kubernetes 官方文档 - Logging Architecture](https://kubernetes.io/docs/concepts/cluster-administration/logging/)
|
||
- [Kubernetes 官方文档 - Configure Liveness, Readiness and Startup Probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)
|
||
- [Prometheus Operator 文档](https://prometheus-operator.dev/)
|
||
- [Grafana Loki 官方文档](https://grafana.com/docs/loki/latest/)
|
||
- [Google SRE Book - Monitoring Distributed Systems](https://sre.google/sre-book/practical-alerting/)
|