主题
第二部分 Kubernetes 进阶实战
第一部分我们打好了地基:集群搭建、核心对象、基本排错。本部分进入"进阶实战"阶段——你将真正理解 kube-scheduler 如何做决策、requests/limits 背后发生了什么、Service 流量如何穿过 iptables/IPVS、如何用 RBAC 与 PSS 把集群管起来、如何用 Prometheus 把集群"看"清楚、以及如何用 Helm 与 Kustomize 把应用"发"出去。这些内容既是生产运维的日常,也是 Kubernetes 面试的高频考点。
适用版本:Kubernetes v1.28+,RKE2 v1.28+,Helm 3.x
本部分目录
| 章节 | 主题 |
|---|---|
| 第 1 章 | 调度进阶 |
| 第 2 章 | 资源管理进阶 |
| 第 3 章 | 存储进阶 |
| 第 4 章 | 网络进阶 |
| 第 5 章 | 安全进阶 |
| 第 6 章 | 可观测性入门到实战 |
| 第 7 章 | 应用打包与发布 |
| 第 8 章 | 常见面试题与进阶排错套路 |
第 1 章 调度进阶
1.1 kube-scheduler 工作原理
调度器的职责:为 Pending 状态的 Pod 选择一个最合适的节点。整个过程分两个阶段:
┌─────────────────────────────────────────────────────────────┐
│ kube-scheduler │
│ │
│ ┌───────────┐ ┌──────────────────┐ ┌────────────┐ │
│ │ 调度队列 │ ──> │ Filtering(过滤) │ ──> │ Scoring(打分)│ │
│ │(activeQ) │ │ 不满足条件的出局 │ │ 按策略排序 │ │
│ └───────────┘ └──────────────────┘ └─────┬──────┘ │
│ │ │
│ ┌───────▼──────┐ │
│ │ Bind: 写 API │ │
│ │ (异步,失败回队列)│ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘- Filtering(过滤):检查节点资源是否够用、taints 是否被容忍、端口是否冲突、亲和性是否满足等。插件如
NodeResourcesFit、TaintToleration、NodeAffinity、VolumeBinding。 - Scoring(打分):对剩余节点打分,插件如
NodeResourcesFit(least/most allocated)、ImageLocality(优先有镜像的节点)、InterPodAffinity。 - Bind:调度器更新 Pod 的
spec.nodeName(通过 Binding 子资源),之后由该节点上的 kubelet 接手。
注意:调度器只在 Pod 创建或节点标签变化触发"重新调度事件"时工作。它不会主动迁移已经运行的 Pod——这是 descheduler(外部项目)做的事。
查看调度决策过程:
bash
kubectl get events --field-selector reason=FailedScheduling
kubectl describe pod <pending-pod> # 看 Events 里的 FailedScheduling 信息
# 调度器日志(RKE2 中调度器是静态 Pod,位于 control-plane 节点)
kubectl -n kube-system logs kube-scheduler-rke2-server1 | grep -i "unable to schedule"1.2 nodeSelector:最简单的定向调度
bash
kubectl label node rke2-agent1 disktype=ssdyaml
apiVersion: v1
kind: Pod
metadata:
name: nginx-ssd
spec:
nodeSelector:
disktype: ssd
containers:
- name: nginx
image: nginx:1.25特点:硬性要求,没有匹配节点则 Pod 一直 Pending。适合简单场景。
1.3 nodeAffinity:节点亲和性
比 nodeSelector 更强大,支持 In / NotIn / Exists / DoesNotExist / Gt / Lt 运算符,支持软/硬两种模式:
| 字段 | 含义 | 类比 |
|---|---|---|
requiredDuringSchedulingIgnoredDuringExecution | 硬亲和,必须满足 | nodeSelector 增强版 |
preferredDuringSchedulingIgnoredDuringExecution | 软亲和,尽量满足(权重 1~100) | 偏好排序 |
"IgnoredDuringExecution" 是考点:Pod 运行期间节点标签被移除,Pod 不会被驱逐(K8s 1.26+ 引入了
...RequiredDuringExecution的 Alpha 特性,默认不开启)。
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cache
spec:
replicas: 3
selector:
matchLabels: { app: cache }
template:
metadata:
labels: { app: cache }
spec:
affinity:
nodeAffinity:
# 硬性:必须调度到生产环境节点
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: environment
operator: In
values: ["prod"]
# 软性:优先 ssd 节点(权重 80)和高带宽节点(权重 20)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
- weight: 20
preference:
matchExpressions:
- key: bandwidth
operator: Gt
values: ["10"]
containers:
- name: redis
image: redis:71.4 podAffinity / podAntiAffinity:Pod 间亲和与反亲和
以已经运行在节点上的 Pod 的标签为参照做调度,必须搭配 topologyKey(拓扑域,通常是 kubernetes.io/hostname 或 topology.kubernetes.io/zone)。
yaml
spec:
affinity:
# 反亲和:同一副本不要落在同一节点(高可用常用)
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels: { app: web }
topologyKey: kubernetes.io/hostname
# 亲和:尽量和 cache 同节点(减少网络延迟)
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels: { app: cache }
topologyKey: kubernetes.io/hostname| 场景 | 推荐用法 |
|---|---|
| 同一应用多副本分散到不同节点 | podAntiAffinity + hostname |
| 同一应用分散到不同可用区 | podAntiAffinity + zone(软) |
| 前端紧贴缓存部署 | podAffinity |
| 多副本 > 节点数时 | 必须用软反亲和,否则多出的副本 Pending |
最佳实践:生产环境的硬反亲和要谨慎——当副本数超过拓扑域数量时,超出的 Pod 永远 Pending。多数场景用
preferred或直接用topologySpreadConstraints(见 8.5)。
1.5 topologySpreadConstraints:拓扑分布约束
K8s 1.19 稳定,比 podAntiAffinity 更灵活:可以控制"最大倾斜度",而不是简单的"绝不在一起"。
yaml
spec:
topologySpreadConstraints:
- maxSkew: 1 # 任意两个拓扑域的 Pod 数差值不超过 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule # 硬约束;ScheduleAnyway 为软约束
labelSelector:
matchLabels: { app: web }
- maxSkew: 2
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: { app: web }验证分布效果:
bash
kubectl get pods -l app=web -o wide --sort-by=.spec.nodeName1.6 Taints 与 Tolerations:污点与容忍
亲和性是 Pod 的"主动吸引",污点是节点的"主动排斥"。两者配合使用。
bash
# 给节点打污点(三个 effect 见下表)
kubectl taint nodes rke2-gpu1 dedicated=gpu:NoSchedule
# 删除污点(注意末尾的 -)
kubectl taint nodes rke2-gpu1 dedicated=gpu:NoSchedule-
# 查看节点污点
kubectl describe node rke2-gpu1 | grep Taints| effect | 行为 |
|---|---|
NoSchedule | 不容忍的新 Pod 不调度上来(已有的不动) |
PreferNoSchedule | 尽量不调度(软约束) |
NoExecute | 不容忍的 Pod 立即驱逐(新 Pod 也不调度) |
容忍污点的 Pod:
yaml
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
# 容忍所有 key 为 dedicated 的污点(value 不限)
- key: dedicated
operator: Exists
effect: NoExecute
tolerationSeconds: 300 # NoExecute 特有:被驱逐前宽限 300 秒RKE2 控制平面默认带污点:
bash
kubectl describe node rke2-server1 | grep -i taint
# Taints: node-role.kubernetes.io/control-plane=true:NoSchedule常见面试题:节点 NotReady 后 Pod 多久被驱逐?答:节点控制器给节点打
node.kubernetes.io/not-ready:NoExecute污点,Pod 默认通过自动注入的 toleration 容忍 300 秒(tolerationSeconds,受default-not-ready-toleration-seconds控制),之后被驱逐并在其他节点重建。K8s 1.27+ 还可以结合 Pod Disruption Budget 控制。
1.7 优先级与抢占:PriorityClass
资源不足时,高优先级 Pod 可以**抢占(Preemption)**低优先级 Pod:低优 Pod 被驱逐,高优 Pod 上位。
yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000 # 越大越优先;>10亿 保留给系统组件
globalDefault: false
description: "核心在线业务"
preemptionPolicy: PreemptLowerPriority # Never 可禁用抢占
---
# Pod 中使用
spec:
priorityClassName: high-priority资源不足时的决策链:
新 Pod Pending
│
├─ priorityClass 比现有 Pod 高? ──否──> 继续 Pending
│
是
│
▼
逐节点尝试"驱逐低优 Pod 后能否放下"
│
├─ 能 ──> 驱逐低优 Pod(尊重 PDB 与 terminationGracePeriod)──> 绑定节点
└─ 不能 ──> Pending注意:抢占遵守 PodDisruptionBudget;被抢占的 Pod 有优雅退出时间;
system-cluster-critical(2000000000)和system-node-critical(2000001000)是内置最高优先级,kubelet 的静态 Pod 也受其保护。
1.8 本章小结与面试题
Q1:nodeSelector 和 nodeAffinity 的区别? nodeSelector 只支持等值匹配的硬约束;nodeAffinity 支持多运算符、多表达式、软/硬两种模式。
Q2:taint 和 affinity 有什么关系? 互补。affinity 是 Pod 说"我想去哪",taint 是节点说"谁能来"。要独占一组节点(如 GPU 节点),通常"污点 + 亲和"双管齐下:节点打 NoSchedule 污点挡住普通 Pod,专用 Pod 加 toleration + nodeAffinity 确保只去这些节点。
Q3:如何实现"每个节点最多跑 1 个副本"? 硬 podAntiAffinity(topologyKey=hostname)或 topologySpreadConstraints(maxSkew=1 + DoNotSchedule)。
第 2 章 资源管理进阶
2.1 requests / limits 深入
| 维度 | CPU | 内存 |
|---|---|---|
| requests 的作用 | 调度依据;分配 cpu.shares(相对权重) | 调度依据 |
| limits 超限后果 | throttle(限流),不杀容器 | OOMKill,杀容器(OOM Score 提高) |
| 单位 | 500m = 0.5 核 | 128Mi、1Gi |
yaml
resources:
requests:
cpu: 250m # 保证:调度时按此值找节点
memory: 512Mi
limits:
cpu: "1" # 封顶:最多用 1 核
memory: 1Gi要点:
- CPU 是可压缩资源,超 limit 只是被 CFS 限流(表现:CPU Throttled 指标升高、延迟抖动)。
- 内存是不可压缩资源,超 limit 直接触发 cgroup OOM Kill(退出码 137,
reason: OOMKilled)。 - 调度只认 requests。节点可分配量 = 节点容量 - system-reserved - kube-reserved - eviction-reserved。
bash
kubectl describe node rke2-agent1 | grep -A 8 "Allocated resources"
kubectl top pod -A --sort-by=memory2.2 QoS 等级与 OOM 策略
K8s 根据 requests/limits 的组合给 Pod 分 QoS:
| QoS | 条件 | 驱逐/OOM 优先级 |
|---|---|---|
Guaranteed | 每个容器 requests == limits(CPU 和内存都设置且相等) | 最后被杀 |
Burstable | 设置了 requests,但不满足 Guaranteed | 中间 |
BestEffort | 什么都没设置 | 最先被杀 |
节点内存压力时的两级处理:
节点内存不足
├─ kubelet eviction(软/硬驱逐阈值,如 memory.available<100Mi)
│ 按 QoS + 超出 requests 的量排序,驱逐 BestEffort → Burstable → Guaranteed
└─ 内核 OOM Killer(kubelet 反应不过来时)
按 oom_score_adj 排序:BestEffort(1000) > Burstable > Guaranteed(-997~-998)bash
kubectl get pod xxx -o jsonpath='{.status.qosClass}'最佳实践:生产关键服务设 Guaranteed(req == limit);Java 应用务必显式设置
-Xmx并小于容器 limit 的 75%~80%,否则 JVM 按宿主机内存堆栈化导致 OOMKill(JDK 8u191+/JDK 10+ 已感知 cgroup);CPU limit 对延迟敏感服务可只设 requests 不设 limit(K8s 社区争议点,视业务取舍)。
2.3 HPA:水平自动伸缩(autoscaling/v2)
bash
# 前置条件:metrics-server 必须可用
kubectl top nodeyaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
# 资源指标:CPU 利用率达 60% 触发扩容(利用率 = 实际使用 / requests)
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
# 资源指标:内存绝对值
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 800Mi
# 自定义指标:每个 Pod 每秒 1000 请求(需 prometheus-adapter)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
# 扩缩容行为控制(v2 重要特性)
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 缩容冷却 5 分钟,防止抖动
policies:
- type: Percent
value: 50
periodSeconds: 60 # 每分钟最多缩掉 50%
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 4
periodSeconds: 60 # 每分钟最多加 4 个 Pod
selectPolicy: Max期望副本数计算公式:
desiredReplicas = ceil(currentReplicas × currentMetricValue / desiredMetricValue)例如当前 4 副本、CPU 利用率 90%、目标 60%:ceil(4 × 90/60) = 6 副本。
常用命令:
bash
kubectl get hpa
kubectl describe hpa web-hpa # 看 Events:指标获取失败、扩容记录
kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods | jq .注意:HPA 的 Resource 指标要求目标 Pod 必须设置 requests,否则无法计算利用率;多指标时取各指标计算结果的最大值;HPA 与 VPA 不要同时作用于同一指标。
2.4 VPA:垂直自动伸缩
VPA(Vertical Pod Autoscaler,社区组件,需单独安装)自动推荐/调整 Pod 的 requests/limits。
bash
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler && ./hack/vpa-up.shyaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: web
updatePolicy:
updateMode: "Auto" # Off=只给建议; Initial=创建时注入; Recreate/Auto=重建生效
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed: { cpu: 100m, memory: 128Mi }
maxAllowed: { cpu: 2, memory: 4Gi }bash
kubectl get vpa web-vpa -o jsonpath='{.status.recommendation}' | jq .最佳实践:生产先用
Off模式观察推荐值(至少积累 1~2 周数据),确认合理后再切 Auto。VPA 调整资源会重建 Pod,注意 PDB 与滚动节奏。
2.5 KEDA 简介:事件驱动自动伸缩
KEDA(Kubernetes Event-driven Autoscaling)以外部事件源(Kafka 积压、Redis 队列长度、Prometheus 指标、Cron 等)驱动伸缩,支持缩到 0。
bash
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda -n keda --create-namespaceyaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer-scaler
spec:
scaleTargetRef:
name: kafka-consumer # 目标 Deployment
minReplicaCount: 0 # 无消息时缩到 0,省资源
maxReplicaCount: 30
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-svc:9092
consumerGroup: order-group
topic: orders
lagThreshold: "10" # 每个副本承担 10 条积压HPA vs VPA vs KEDA 对比:
| 维度 | HPA | VPA | KEDA |
|---|---|---|---|
| 伸缩方向 | 水平(改副本数) | 垂直(改 requests/limits) | 水平(改副本数,可缩到 0) |
| 触发依据 | CPU/内存/自定义指标 | 历史资源用量 | 外部事件源(50+ Scaler) |
| 是否重建 Pod | 否 | 是(Auto 模式) | 否 |
| 典型场景 | Web 流量波动 | 资源规格调优 | 消息队列消费者、定时任务 |
2.6 LimitRange 与 ResourceQuota 实战
LimitRange:命名空间级,给 Pod/容器设置资源默认值与上下限(防止有人不设 requests)。
yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: dev
spec:
limits:
- type: Container
default: # 相当于 limits 默认值
cpu: 500m
memory: 512Mi
defaultRequest: # 相当于 requests 默认值
cpu: 100m
memory: 128Mi
max: { cpu: "2", memory: 2Gi }
min: { cpu: 50m, memory: 64Mi }
maxLimitRequestRatio: # limit/request 比值上限,防超卖失衡
cpu: "4"ResourceQuota:命名空间级总量管控。
yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: dev
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
services.loadbalancers: "2"
persistentvolumeclaims: "10"bash
kubectl describe quota -n dev # 查看已用/剩余注意:命名空间设置了
requests.cpu配额后,该空间所有 Pod 必须显式声明 requests(否则创建被拒),所以 ResourceQuota 通常和 LimitRange 搭配使用。面试高频:"如何保证每个命名空间资源不被打满?"——答案就是这两个对象。
2.7 本章小结与面试题
Q1:容器被 OOMKilled,exit code 137,如何排查?kubectl describe pod 看 Last State: OOMKilled;确认 limit 设置与 JVM/应用参数;用 kubectl top 与 Prometheus 看增长曲线(泄漏 or 突发);调整 limit 或修复泄漏。
Q2:HPA 为什么不扩容? 依次检查:metrics-server 是否正常(kubectl top);Pod 是否设了 requests;指标是否真达阈值;kubectl describe hpa 看 Events(FailedGetResourceMetric 等);是否到 maxReplicas。
Q3:CPU limit 该不该设? 设了会 throttle 影响延迟敏感业务;不设可能挤爆节点。折中:延迟敏感服务不设 CPU limit 但设 requests(Guaranteed 内存 + Burstable CPU),配合节点 monitoring。
第 3 章 存储进阶
3.1 PV / PVC 生命周期与回收策略
┌──────────────────────────────────────────────────┐
│ 生命周期 │
│ │
Provision ──> Bound ──> Released ──> (Retain / Delete / 回收)
(静态创建或 绑定 PVC 被删除 │
动态供给) ├─ Retain: 保留数据, 人工清理后可复用
▲ ├─ Delete: 自动删除底层卷(动态供给默认)
│ └─ Recycle: 已废弃(deprecated)
└────── Retain 的 PV 清理 claimRef 后重新 Available状态流转:Available → Bound → Released →(Retain 处理)→ Available,或 → Failed。
回收策略对比:
| reclaimPolicy | 行为 | 适用 |
|---|---|---|
Delete | PVC 删除时连底层存储一起删 | 云盘动态供给默认;无状态数据 |
Retain | 保留 PV 与数据,需人工处理 | 数据库等宝贵数据(生产推荐) |
Recycle | rm -rf 后复用 | 已废弃,用动态供给替代 |
Retain 复用手动操作:
bash
kubectl delete pvc old-pvc
kubectl get pv # 状态 Released
kubectl patch pv old-pv -p '{"spec":{"claimRef": null}}' # 解绑
kubectl get pv # 状态 Available,可被新 PVC 绑定注意:PVC 被使用中的 Pod 引用时删除会卡在 Terminating(由
kubernetes.io/pvc-protectionFinalizer 保护),Pod 删除后才真正释放。
3.2 StorageClass 详解
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
annotations:
storageclass.kubernetes.io/is-default-class: "true" # 默认 SC
provisioner: rancher.io/local-path # 供给器(见下表)
parameters:
type: gp3 # provisioner 自定义参数
reclaimPolicy: Retain # 动态 PV 的回收策略(默认 Delete)
volumeBindingMode: WaitForFirstConsumer # 延迟绑定(默认 Immediate)
allowVolumeExpansion: true # 允许扩容
mountOptions:
- noatime| 字段 | 说明 | 关键点 |
|---|---|---|
provisioner | 谁来创建卷 | 内置插件已移除(1.26 完成 CSI 迁移),全部用 CSI 驱动名 |
reclaimPolicy | 动态 PV 回收策略 | 生产改 Retain 防误删数据 |
volumeBindingMode | 绑定时机 | WaitForFirstConsumer:等 Pod 调度后再在所在节点/可用区创建卷,避免"卷在 A 区、Pod 调度到 B 区" |
allowVolumeExpansion | 是否允许在线扩容 | CSI 驱动需支持 |
常见 provisioner:
| provisioner | 存储 |
|---|---|
rancher.io/local-path | Rancher local-path-provisioner(RKE2 常用) |
driver.longhorn.io | Longhorn 分布式块存储 |
ebs.csi.aws.com | AWS EBS |
cephfs.csi.ceph.com / rbd.csi.ceph.com | Ceph CSI |
kubernetes.io/no-provisioner | 静态 Local PV |
3.3 CSI 原理
CSI(Container Storage Interface)把存储驱动从 K8s 核心中剥离。一个 CSI 驱动通常部署为两部分:
┌──────────────────────────┐ ┌──────────────────────────────┐
│ Controller Plugin (STS) │ │ Node Plugin (DaemonSet) │
│ ┌──────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ csi-provisioner │ │ │ │ node-driver-registrar │ │
│ │ csi-attacher │ │ │ │ csi-driver (业务逻辑) │ │
│ │ csi-resizer │ │ │ └──────────────────────────┘ │
│ │ csi-snapshotter │ │ │ 负责: NodeStage/Publish │
│ │ csi-driver │ │ │ (在 kubelet 要求时挂载到节点) │
│ └──────────────────────┘ │ └──────────────────────────────┘
│ 负责: Create/Delete 卷, │ ▲
│ Attach/Detach │ gRPC over unix socket
└──────────────────────────┘
Sidecar 监听 K8s 对象(PVC/VolumeAttachment/Snapshot)并调 CSI 接口一次动态供给的完整链路:
用户创建 PVC
→ external-provisioner 发现 PVC 的 SC 属于自己 → 调 CSI CreateVolume → 创建 PV 对象
→ PV Controller 绑定 PVC ↔ PV
→ Pod 调度到节点 → external-attacher 创建 VolumeAttachment → 驱动 Attach(云盘挂到 VM)
→ kubelet 调 Node Plugin NodeStageVolume + NodePublishVolume(格式化+挂载到 Pod 目录)bash
kubectl get csidrivers
kubectl get volumeattachment
kubectl get pods -n longhorn-system # 以 Longhorn 为例看 CSI 组件3.4 VolumeSnapshot:卷快照
快照涉及三个 CRD + 快照控制器:
bash
kubectl api-resources | grep snapshot
# volumesnapshotclasses / volumesnapshotcontents / volumesnapshotsyaml
# 1. 快照类(类比 StorageClass)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-snapclass
driver: driver.longhorn.io
deletionPolicy: Delete # 删除快照对象时是否删底层快照
---
# 2. 创建快照
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysql-snap-20260719
spec:
volumeSnapshotClassName: csi-snapclass
source:
persistentVolumeClaimName: mysql-data
---
# 3. 从快照恢复(创建新 PVC)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-restore
spec:
storageClassName: longhorn
dataSource:
name: mysql-snap-20260719
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi注意:RKE2 自带
snapshot-controller与 CRD(可通过disable: [rke2-snapshot-controller]关闭),但快照能力取决于 CSI 驱动(local-path 不支持真快照,Longhorn/EBS 支持);deletionPolicy: Retain可在删除对象时保住底层快照。
3.5 Local PV:本地持久卷
场景:高性能本地 SSD、有状态中间件(如自建 ES/Kafka)。特点:性能高,但数据绑定节点,节点挂则卷不可用。
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer # 必须! 等 Pod 调度再绑定
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-agent1
spec:
capacity:
storage: 100Gi
accessModes: ["ReadWriteOnce"]
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /data/local-pv
nodeAffinity: # 必须声明卷所在节点
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["rke2-agent1"]对比 local-path-provisioner(RKE2 默认插件 rke2-local-storage 可装):自动创建目录、自动带 nodeAffinity,但不做容量隔离,本质是简化版 Local PV。
3.6 PVC 扩容
前提:StorageClass allowVolumeExpansion: true + CSI 驱动支持。
bash
# 直接编辑 PVC 的 requests.storage(只能改大不能改小)
kubectl patch pvc mysql-data -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl describe pvc mysql-data
# 事件: Resizing → FileSystemResizePending(等Pod重启挂载扩容文件系统) → 完成
kubectl get pvc mysql-data # CAPACITY 变为 20Gi注意:块设备扩容快,但文件系统扩容通常需要 Pod 重建(部分驱动支持在线扩容 ExpandInUsePersistentVolumes,1.24+ 默认可用)。
3.7 本章小结与面试题
Q1:PV 和 PVC 的关系?为什么要两层? PVC 是"申请单"(应用视角:我要多大、什么模式),PV 是"实际卷"(运维视角:具体存储细节)。解耦后应用不用关心后端是 NFS、云盘还是 Ceph。
Q2:PVC 一直 Pending? 检查:StorageClass 是否存在且 provisioner Pod 正常;kubectl describe pvc 看 provisioner 报错;是否有匹配 PV(静态供给时 accessModes/capacity/selector 是否匹配);volumeBindingMode 为 WaitForFirstConsumer 时 Pod 未创建前 Pending 是正常的。
Q3:数据安全的最佳实践? 生产 SC 设 reclaimPolicy: Retain;重要数据定期 VolumeSnapshot;删除 PVC 前确认 kubectl get pv 的 CLAIM 与回收策略。
第 4 章 网络进阶
4.1 Service 实现原理:iptables vs IPVS
kube-proxy 负责把 Service 的虚拟 IP 规则落到每个节点。
iptables 模式(默认):
bash
iptables -t nat -L KUBE-SERVICES -n | head -30
iptables -t nat -L KUBE-SVC-XXXX -n规则链:PREROUTING/OUTPUT → KUBE-SERVICES → KUBE-SVC-xxx(按概率 statistic 模块分流)→ KUBE-SEP-xxx(DNAT 到具体 Pod IP)。
IPVS 模式:
bash
ipvsadm -Ln | head -30对比表(面试必考):
| 维度 | iptables | IPVS |
|---|---|---|
| 数据结构 | 线性链式匹配 O(n) | 哈希表 O(1) |
| 大规模表现 | 数千 Service 时规则膨胀、更新慢、延迟抖动 | 万级 Service 无压力 |
| 负载均衡算法 | 仅随机(probability) | rr/wrr/lc/wlc/sh/dh 等多种 |
| 会话保持 | 不支持真亲和(仅 Service 层 timeout) | 支持 |
| 内核要求 | 通用 | 需加载 ip_vs 模块 |
RKE2 启用 IPVS:在 server/agent 的 config 中配置 kube-proxy 参数(或安装时通过 HelmChartConfig 改 rke2-kube-proxy 的 mode: ipvs)。
流量路径(ClusterIP 为例):
Pod A ──> 10.43.0.10:80 (ClusterIP)
│ 本机 netfilter: DNAT → 选中后端 Pod IP 10.42.1.15
│ (记录 conntrack,回程自动做 SNAT 还原)
▼
Pod B (可能在另一节点, 经 CNI VXLAN 隧道)externalTrafficPolicy(NodePort/LoadBalancer):Cluster(默认,SNAT 后任意转发,丢源 IP)vs Local(只发本节点后端,保留源 IP,可能不均)。
4.2 CoreDNS 与 NodeLocal DNSCache
CoreDNS 配置(RKE2 中 ConfigMap 为 kube-system/coredns,通过 HelmChartConfig rke2-coredns 定制):
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa { # K8s 域名解析
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf # 非集群域名转发到上游 DNS
cache 30
loop
reload
loadbalance
}常用定制:
# stubDomain 效果:corp.example.com 走公司内网 DNS
corp.example.com {
forward . 10.10.0.2 10.10.0.3
cache 30
}
# rewrite:把 external-svc.default.svc.cluster.local 重写到真实服务
rewrite name exact old-svc.default.svc.cluster.local new-svc.other-ns.svc.cluster.local验证解析:
bash
kubectl run dnsutils --rm -it --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 -- nslookup kubernetes.default
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50NodeLocal DNSCache:在每个节点跑一个本地缓存(DaemonSet),Pod 查询走节点本地 169.254.20.10,避免大量 DNS 查询打到 CoreDNS 造成的 conntrack 表满与延迟。RKE2 支持通过 HelmChartConfig 启用 nodelocal 配置。
无 NodeLocal: Pod ──UDP──> CoreDNS ClusterIP ──DNAT──> 某 CoreDNS Pod
(每个查询占 conntrack 条目, UDP 丢包无重传感知)
有 NodeLocal: Pod ──> 本节点 nodelocal-dns (缓存命中直接返回)
未命中 ──TCP──> CoreDNS (TCP 不占 conntrack 槽位问题小)4.3 Ingress 深入:Ingress-NGINX
RKE2 默认安装 rke2-ingress-nginx(DaemonSet + hostNetwork 可选)。以 Helm 部署为例:
bash
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --create-namespace \
--set controller.service.type=LoadBalancerTLS 配置:
bash
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key -n prodyaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: prod
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2 # 路径重写
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 50m
spec:
ingressClassName: nginx
tls:
- hosts: ["app.example.com"]
secretName: web-tls
rules:
- host: app.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific # rewrite-target $2 捕获组配合
backend:
service:
name: api-svc
port:
number: 8080常用 annotation 速查:
| annotation | 作用 |
|---|---|
rewrite-target | 重写后端路径 |
ssl-redirect / force-ssl-redirect | HTTP 强制跳 HTTPS |
canary: "true" + canary-weight: "10" | 灰度:10% 流量到新版本 |
canary-by-header: "X-Canary" | 按请求头灰度 |
limit-rps: "20" | 限流 |
auth-type: basic + auth-secret | Basic 认证 |
proxy-body-size | 上传大小限制 |
configuration-snippet | 自定义 NGINX 片段 |
金丝雀发布示例:
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% 流量到 v2
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-v2
port:
number: 80排查 Ingress:
bash
kubectl -n ingress-nginx logs -l app.kubernetes.io/component=controller --tail=100
kubectl -n ingress-nginx exec deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | less
kubectl describe ingress web4.4 Gateway API 简介
Ingress 的继任者(GA since v1.0,2023)。角色分离 + 更丰富的流量管理能力:
┌─────────────┐ ┌──────────┐ ┌───────────┐ ┌─────────┐
│ GatewayClass│──>│ Gateway │──>│ HTTPRoute │──>│ Service │
│ (基础设施商 │ │(运维:监听 │ │(开发者: │ │(后端) │
│ 定义实现) │ │ 端口/TLS) │ │ 路由规则) │ └─────────┘
└─────────────┘ └──────────┘ └───────────┘yaml
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: prod-gateway
spec:
gatewayClassName: nginx # 或 cilium / istio / traefik
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
certificateRefs:
- name: web-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
spec:
parentRefs:
- name: prod-gateway
hostnames: ["app.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /v2
backendRefs:
- name: web-v2
port: 80
weight: 10 # 原生支持流量拆分(灰度), 无需 annotation
- name: web-v1
port: 80
weight: 90Ingress vs Gateway API:Ingress 单一资源、能力靠各实现 annotation 扩展;Gateway API 角色分离(GatewayClass/Gateway/Route)、标准化流量拆分与 header 匹配。新项目可关注,存量 Ingress 无需急于迁移。
4.5 NetworkPolicy(Calico 示例)
RKE2 默认 CNI 是 Canal(Calico + Flannel),支持 NetworkPolicy。策略是白名单模型:一旦某 Pod 被 policy 选中,未声明的流量一律拒绝。
yaml
# 1. 默认拒绝某命名空间所有入站
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: prod
spec:
podSelector: {} # 空 = 选中所有 Pod
policyTypes: ["Ingress"]
---
# 2. 只允许 frontend 访问 backend 的 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: prod
spec:
podSelector:
matchLabels: { app: backend }
policyTypes: ["Ingress"]
ingress:
- from:
- podSelector:
matchLabels: { app: frontend }
ports:
- protocol: TCP
port: 8080
---
# 3. 放行 DNS(出方向几乎必备,否则 default-deny 后无法解析)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: prod
spec:
podSelector: {}
policyTypes: ["Egress"]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53排错思路:kubectl get networkpolicy -A;临时用 kubectl run test --rm -it --image=busybox:1.36 -- wget -qO- --timeout=3 http://svc:port 验证连通性;Calico 环境可用 calicoctl 查看下发规则。
注意:NetworkPolicy 依赖 CNI 支持(纯 Flannel 不支持);命名空间打
default-deny后记得放行 DNS 出口,这是最常见的踩坑点。
4.6 本章小结与面试题
Q1:Service ClusterIP 不通,排查顺序? Service selector 是否匹配 Pod 标签 → Endpoints/EndpointSlice 是否有地址 → Pod 是否 Ready(readinessProbe)→ kube-proxy 规则(iptables -t nat / ipvsadm)→ CNI 网络是否通 → NetworkPolicy 是否拦截。
Q2:Ingress 502 的常见原因? 后端 Service 无 Endpoints(Pod NotReady);端口不匹配(Service targetPort vs 容器监听);proxy-body-size/超时限制;后端 OOM 重启中。
Q3:外部流量经过 NodePort 后源 IP 丢失怎么办? 设 externalTrafficPolicy: Local,或 LB 层用 PROXY protocol / Ingress 直接 hostNetwork。
第 5 章 安全进阶
5.1 RBAC 深入
RBAC 四要素:Role/ClusterRole(权限集合)+ RoleBinding/ClusterRoleBinding(绑定到主体)。
Subject (User / Group / ServiceAccount)
│
│ RoleBinding (限某命名空间) / ClusterRoleBinding (集群范围)
▼
Role (命名空间级权限) / ClusterRole (集群级权限)
│
▼
rules: apiGroups + resources + verbsyaml
# Role:只允许在 dev 命名空间读写 Pod 和 Deployment
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-manager
namespace: dev
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-manager-binding
namespace: dev
subjects:
- kind: ServiceAccount
name: app-sa
namespace: dev
roleRef:
kind: Role
name: app-manager
apiGroup: rbac.authorization.k8s.ioClusterRole 聚合规则(给内置 admin/edit/view 追加权限的经典手法):
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregate-crd-view
labels:
rbac.authorization.k8s.io/aggregate-to-view: "true" # 自动并入内置 view 角色
rules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors"]
verbs: ["get", "list", "watch"]ServiceAccount 使用:
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: dev
automountServiceAccountToken: true
---
# Pod 中引用
spec:
serviceAccountName: app-sa常用排查命令:
bash
kubectl auth can-i create deployments -n dev --as=system:serviceaccount:dev:app-sa
kubectl auth can-i --list -n dev --as=system:serviceaccount:dev:app-sa
kubectl get rolebindings,clusterrolebindings -A | grep app-sa最佳实践:最小权限原则;不用
defaultSA 跑业务;避免给*verbs /*resources;给 CI/CD 的 SA 用 RoleBinding 限定命名空间而非 cluster-admin。
5.2 Pod Security Standards / Admission
K8s 1.23+ 内置 Pod Security Admission(替代已删除的 PodSecurityPolicy),三个等级:
| 级别 | 说明 |
|---|---|
privileged | 不限制 |
baseline | 禁止特权容器、hostNetwork、hostPath 等明显危险项 |
restricted | 最严格:必须 runAsNonRoot、禁特权提升、seccomp、限制 capabilities |
对命名空间打标签启用:
bash
kubectl label namespace prod \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
# enforce=拒绝; audit=记录审计; warn=客户端警告restricted 合规的 Pod 示例:
yaml
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]测试违规效果:
bash
kubectl run test --image=nginx -n prod --overrides='{"spec":{"containers":[{"name":"test","image":"nginx","securityContext":{"privileged":true}}]}}'
# Error: pods "test" is forbidden: violates PodSecurity "baseline:latest": privileged5.3 Secret 加密
默认 Secret 在 etcd 中只是 base64(不是加密!)。静态加密需配置 EncryptionConfiguration:
yaml
# /etc/rancher/rke2/encryption-provider-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: <32字节随机串的base64>
- identity: {} # 兜底:明文(迁移期用), 完成后移除RKE2 更简单——直接用内置命令:
bash
rke2 secrets-encrypt enable # 自动生成并应用加密配置
rke2 secrets-encrypt status
rke2 secrets-encrypt rotate # 轮换密钥验证(在 control-plane 节点直接查 etcd):
bash
crictl exec $(crictl ps --name etcd -q) \
etcdctl --cert=/var/lib/rancher/rke2/server/tls/etcd/server-client.crt \
--key=/var/lib/rancher/rke2/server/tls/etcd/server-client.key \
--cacert=/var/lib/rancher/rke2/server/tls/etcd/ca.crt \
get /registry/secrets/default/my-secret | head -c 100
# 密文以 k8s:enc:aescbc:v1: 开头即加密生效KMS 方案:云上或高安全场景用 KMS provider(AWS KMS / Vault),密钥不落盘到节点,apiserver 通过 envelope 加密(DEK 加密数据、KEK 在 KMS 保管)。RKE2 支持 KMS v2 配置。
其他最佳实践:限制 Secret 的 RBAC 读取;用外部 Secret 管理(ESO/Vault);避免 env 注入大 Secret(进程环境可被 kubectl exec env 看到),优先 volume 挂载。
5.4 镜像安全扫描概念
防线分层:
构建期: Dockerfile 检查(hadolint) + 基础镜像最小化(distroless/alpine)
推送期: Registry 扫描(Harbor 内置 Trivy 自动扫描 + 阻止高危镜像拉取)
部署期: Admission 校验(只允许受信仓库签名镜像, cosign/Kyverno)
运行期: 持续扫描(Trivy Operator 定期扫描集群内镜像生成 VulnerabilityReport)bash
# 手动扫描示例
trivy image nginx:1.25
trivy image --severity CRITICAL,HIGH myapp:1.0配合策略:Harbor 项目开启"阻止漏洞级别 ≥ High 的镜像被拉取";Kyverno/OPA Gatekeeper 策略拒绝 :latest 标签与非受信 registry。
5.5 审计日志
审计策略定义"哪些 API 请求要记录、记到什么级别":
yaml
# /etc/rancher/rke2/audit-policy.yaml(节选)
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"] # Secret 访问必须记录
- level: RequestResponse
verbs: ["delete", "deletecollection"] # 删除操作记录完整请求响应
- level: Metadata # 其余默认级别RKE2 中通过 server 配置启用:
yaml
# /etc/rancher/rke2/config.yaml
kube-apiserver-arg:
- "audit-policy-file=/etc/rancher/rke2/audit-policy.yaml"
- "audit-log-path=/var/lib/rancher/rke2/server/logs/audit.log"
- "audit-log-maxage=30"
- "audit-log-maxbackup=10"
- "audit-log-maxsize=100"审计级别:None < Metadata(元数据)< Request(+请求体)< RequestResponse(+响应体)。级别越高磁盘压力越大,生产常用 Metadata + 对敏感资源提级。
5.6 本章小结与面试题
Q1:Secret 是安全的吗? 默认不是:base64 可逆、etcd 明文存储、任何有 get 权限的人可读。加固:EncryptionConfiguration 静态加密 + RBAC 最小化 + 审计 + 外部密管。
Q2:PodSecurityPolicy 没了用什么? 内置 Pod Security Admission(命名空间打 label)做基础防护;复杂策略用 Kyverno / OPA Gatekeeper。
Q3:如何给开发人员只读权限? 绑定内置 ClusterRole view(RoleBinding 限定命名空间),需要看 CRD 再用聚合 ClusterRole 扩展。
第 6 章 可观测性入门到实战
6.1 Metrics Server
资源指标(CPU/内存)采集器,HPA 与 kubectl top 的数据源。RKE2 默认安装。
bash
kubectl top node
kubectl top pod -A --sort-by=cpu
kubectl get apiservice v1beta1.metrics.k8s.io # AVAILABLE=True 为正常自建集群安装:
bash
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 自签证书集群需加启动参数 --kubelet-insecure-tls排错:
kubectl top报错Metrics API not available→ 检查 metrics-server Pod 日志(多为证书 SAN 或 10250 端口不通)。
6.2 kube-prometheus-stack 部署
一条 Helm 命令装齐 Prometheus + Alertmanager + Grafana + node-exporter + kube-state-metrics + 一套默认告警规则:
bash
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace \
--set prometheus.prometheusSpec.retention=15d \
--set prometheus.prometheusSpec.storageClassName=longhorn \
--set prometheus.prometheusSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi \
--set grafana.adminPassword='xxx' \
--set grafana.service.type=NodePortbash
kubectl -n monitoring get pods
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80
# Grafana: admin / xxx,内置 K8s 全套看板架构:
┌────────────────────────────────────────────────────────────┐
│ Prometheus (时序库 + 抓取 + 规则评估) │
│ ▲ scrape │
│ │ /metrics │
│ ├── node-exporter (DaemonSet, 节点 CPU/内存/磁盘/网络) │
│ ├── kube-state-metrics (K8s 对象状态: Pod 重启数/副本数) │
│ ├── kubelet/cadvisor (容器指标) │
│ └── 业务 Pod (ServiceMonitor/PodMonitor 自动发现) │
│ │ │
│ ├──> Alertmanager (告警分组/静默/路由 → 邮件/钉钉/飞书) │
│ └──> Grafana (可视化) ◄── Loki (日志, 另一数据源) │
└────────────────────────────────────────────────────────────┘让 Prometheus 抓取业务指标:
yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: myapp
namespace: monitoring # 注意 release 的 serviceMonitorSelector
labels:
release: monitoring # kube-prometheus-stack 默认按此标签选择
spec:
selector:
matchLabels: { app: myapp }
endpoints:
- port: http-metrics
interval: 15s6.3 Grafana 实战
- 常用社区看板 ID:
15759(K8s 集群总览)、1860(Node Exporter Full)、13639(Loki 日志)。 - 导入:Grafana → Dashboards → Import → 输入 ID。
- 配置多数据源:Prometheus + Loki + Alertmanager。
6.4 告警规则与 Alertmanager
自定义 PrometheusRule(会被自动加载):
yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerts
namespace: monitoring
labels:
release: monitoring
spec:
groups:
- name: app.rules
rules:
- alert: PodRestartTooOften
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 1小时内重启超过5次"
- alert: NodeMemoryPressure
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 10m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 内存使用率超过 85%"
- alert: PvcAlmostFull
expr: kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} 使用率超 85%"Alertmanager 通知(以 Webhook/钉钉为例)通过 Helm values:
yaml
alertmanager:
config:
route:
receiver: dingtalk
group_by: ["namespace", "alertname"]
group_wait: 30s
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: dingtalk-critical
receivers:
- name: dingtalk
webhook_configs:
- url: http://dingtalk-webhook:8060/dingtalk/ops/send6.5 Loki 日志
轻量日志系统,只索引标签不索引全文,与 Grafana 无缝集成:
bash
helm repo add grafana https://grafana.github.io/helm-charts
helm install loki grafana/loki-stack -n monitoring \
--set promtail.enabled=true \
--set loki.persistence.enabled=true \
--set loki.persistence.storageClassName=longhorn \
--set loki.persistence.size=20GiGrafana 添加 Loki 数据源后,在 Explore 中用 LogQL 查询:
logql
{namespace="prod", app="web"} |= "ERROR"
{namespace="prod"} |~ "timeout|refused" | json | line_format "{{.msg}}"
rate({namespace="prod"} |= "ERROR" [5m])Promtail(采集端)以 DaemonSet 运行,自动读取 /var/log/pods 并附加 K8s 元数据标签。
6.6 常用 PromQL 速查
| 需求 | PromQL |
|---|---|
| 节点 CPU 使用率 | 100 - avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100 |
| 节点内存使用率 | (1 - node_memory_MemAvailable_bytes/node_memory_MemTotal_bytes)*100 |
| Pod CPU(核) | sum by(namespace,pod)(rate(container_cpu_usage_seconds_total{container!="",container!="POD"}[5m])) |
| Pod 内存 | sum by(namespace,pod)(container_memory_working_set_bytes{container!="",container!="POD"}) |
| Pod 重启次数 | increase(kube_pod_container_status_restarts_total[1h]) |
| Deployment 不可用副本 | kube_deployment_spec_replicas - kube_deployment_status_replicas_available |
| PVC 使用率 | kubelet_volume_stats_used_bytes/kubelet_volume_stats_capacity_bytes*100 |
| 容器限流率 | rate(container_cpu_cfs_throttled_periods_total[5m])/rate(container_cpu_cfs_periods_total[5m]) |
| HTTP QPS | sum(rate(http_requests_total[5m])) |
| P99 延迟 | histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by(le)) |
6.7 本章小结与面试题
Q1:metrics-server 和 Prometheus 的区别? metrics-server 只存最近的 CPU/内存(内存态,给 HPA 和 kubectl top 用);Prometheus 是完整时序库,长期存储 + 告警 + 丰富指标。HPA v2 自定义指标需要 prometheus-adapter 把 Prometheus 指标转成 custom.metrics.k8s.io API。
Q2:如何监控自己应用的指标? 应用暴露 /metrics(client_golang/prom-client 等)→ 创建 ServiceMonitor(标签匹配 release)→ Grafana 出图 + PrometheusRule 告警。
第 7 章 应用打包与发布
7.1 Helm 深入
Chart 结构:
mychart/
├── Chart.yaml # 元数据: name/version/appVersion/dependencies
├── values.yaml # 默认配置(可覆盖)
├── templates/ # 模板目录
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # 命名模板(define/template/include)
│ ├── NOTES.txt # 安装后提示
│ └── tests/
├── charts/ # 依赖子 chart
└── crds/ # 先于模板安装的 CRDChart.yaml 示例:
yaml
apiVersion: v2
name: myapp
description: My Application
type: application
version: 1.2.0 # chart 版本(每次改动递增)
appVersion: "2.3.1" # 应用版本
dependencies:
- name: redis
version: "18.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled模板与函数:
yaml
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
{{- if .Values.env }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}常用内置对象与函数:
| 类别 | 示例 |
|---|---|
| 内置对象 | .Values、.Chart、.Release(Name/Namespace/Revision)、.Capabilities |
| 字符串 | quote、upper、trunc 63(名称超长截断)、trimSuffix "-" |
| 默认值 | default "nginx" .Values.image |
| 控制流 | if/else、range、with |
| 管道 | .Values.labels | toYaml | nindent 4 |
| 调试 | helm template、helm install --dry-run --debug |
Hooks(在 release 生命周期特定点执行):
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade # 升级前跑迁移
"helm.sh/hook-weight": "5" # 多 hook 排序
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: myapp-migrator:{{ .Chart.AppVersion }}钩子类型:pre-install / post-install / pre-upgrade / post-upgrade / pre-delete / post-delete / pre-rollback / post-rollback / test。
常用命令:
bash
helm repo add bitnami https://charts.bitnami.com/bitnami
helm search repo nginx --versions
helm show values bitnami/nginx # 查看可配置项
helm install myapp ./mychart -n prod --create-namespace \
--set image.tag=2.3.1 -f values-prod.yaml
helm list -A
helm status myapp -n prod
helm history myapp -n prod
helm upgrade myapp ./mychart -n prod --set image.tag=2.3.2
helm rollback myapp 3 -n prod # 回滚到 revision 3
helm diff upgrade myapp ./mychart -n prod # 需 helm-diff 插件
helm template ./mychart --debug | less # 纯渲染调试
helm uninstall myapp -n prod
helm lint ./mychart # 静态检查
helm package ./mychart # 打包成 tgz私有仓库(以 Harbor Chart Museum / OCI 为例):
bash
# OCI 方式(Helm 3.8+ 推荐,Harbor 2.x 支持)
helm registry login harbor.example.com -u admin
helm package ./mychart
helm push mychart-1.2.0.tgz oci://harbor.example.com/charts
helm install myapp oci://harbor.example.com/charts/mychart --version 1.2.0最佳实践:values 分层(
values.yaml默认 +values-prod.yaml环境差异);敏感信息不入 values(用 ExternalSecret 或--set+ CI 密钥);用--atomic --timeout做 CI 部署(失败自动回滚);镜像 tag 不用latest。
7.2 Kustomize 深入
Kustomize 无模板、基于"YAML 叠加":kubectl 内置(kubectl apply -k)。
myapp/
├── base/ # 通用配置
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
└── prod/
├── kustomization.yaml
├── patch-resources.yaml
└── ingress.yamlbase/kustomization.yaml:
yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app: myapp
images:
- name: myapp
newTag: 2.3.1overlays/prod/kustomization.yaml:
yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prod
namePrefix: prod-
resources:
- ../../base
- ingress.yaml
images:
- name: myapp
newTag: 2.3.1
replicas:
- name: myapp
count: 6
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=warn
behavior: merge
secretGenerator:
- name: db-secret
envs:
- db.env
patches:
- path: patch-resources.yamlstrategic merge patch(patch-resources.yaml):
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
containers:
- name: myapp
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: "2", memory: 4Gi }JSON6902 patch(精确到路径):
yaml
# kustomization.yaml 中
patches:
- target:
kind: Deployment
name: myapp
patch: |-
- op: replace
path: /spec/template/spec/containers/0/imagePullPolicy
value: Alwayscomponents(1.24+,可复用的横切配置,如"加监控注解"):
yaml
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- target:
kind: Deployment
patch: |-
- op: add
path: /spec/template/metadata/annotations
value:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"yaml
# overlay 中引用
components:
- ../../components/monitoring使用:
bash
kubectl apply -k overlays/prod
kubectl kustomize overlays/prod | less # 只渲染不应用
kubectl diff -k overlays/prod # 与线上对比7.3 Helm vs Kustomize 对比
| 维度 | Helm | Kustomize |
|---|---|---|
| 范式 | 模板引擎(Go template) | 声明式叠加(无模板) |
| 学习曲线 | 较陡(模板语法+函数) | 平缓(纯 YAML) |
| 发布管理 | 有(release 历史/回滚/hook) | 无(纯 apply,回滚靠 Git) |
| 参数化 | values 文件覆盖 | overlay + patch |
| 生态 | 海量现成 chart | 需自己写 |
| kubectl 内置 | 否 | 是(apply -k) |
| 复杂逻辑 | 强(if/range/hook) | 弱(靠 patch 组合) |
常见组合用法:第三方组件用 Helm(先 helm template 渲染成 YAML),再用 Kustomize 做环境差异化 patch——这正是 RKE2 管理其内置组件(HelmChart/HelmChartConfig)的思路。GitOps(ArgoCD/Flux)对两者都有原生支持。
7.4 本章小结与面试题
Q1:Helm 回滚的原理? 每次 release 操作存一份完整 manifest 到 Secret(sh.helm.release.v1.xxx),helm rollback 把指定 revision 的 manifest 重新 apply。注意:它只管 K8s 资源,不回滚数据(如 PVC/DB 变更)。
Q2:hook 执行失败会怎样? release 标记为失败;配合 --atomic 可自动回滚。pre-upgrade hook 失败则升级中止。
第 8 章 常见面试题与进阶排错套路
8.1 进阶面试题精选
Q1:Pod 一直 Pending 的排查步骤?
kubectl describe pod → Events
├─ FailedScheduling: insufficient cpu/memory → 资源不足, 扩容或降 requests
├─ node(s) had untolerated taint → 缺 toleration
├─ didn't match Pod's node affinity → 标签/亲和性不满足
├─ persistentvolumeclaim not found / pvc pending → 存储未就绪
└─ 无 Events → 检查 scheduler 是否运行, Pod 是否卡在卷绑定Q2:Pod CrashLoopBackOff 排查?kubectl logs --previous 看崩溃前日志(关键!);describe pod 看退出码(1=应用错误,137=OOMKill,143=被 SIGTERM);探针配置是否过严;依赖服务/配置是否就绪;镜像入口命令是否正确。
Q3:Service 四层负载均衡是怎么做到的?长连接怎么办? kube-proxy 的 iptables/IPVS 是连接级 DNAT(首次连接选后端,conntrack 记住后续包),对长连接(gRPC/WebSocket)会造成负载不均。方案:用七层 LB(Ingress/gRPC 感知 LB 如 Envoy/Linkerd)或 headless Service + 客户端 LB。
Q4:如何不停机更新一个 Deployment? RollingUpdate 策略(默认 maxSurge=25%、maxUnavailable=25%)+ readinessProbe(就绪才接流量)+ preStop hook 或优雅退出(先停接流量再处理完在途请求)+ PDB(保证驱逐/升级期间最少副本)。
Q5:etcd 在 K8s 中的角色?如何备份? 唯一持久化存储,存所有集群状态。RKE2 默认每 12 小时自动快照(etcd-snapshot-schedule-cron),手动:
bash
rke2 etcd-snapshot save --name manual-backup
ls /var/lib/rancher/rke2/server/db/snapshots/
# 恢复见第一部分《RKE2 控制面仲裁丢失灾难恢复》文档Q6:一个命名空间删不掉(Terminating)? 有资源残留或 Finalizer 卡住:
bash
kubectl get all -n stuck-ns # 找残留资源
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get -n stuck-ns 2>/dev/null
kubectl get ns stuck-ns -o json | jq '.spec.finalizers'
# 万不得已: 清空 finalizers(有风险, 会跳过资源清理)
kubectl replace --raw "/api/v1/namespaces/stuck-ns/finalize" -f <(kubectl get ns stuck-ns -o json | jq '.spec.finalizers=[]')Q7:生产集群升级策略? 先升 control-plane 再升 worker(版本偏差 ≤1 个小版本);kubectl drain 腾空节点(配合 PDB);逐台升级、观察、再下一台;RKE2 用 system-upgrade-controller 自动化;升级前 etcd 快照 + 备份证书。
8.2 进阶排错套路总表
| 症状 | 第一反应 | 深入工具 |
|---|---|---|
| Pod Pending | describe pod 看 Events | 查 quota / taint / pvc |
| CrashLoopBackOff | logs --previous + 退出码 | 探针、依赖、OOM |
| ImagePullBackOff | describe 看错误 | registry 认证(imagePullSecret)、网络、tag 不存在 |
| Service 不通 | endpoints 是否有 IP | kube-proxy 规则、CNI、NetworkPolicy |
| DNS 解析失败 | 进 Pod nslookup | CoreDNS 日志、resolv.conf、ndots:5 导致的多次查询 |
| 节点 NotReady | describe node 看 Conditions | kubelet 日志(journalctl -u rke2-agent)、磁盘压力、网络插件 |
| 性能抖动 | top node/pod + Prometheus | CPU throttle(limit 过低)、磁盘 IO、conntrack 表 |
| 证书错误 | openssl s_client 验证 | RKE2 证书 1 年到期:rke2 certificate rotate |
| etcd 慢/告警 | etcdctl endpoint status | 磁盘延迟(fdatasync >10ms 告警)、碎片整理 |
8.3 排错通用方法论
1. 定界: 是单个 Pod / 单个节点 / 整个命名空间 / 全集群?
2. 看面: kubectl get -o wide → describe → logs (--previous)
3. 看底: 节点 journalctl -u rke2-server/-u rke2-agent
crictl ps / crictl logs(容器运行时层)
4. 看网: 容器内 ping/curl/nslookup → Service → Endpoints → kube-proxy → CNI
5. 看数: Prometheus/Grafana 历史曲线, 找时间相关性(变更? 流量突增? 证书到期?)
6. 复现: 最小化复现(临时 busybox Pod) → 二分法定位黄金命令合集:
bash
kubectl get events -A --sort-by=.lastTimestamp | tail -20
kubectl get pods -A -o wide | grep -vE 'Running|Completed'
kubectl describe node <node> | sed -n '/Conditions/,/Addresses/p'
journalctl -u rke2-agent --since "1 hour ago" | grep -iE 'error|fail'
crictl ps -a | grep -v Running本部分完。下一部分将围绕 K8s 辅助工具生态(Rancher、K9s、ArgoCD、cert-manager、备份工具 Velero 等)展开,把本部分的进阶能力落到日常运维效率上。