Files
autumn-recruitment/05.架构/ci-cd/K8s 发布与可观测性体系.md
T

259 lines
11 KiB
Markdown
Raw Normal View History

2026-08-08 19:01:04 +08:00
---
tags: [arch/cicd, kubernetes, rolling-update, canary-deployment, hpa, vpa, prometheus, grafana]
create time: 2026-08-08 18:00
update time: 2026-08-08 18:00
---
# K8s 发布与可观测性体系
## 概述
Kubernetes 是现代 CI/CD 体系的编排核心,提供从滚动发布、灰度策略到自动扩缩容的完整能力链。配合 Prometheus + Grafana + 日志采集组件,构成生产级别的可观测性基础设施。本文系统梳理这些组件的设计原理与协作方式。
## 核心原理
### 整体架构概览
```mermaid
graph TD
subgraph UserTraffic["用户流量"]
U[用户请求] --> INGRESS[Nginx Ingress Controller]
end
subgraph K8sCluster["K8s 集群"]
SUBNAME["Service Name: user-service"] --> EP[Endpoint: pod IPs]
EP --> P1[Pod A: v2.3.1]
EP --> P2[Pod B: v2.3.1]
EP --> P3[Pod C: v2.4.0-new]
DEPLOY[Deployment] -->|管理| P1
DEPLOY -->|管理| P2
DEPLOY -->|管理| P3
HPA[Horizontal Pod Autoscaler] -->|扩缩容指令| DEPLOY
sub kubeSystem["kube-system 组件"]
APISERVER[API Server]
ETCD[(etcd)]
CONTROLLER[M-controller Manager]
SCHEDULER[Scheduler]
end
P1 -->|心跳 / metrics| PROMETHEUS[Prometheus TSDB]
P2 -->|心跳 / metrics| PROMETHEUS
P3 -->|心跳 / metrics| PROMETHEUS
P1 -.->Fluentd --> ES[ES / Loki]
P2 -.->Fluentd --> ES
P3 -.->Fluentd --> ES
end
sub GrafanaUI["可视化层"]
GRAFANA[Grafana Dashboard] -->|查询| PROMETHEUS
GRAFANA -->|告警规则| ALERTMANAGER[Alertmanager]
end
INGRESS --> SUBNAME
```
### RollingUpdate 策略详解
K8s Deployment 默认使用 `RollingUpdate` 策略,通过两个参数控制滚动速度:
| 参数 | 默认值 | 含义 | 影响 |
|------|-------|------|------|
| maxSurge | 25% | 更新过程中允许超出目标副本数的最大数量 | 控制新 Pod 先启动的数量 |
| maxUnavailable | 25% | 更新过程中允许不可用的最大副本数 | 控制旧 Pod 被终止的速度 |
**两种典型配置对比:**
| 配置 | 行为 | 适用场景 |
|------|-----|---------|
| maxSurge=1, maxUnavailable=0 | 逐台升级,零停机 | 对可用性要求极高的生产环境 |
| maxSurge=25%, maxUnavailable=25% | 批量快速滚动 | 测试环境或无需保持全量的场景 |
以 3 副本升级为 4 副本为例(maxSurge=1, maxUnavailable=0):
```mermaid
gantt
title RollingUpdate 过程 (3 -> 4 pods, surge=1)
dateFormat YYYY-MM-DD
axisFormat %H:%M
section Pod Group
old-1 :done, 1, 2026-08-08T00:00, 2026-08-08T00:05
old-2 :done, 1, 2026-08-08T00:00, 2026-08-08T00:05
old-3 :done, 1, 2026-08-08T00:00, 2026-08-08T00:05
new-1 (surge +1) :active, 2026-08-08T00:01, 2026-08-08T00:05
old-3 terminate :crit, 2026-08-08T00:06, 2026-08-08T00:07
new-2 (surge +1) :active, 2026-08-08T00:07, 2026-08-08T00:11
old-2 terminate :crit, 2026-08-08T00:12, 2026-08-08T00:13
new-3 (surge +1) :active, 2026-08-08T00:13, 2026-08-08T00:17
old-1 terminate :crit, 2026-08-08T00:18, 2026-08-08T00:19
new-4 :active, 2026-08-08T00:19, 2026-08-08T00:23
```
**关键约束:就绪探针(Readiness Probe)**
- 新 Pod 必须通过 Readiness Probe 才会被加入 Service 的 Endpoint 列表。
- 如果 Readiness 失败,即使新 Pod 已运行也不会接收流量。
- livenessProbe 用于检测进程是否存活并可能触发重启;readinessProbe 用于决定"这个 Pod 是否可以接受流量"。
### 蓝绿部署 vs Canary 发布
| 维度 | RollingUpdate | 蓝绿部署 | Canary 发布 |
|------|-------------|---------|------------|
| 资源消耗 | 低(增量替换) | 高(需要双倍实例) | 中(少量 Canary 实例) |
| 回滚速度 | 慢(需要滚动回去) | 秒级(切换流量即可) | 快(减少 Canary 流量比例) |
| 风险 | 低(渐进式) | 中高(全部切到新版本后才发现问题) | 最低(小部分用户先体验) |
| 复杂度 | 低(K8s 原生支持) | 中(需要外部 LB 管理) | 高(需要 Istio 或 Nginx 权重配置) |
| 适用团队 | 所有规模 | 大团队,有独立预发环境 | 有成熟 DevOps 能力的团队 |
Canary 发布的流量权重示例(基于 Istio VirtualService):
```yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service-canary
spec:
hosts: ["user-service.default.svc.cluster.local"]
http:
- route:
- destination:
host: user-service
subset: v1
weight: 90 # 90% 流量走稳定版
- destination:
host: user-service
subset: v2
weight: 10 # 10% 流量进 Canary
```
### HPA — 水平自动扩缩容
HPA 基于指标监控动态调整 Pod 副本数:
```go
// HPA 核心指标类型
type MetricSourceType string
const (
// CPU 利用率(容器内统计)
ContainerCPU MetricSourceType = "ContainerResource"
// 内存利用率
ContainerMemory MetricSourceType = "ContainerResource"
// Prometheus 自定义指标(外部指标)
External MetricSourceType = "External"
// Pods 级别指标(如并发连接数)
Pods MetricSourceType = "Pods"
)
```
HPA 的计算公式:`targetReplicas = ceil(currentReplicas * (currentMetricValue / desiredMetricValue))`
例如 CPU 目标 70%,当前平均 CPU 使用率 91%,当前 5 个副本:
`ceil(5 * 91 / 70) = ceil(6.5) = 7` → 扩容到 7 个副本。
> [!WARNING]
> HPA 不是实时的——它依赖 metrics-server 每 15~60 秒的采集间隔,扩容动作有分钟级延迟。对于需要即时响应的秒杀场景,应该提前设置 minReplicas >= 预期峰值的 80%。
### VPA — 垂直自动扩缩容
VPA 自动调整单个 Pod 的 CPU/Memory requests 和 limits:
| 模式 | 行为 | 说明 |
|------|-----|------|
| Offline | 只推荐不应用 | 人工审核后手动调整 |
| Initial | 在 Pod 创建时设置建议值 | 对新 Pod 生效 |
| Auto | 直接在运行时修改 Pod Spec | 会导致 Pod 被杀掉重建 |
| Recreate | 类似 Auto 但先删除旧的再创建 | 保证平滑过渡 |
VPA 的典型场景:微服务刚上线阶段,运维难以精确预估资源需求,可以让 VPA 学习实际使用模式后给出最优建议。
### Prometheus 指标采集链路
```mermaid
flowchart LR
CLIENT_LIB["Client Library<br/>Go SDK / Java SDK / Python SDK"] -->|暴露 /metrics HTTP endpoint| APP_POD["App Pod"]
APP_POD -->|HTTP GET /metrics| SCRAPE["Prometheus Scrape"]
SCRAPE -->|写入| TSDB["TSDB: Thanos / VictoriaMetrics"]
TSDB --> QUERY["PromQL Query Engine"]
QUERY --> ALERT["Alert Rule Evaluation"]
ALERT -->|告警触发| AM["Alertmanager"]
AM -->|Webhook / 邮件 / 钉钉| NOTIFICATION["通知通道"]
```
**核心概念:**
- **Counter**:单调递增计数器(如总请求数),适合画 Rate 曲线。
- **Gauge**:可上可下的仪表值(如活跃连接数)。
- **Histogram**:分桶统计(如请求延迟分布),自动生成 count + sum + bucket。
- **Summary**:客户端计算分位数(P50/P99),服务器端不做计算但有额外开销。
> [!TIP]
> 面试常考点:为什么 Prometheus 用 Pull 而不是 Push?因为 Prometheus 作为中心化的采集器天然知道所有 target 的地址,Pull 模式下失联的 service 会自动被忽略(不再收到数据即标记为 down)。如果是 Push 模式(如 StatsD),挂了的服务会停止发送数据但采集器不知道它们已经不存在了。
### Grafana Dashboard 模板来源
Grafana 本身不提供指标,它只是可视化层。主要数据来源:
| 数据源 | 采集内容 | 典型用途 |
|--------|---------|---------|
| cAdvisor | 容器级别的 CPU/内存/网络/Disk I/O | 容器资源监控 |
| kube-state-metrics | Pod/Node/Deployment/RS 的状态元数据 | K8s 资源健康度 |
| node-exporter | 宿主机硬件指标(CPU、内存、磁盘、网络) | 节点层面性能分析 |
| cadvisor+prometheus | 组合 cAdvisor 和 Prometheus 的指标导出 | 综合容器监控面板 |
### 日志收集方案
```mermaid
graph LR
PODS["所有 Pod 的 stdout/stderr"] --> FLUENTD["Fluentd / Filebeat DaemonSet"]
FLUENTD -->|"Filesystem tail"| LOG_FILES["文件日志"]
FLUENTD --> ES_ELB["Elasticsearch Cluster"]
FLUENTD --> LOKI["Loki"]
ES_ELB --> KIBANA["Kibana"]
LOKI --> GRAFANA["Grafana Log Panel"]
```
- **ELK 栈(Elasticsearch + Logstash/Filebeat + Kibana)**:功能最强大,支持全文搜索、聚合分析,但存储成本高(JSON 结构化索引非常占空间)。
- **Loki(Grafana 生态)**:不建立全文索引,只索引 Labels。查询时用 label 过滤后再检索日志内容,存储成本极低。适合与 Grafana 深度集成的场景。
> [!NOTE]
> 最佳实践:日志采集应该在 Pod 层面做 sidecar 注入(而非直接写文件),这样即使 Pod 崩溃也能收集到最后一段输出。
## 代码示例
Go 中使用 Prometheus Client Library 暴露自定义指标:
```go
import "github.com/prometheus/client_golang/prometheus"
var requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
func init() {
prometheus.MustRegister(requestDuration)
}
```
## 实践场景
**秋招高频问题:**
- "HPA 能基于自定义业务指标扩缩容吗?" — 可以。通过 Custom Metrics API + adapter(如 prometheus-adapter),HPA 可以从 Prometheus 读取任意业务指标。例如根据 QPS、错误率、或者 Kafka 消费 lag 来触发扩缩容。
- "为什么蓝绿部署要双倍资源?" — 蓝绿的本质是同时保留新旧两套完整环境,用负载均衡器切换流量。这保证了随时可以秒级回滚,代价就是付费双倍的生产实例。
- "Prometheus 在什么场景下不适合?" — 海量短生命周期任务(如 Serverless 函数,每分钟创销毁成千上万 Pod)。Prometheus 的拉取模型无法跟得上如此动态的变化。此时更适合用 Pushgateway 或改为 push-based 的方案。
> [!WARNING]
> K8s 的 Ready/NotReady 状态切换不会自动通知 HPA——只有当 Pod 处于 Running + Ready 时才计入副本基数。所以在 deployment.yaml 里一定要配好 readiness probe,否则 HPA 会误判当前副本数从而做出错误的扩缩容决策。
## 关联笔记
- [[负载均衡算法]]
- [[限流熔断降级]]