找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1385

积分

0

好友

177

主题
发表于 2026-2-12 15:04:27 | 查看: 33| 回复: 0

一、概述

1.1 背景介绍

K8s集群在测试环境跑得好好的,一上生产就各种花式故障——etcd磁盘写满集群瘫痪、OOMKill连锁反应打崩整个命名空间、滚动更新配错一个参数导致服务全量中断。这些问题不是理论推演,是真金白银的生产事故,每一个都对应着一次P0/P1级别的故障复盘。

K8s的复杂度在于它是一个分布式系统的分布式系统。etcd是分布式KV存储、kube-apiserver是无状态API网关、kubelet是节点Agent、CNI/CSI/CRI各种插件各管一摊。任何一个环节出问题,都可能引发级联故障。更麻烦的是,很多问题在小规模集群上根本不会暴露,节点数一过50、Pod数一过2000,各种边界条件就开始冒头。

这篇文章整理了10个在生产环境运维中高频出现的故障场景,每个问题都按照 现象 -> 原因分析 -> 排查路径 -> 解决方案 的结构展开,附带可直接复用的配置和命令。

1.2 技术特点

  • 故障驱动:每个问题都来自真实生产场景,不是实验室里造出来的
  • 全链路覆盖:从etcd存储层到应用层,从网络到安全,覆盖K8s核心组件
  • 可复现可验证:所有排查命令和修复方案都经过K8s 1.32环境验证
  • 防御性配置:不只是修复问题,更重要的是如何在问题发生前做好防护

1.3 适用场景

  • 场景一:生产集群运维,需要快速定位和修复常见故障
  • 场景二:集群初始化阶段,需要提前规避已知的配置陷阱
  • 场景三:SRE团队建设,需要建立标准化的故障排查Runbook
  • 场景四:K8s升级前的风险评估,需要了解各版本的行为差异

1.4 环境要求

组件 版本要求 说明
Kubernetes 1.32+ 2026年主流生产版本
etcd 3.5.17+ 集群状态存储
containerd 2.0+ 容器运行时
操作系统 Ubuntu 24.04 LTS 内核6.8+,cgroup v2默认启用
Cilium 1.17+ 推荐CNI插件
cert-manager 1.17+ 证书自动管理

二、详细步骤

2.1 问题一:etcd磁盘性能不足导致集群不可用

2.1.1 现象

kube-apiserver响应变慢,kubectl 命令超时,最终所有API请求返回 context deadline exceeded。集群内的Pod调度停滞,已运行的Pod不受影响但无法进行任何变更操作。etcd日志中大量出现 took too longslow fdatasync 警告。

2.1.2 原因分析

etcd是K8s的“大脑”,所有集群状态都存在etcd里。etcd使用WAL(Write-Ahead Log)保证数据一致性,每次写操作都要先写WAL再fsync到磁盘。如果磁盘I/O延迟超过etcd的心跳间隔(默认100ms),就会触发leader选举,频繁选举直接导致集群不可用。

常见的磁盘性能不足原因:

  • etcd和其他I/O密集型服务共享磁盘(比如跑在同一块SATA HDD上)
  • 云厂商的普通云盘IOPS不够(通常需要 > 3000 IOPS)
  • 虚拟化环境下的I/O调度器配置不当
  • etcd数据目录和WAL目录没有分离

2.1.3 排查路径

# 检查 etcd 的 fsync 延迟(核心指标)
etcdctl endpoint status --write-out=table

# 查看 etcd 慢请求日志
journalctl -u etcd | grep "slow fdatasync" | tail -20

# 检查磁盘 I/O 延迟
# WAL fsync 延迟应该 < 10ms,超过 50ms 就很危险
iostat -x 1 5

# 用 fio 测试磁盘实际性能
fio --rw=write --ioengine=sync --fdatasync=1 --directory=/var/lib/etcd \
    --size=22m --bs=2300 --name=etcd-benchmark

# 检查 etcd 的 Prometheus 指标
# etcd_disk_wal_fsync_duration_seconds 的 p99 应该 < 10ms
# etcd_disk_backend_commit_duration_seconds 的 p99 应该 < 25ms
curl -s http://localhost:2379/metrics | grep etcd_disk_wal_fsync_duration

2.1.4 解决方案

# 方案一:使用高性能 SSD(推荐 NVMe)
# 云环境下选择 IOPS >= 3000 的 SSD 云盘
# AWS: gp3 (配置 3000+ IOPS) 或 io2
# 阿里云: ESSD PL1 或更高

# 方案二:分离 WAL 目录到独立磁盘
# 修改 etcd 启动参数
--wal-dir=/mnt/etcd-wal  # 独立的高速 SSD

# 方案三:调整 I/O 调度器(适用于 SSD)
echo none > /sys/block/nvme0n1/queue/scheduler

# 方案四:给 etcd 进程设置 I/O 优先级
ionice -c2 -n0 -p $(pgrep etcd)

# 方案五:定期压缩和碎片整理
# etcd 数据库大小默认限制 2GB,超过会拒绝写入
etcdctl compact $(etcdctl endpoint status --write-out=json | jq '.[0].Status.header.revision')
etcdctl defrag --endpoints=https://127.0.0.1:2379
etcdctl alarm disarm

关键指标阈值

指标 健康值 警告值 危险值
WAL fsync p99 < 10ms 10-50ms > 50ms
Backend commit p99 < 25ms 25-100ms > 100ms
DB 大小 < 4GB 4-6GB > 6GB
Leader 选举次数/h 0 1-2 > 3

2.2 问题二:资源 Limit 设置不当引发 OOMKill 连锁反应

2.2.1 现象

某个Pod被OOMKill,Deployment控制器立即拉起新Pod,新Pod启动时内存飙升又被Kill,反复重启。更糟糕的是,同节点上的其他Pod因为内存压力也开始被驱逐,形成雪崩效应。kubectl get pods 看到一片 CrashLoopBackOffEvicted

2.2.2 原因分析

K8s的资源管理分Request和Limit两层:

  • Request:调度器用来决定Pod放在哪个节点,是“最低保障”
  • Limit:kubelet用cgroup强制限制的上限,超过就OOMKill

常见的错误配置模式:

  1. Limit设得太低:Java应用的JVM堆 + 非堆 + 线程栈 + NIO DirectBuffer加起来超过Limit
  2. Request远小于Limit(过度超卖):节点实际内存不够,触发节点级OOM
  3. 没设Limit:BestEffort QoS,节点内存紧张时第一个被干掉
  4. 忽略了init container的资源消耗:init container和主容器的Request取最大值

2.2.3 排查路径

# 查看 Pod 被 OOMKill 的详细信息
kubectl describe pod <pod-name> -n <namespace> | grep -A 5 "Last State"

# 查看节点内存压力
kubectl describe node <node-name> | grep -A 10 "Conditions"

# 查看节点上所有 Pod 的资源使用情况
kubectl top pods -n <namespace> --sort-by=memory

# 查看被驱逐的 Pod
kubectl get pods -n <namespace> --field-selector=status.phase=Failed \
  -o jsonpath='{range .items
  • }{.metadata.name}{"\t"}{.status.reason}{"\n"}{end}' # 查看 cgroup 内存限制和实际使用(节点上执行) # cgroup v2 路径 cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod<uid>.slice/memory.max cat /sys/fs/cgroup/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod<uid>.slice/memory.current # 查看 OOM 事件 dmesg | grep -i "oom\|killed process" | tail -20
  • 2.2.4 解决方案

    # 正确的资源配置模板
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: app-example
    spec:
      template:
        spec:
          containers:
          - name: app
            resources:
              requests:
                memory: "512Mi"  # 基于实际 p50 使用量
                cpu: "250m"
              limits:
                memory: "1Gi"    # Request 的 1.5-2 倍,留足余量
                cpu: "1000m"     # CPU 建议不设 Limit 或设较大值
            # Java 应用特别注意:JVM 参数要和 cgroup 限制对齐
            env:
            - name: JAVA_OPTS
              value: "-XX:MaxRAMPercentage=75.0 -XX:+UseContainerSupport"
    # 配置 LimitRange 防止"裸奔" Pod
    apiVersion: v1
    kind: LimitRange
    metadata:
      name: default-limits
      namespace: production
    spec:
      limits:
      - default:             # 默认 Limit
          memory: "512Mi"
          cpu: "500m"
        defaultRequest:      # 默认 Request
          memory: "256Mi"
          cpu: "100m"
        max:                 # 单个容器最大值
          memory: "4Gi"
          cpu: "4000m"
        min:                 # 单个容器最小值
          memory: "64Mi"
          cpu: "50m"
        type: Container
    # 配置 ResourceQuota 防止命名空间资源耗尽
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: namespace-quota
      namespace: production
    spec:
      hard:
        requests.memory: "32Gi"
        limits.memory: "64Gi"
        requests.cpu: "16"
        limits.cpu: "32"
        pods: "100"

    CPU Limit的争议:2026年的主流实践是 不设CPU Limit或设一个很大的值。原因是CPU是可压缩资源,超过Limit不会被Kill,只会被throttle。而CFS throttle会导致延迟毛刺,对延迟敏感的服务影响很大。只设CPU Request保证调度公平性就够了。


    2.3 问题三:滚动更新配置错误导致服务中断

    2.3.1 现象

    执行 kubectl apply 更新Deployment后,服务出现短暂或持续的不可用。监控上看到请求成功率骤降,部分请求返回502/503。旧Pod已经被终止,新Pod还没Ready,中间出现了可用实例数为0的窗口期。

    2.3.2 原因分析

    滚动更新的核心参数是 maxSurgemaxUnavailable,配合 readinessProbeterminationGracePeriodSeconds 一起工作。任何一个配错都可能导致服务中断:

    1. maxUnavailable设太大:比如replicas=3且maxUnavailable=2,更新时只剩1个Pod扛流量
    2. readinessProbe缺失或配置不当:新Pod还没准备好就被加入Service Endpoints
    3. terminationGracePeriodSeconds太短:旧Pod还在处理请求就被强制Kill
    4. preStop hook缺失:Pod收到SIGTERM后立即停止接收请求,但Endpoints还没更新,流量还在往这个Pod发
    5. 启动时间过长:应用启动需要60秒,但initialDelaySeconds只设了5秒

    2.3.3 排查路径

    # 查看 Deployment 的滚动更新策略
    kubectl get deployment <name> -o jsonpath='{.spec.strategy}' | jq .
    
    # 查看更新过程中的事件
    kubectl rollout status deployment/<name> -n <namespace>
    kubectl describe deployment <name> -n <namespace> | grep -A 20 "Events"
    
    # 查看 ReplicaSet 的变化过程
    kubectl get rs -n <namespace> -l app=<name> --sort-by=.metadata.creationTimestamp
    
    # 检查 Endpoints 变化(关键:看是否有空窗期)
    kubectl get endpoints <service-name> -n <namespace> -w
    
    # 查看 Pod 的 readiness 状态变化
    kubectl get pods -n <namespace> -l app=<name> -w

    2.3.4 解决方案

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
    spec:
      replicas: 3
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1          # 最多多出 1 个 Pod
          maxUnavailable: 0    # 不允许任何 Pod 不可用(零停机的关键)
      template:
        spec:
          terminationGracePeriodSeconds: 60  # 给足优雅关闭时间
          containers:
          - name: web
            # readinessProbe:决定 Pod 何时接收流量
            readinessProbe:
              httpGet:
                path: /healthz
                port: 8080
              initialDelaySeconds: 10  # 根据应用实际启动时间调整
              periodSeconds: 5
              failureThreshold: 3
              successThreshold: 1
            # livenessProbe:决定 Pod 何时被重启
            # 注意:livenessProbe 不要和 readinessProbe 用同一个端点
            livenessProbe:
              httpGet:
                path: /livez
                port: 8080
              initialDelaySeconds: 30  # 必须大于应用最长启动时间
              periodSeconds: 10
              failureThreshold: 3
            # startupProbe:K8s 1.20+ 推荐用于慢启动应用
            startupProbe:
              httpGet:
                path: /healthz
                port: 8080
              failureThreshold: 30     # 30 * 2s = 60s 启动窗口
              periodSeconds: 2
            lifecycle:
              preStop:
                exec:
                  # 关键:等待 Endpoints 控制器移除本 Pod
                  # 没有这个 sleep,会出现流量发到已经开始关闭的 Pod
                  command: ["sh", "-c", "sleep 10"]

    零停机更新的核心公式maxUnavailable=0 + 正确的 readinessProbe + preStop sleep > Endpoints传播延迟(通常5-10秒)。


    2.4 问题四:PDB缺失导致节点维护时服务雪崩

    2.4.1 现象

    执行 kubectl drain 进行节点维护时,节点上的所有Pod被同时驱逐。如果某个服务的多个副本恰好都调度在这个节点上,服务直接不可用。更极端的情况:集群自动伸缩器(Cluster Autoscaler)缩容时自动drain节点,半夜三点服务挂了,没人知道为什么。

    2.4.2 原因分析

    kubectl drain 默认行为是驱逐节点上所有非DaemonSet的Pod。如果没有PodDisruptionBudget(PDB),drain操作不会检查服务的可用副本数,直接一锅端。

    PDB的作用是告诉K8s:“这个服务至少要保持N个副本可用”或“最多允许N个副本同时不可用”。drain操作会尊重PDB的约束,逐个驱逐Pod而不是一次性全部干掉。

    没有PDB的常见后果:

    • 节点维护时服务中断
    • Cluster Autoscaler缩容导致服务不可用
    • 节点故障自动修复(如AWS ASG替换实例)时的级联影响

    2.4.3 排查路径

    # 检查集群中哪些 Deployment 没有对应的 PDB
    kubectl get deployments -A -o json | jq -r '.items[] | select(.spec.replicas > 1) | "\(.metadata.namespace)/\(.metadata.name)"' > /tmp/deployments.txt
    kubectl get pdb -A -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.spec.selector.matchLabels)"' > /tmp/pdbs.txt
    # 对比两个列表,找出缺失 PDB 的 Deployment
    
    # 查看现有 PDB 的状态
    kubectl get pdb -A
    kubectl describe pdb <pdb-name> -n <namespace>
    
    # 模拟 drain 操作(不实际执行)
    kubectl drain <node-name> --dry-run=client --ignore-daemonsets --delete-emptydir-data
    
    # 检查 Pod 的反亲和性配置(避免同一服务的 Pod 扎堆在一个节点)
    kubectl get pod -n <namespace> -l app=<name> -o wide

    2.4.4 解决方案

    # PDB 配置:保证至少 N 个副本可用
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: web-app-pdb
      namespace: production
    spec:
      minAvailable: 2        # 至少保持 2 个副本可用
      # 或者用 maxUnavailable: 1  # 最多允许 1 个副本不可用
      selector:
        matchLabels:
          app: web-app
    # 配合 Pod 反亲和性,避免副本扎堆
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
    spec:
      replicas: 3
      template:
        spec:
          affinity:
            podAntiAffinity:
              # 强制反亲和:同一服务的 Pod 不能在同一节点
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values: ["web-app"]
                topologyKey: kubernetes.io/hostname
              # 如果节点数不够,用软反亲和降级
              # preferredDuringSchedulingIgnoredDuringExecution:
              # - weight: 100
              #   podAffinityTerm:
              #     labelSelector:
              #       matchExpressions:
              #       - key: app
              #         operator: In
              #         values: ["web-app"]
              #     topologyKey: kubernetes.io/hostname

    PDB配置原则minAvailablemaxUnavailable 的值要根据服务的实际副本数来定。3副本的服务设 minAvailable: 2maxUnavailable: 1。单副本服务设PDB没有意义(drain会卡住永远完不成)。


    2.5 问题五:RBAC权限过大引发安全事故

    2.5.1 现象

    开发人员使用CI/CD的ServiceAccount意外删除了生产命名空间的核心ConfigMap,导致依赖该ConfigMap的所有Pod重启后无法启动。排查发现该ServiceAccount绑定了 cluster-admin 角色——一个拥有集群所有资源所有操作权限的超级角色。

    2.5.2 原因分析

    K8s的RBAC模型由四个对象组成:Role、ClusterRole、RoleBinding、ClusterRoleBinding。权限过大的常见原因:

    1. 图省事绑cluster-admin:开发初期为了方便,给所有ServiceAccount都绑了cluster-admin
    2. ClusterRoleBinding滥用:本来只需要某个命名空间的权限,却用了ClusterRoleBinding授予全集群权限
    3. 通配符权限resources: ["*"]verbs: ["*"] 一把梭
    4. ServiceAccount Token泄露:默认挂载的Token被应用日志打印或被攻击者获取
    5. 没有定期审计:权限只增不减,离职员工的权限没有回收

    2.5.3 排查路径

    # 查看集群中所有 ClusterRoleBinding 到 cluster-admin 的绑定
    kubectl get clusterrolebindings -o json | \
      jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name + " -> " + (.subjects[]? | .kind + "/" + .name)'
    
    # 查看某个 ServiceAccount 的实际权限
    kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<sa-name>
    
    # 查看哪些 Pod 挂载了 ServiceAccount Token
    kubectl get pods -A -o json | \
      jq -r '.items[] | select(.spec.automountServiceAccountToken != false) | "\(.metadata.namespace)/\(.metadata.name) -> \(.spec.serviceAccountName)"'
    
    # 审计日志中查找危险操作
    # 需要开启 API Server 审计日志
    grep '"verb":"delete"' /var/log/kubernetes/audit.log | jq -r '.user.username + " deleted " + .objectRef.resource + "/" + .objectRef.name'

    2.5.4 解决方案

    # 最小权限 Role 示例:CI/CD 部署专用
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: cicd-deployer
      namespace: production
    rules:
      # 只允许操作 Deployment 和 Service
      - apiGroups: ["apps"]
        resources: ["deployments"]
        verbs: ["get", "list", "watch", "update", "patch"]
      - apiGroups: [""]
        resources: ["services"]
        verbs: ["get", "list", "watch"]
      # 只允许读取 ConfigMap 和 Secret,不允许删除
      - apiGroups: [""]
        resources: ["configmaps", "secrets"]
        verbs: ["get", "list", "watch"]
      # 允许查看 Pod 状态(排查部署问题)
      - apiGroups: [""]
        resources: ["pods", "pods/log"]
        verbs: ["get", "list", "watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: cicd-deployer-binding
      namespace: production
    subjects:
    - kind: ServiceAccount
      name: cicd-sa
      namespace: production
    roleRef:
      kind: Role
      name: cicd-deployer
      apiGroup: rbac.authorization.k8s.io
    # 禁止自动挂载 ServiceAccount Token(K8s 1.24+ 默认不再自动创建 Secret)
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: app-sa
      namespace: production
    automountServiceAccountToken: false  # 除非应用确实需要调用 K8s API

    RBAC黄金法则:永远从零权限开始,按需添加。用 Role + RoleBinding(命名空间级)而不是 ClusterRole + ClusterRoleBinding(集群级),除非确实需要跨命名空间操作。


    2.6 问题六:ConfigMap/Secret热更新踩坑

    2.6.1 现象

    修改了ConfigMap的内容,kubectl get configmap 确认已经更新,但应用的行为完全没变。等了半天发现Pod里的配置文件还是旧的。更隐蔽的情况:用 subPath 挂载的ConfigMap永远不会自动更新,排查了几个小时才发现这个“特性”。

    2.6.2 原因分析

    ConfigMap/Secret挂载到Pod的更新机制有几个关键细节:

    1. Volume挂载方式:以Volume形式挂载的ConfigMap,kubelet会定期同步更新(默认同步周期60秒 + 一定的缓存TTL),但不是实时的
    2. subPath挂载不更新:使用 subPath 挂载的文件 永远不会自动更新,这是K8s的设计行为,不是Bug
    3. 环境变量方式不更新:通过 envFromenv.valueFrom 引用的ConfigMap/Secret,Pod运行期间不会更新,必须重启Pod
    4. 应用不感知文件变化:即使文件更新了,应用如果没有watch文件变化的逻辑,也不会重新加载配置
    5. Immutable ConfigMap/Secret:K8s 1.21+支持设置 immutable: true,设置后无法修改,需要创建新的ConfigMap并更新Pod引用

    2.6.3 排查路径

    # 确认 ConfigMap 是否已更新
    kubectl get configmap <name> -n <namespace> -o yaml
    
    # 检查 Pod 内的实际文件内容
    kubectl exec <pod-name> -n <namespace> -- cat /path/to/config/file
    
    # 检查挂载方式是否使用了 subPath
    kubectl get pod <pod-name> -n <namespace> -o json | \
      jq '.spec.containers[].volumeMounts[] | select(.subPath != null)'
    
    # 检查 ConfigMap 是否设置了 immutable
    kubectl get configmap <name> -n <namespace> -o jsonpath='{.immutable}'
    
    # 查看 kubelet 的同步周期配置
    # 默认 --sync-frequency=1m
    ps aux | grep kubelet | grep sync-frequency

    2.6.4 解决方案

    # 方案一:避免 subPath,使用目录挂载(推荐)
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          containers:
          - name: app
            volumeMounts:
            # 正确:挂载整个目录,支持自动更新
            - name: config-volume
              mountPath: /etc/app/config
            # 错误:subPath 挂载不会自动更新
            # - name: config-volume
            #   mountPath: /etc/app/config/app.yaml
            #   subPath: app.yaml
          volumes:
          - name: config-volume
            configMap:
              name: app-config
    # 方案二:使用 configmap hash 注解触发滚动更新
    # 在 CI/CD 流水线中计算 ConfigMap 的 hash 并写入 Pod 注解
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        metadata:
          annotations:
            # ConfigMap 内容变化时 hash 值变化,触发 Pod 滚动更新
            checksum/config: "sha256-of-configmap-content"
    # 方案三:手动触发滚动重启(最简单粗暴)
    kubectl rollout restart deployment/<name> -n <namespace>
    # 方案四:使用 Reloader 自动监听 ConfigMap 变化并重启 Pod
    # 安装 stakater/Reloader
    # helm install reloader stakater/reloader -n kube-system
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      annotations:
        reloader.stakater.com/auto: "true"  # 自动监听关联的 ConfigMap/Secret

    最佳实践:对于需要热更新的配置,应用层面实现文件watch机制(如Go的fsnotify、Java的Spring Cloud Config)。对于不支持热更新的应用,使用Reloader或CI/CD流水线中的hash注解方案。


    2.7 问题七:节点NotReady排查(kubelet/容器运行时问题)

    2.7.1 现象

    kubectl get nodes 显示某个节点状态为 NotReady,该节点上的Pod在 node.kubernetes.io/not-ready 容忍时间(默认300秒)后被驱逐到其他节点。如果多个节点同时NotReady,可能触发大规模Pod迁移,进而压垮剩余健康节点。

    2.7.2 原因分析

    节点NotReady的本质是kubelet停止向API Server上报心跳。kube-controller-manager中的NodeLifecycleController在超过 node-monitor-grace-period(默认40秒)没收到心跳后,将节点标记为NotReady。

    常见原因按频率排序:

    1. kubelet进程挂了:OOM、证书过期、配置错误
    2. containerd/CRI运行时故障:containerd进程卡死、shim进程泄漏
    3. 系统资源耗尽:磁盘满(特别是/var/lib/kubelet和/var/lib/containerd)、inode耗尽、内存不足
    4. 内核问题:内核Bug导致的soft lockup、cgroup泄漏
    5. 网络问题:节点到API Server的网络中断
    6. PLEG(Pod Lifecycle Event Generator)超时:containerd响应慢导致PLEG健康检查失败

    2.7.3 排查路径

    # 第一步:查看节点状态和 Conditions
    kubectl describe node <node-name> | grep -A 20 "Conditions"
    
    # 第二步:SSH 到节点,检查 kubelet 状态
    systemctl status kubelet
    journalctl -u kubelet --since "30 minutes ago" | tail -100
    
    # 第三步:检查容器运行时
    systemctl status containerd
    crictl info
    crictl ps -a | head -20
    
    # 第四步:检查系统资源
    df -h                         # 磁盘空间
    df -i                         # inode 使用
    free -h                       # 内存
    dmesg | tail -50              # 内核日志
    
    # 第五步:检查 PLEG 状态
    journalctl -u kubelet | grep "PLEG" | tail -20
    
    # 第六步:检查证书是否过期
    openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates
    
    # 第七步:检查到 API Server 的连通性
    curl -k https://<apiserver-ip>:6443/healthz

    2.7.4 解决方案

    # 场景一:kubelet OOM 被 Kill
    # 检查 kubelet 内存使用
    journalctl -u kubelet | grep "oom\|killed"
    # 增加 kubelet 的系统预留
    # 修改 /var/lib/kubelet/config.yaml
    # systemReserved:
    #   memory: "1Gi"
    #   cpu: "500m"
    # kubeReserved:
    #   memory: "1Gi"
    #   cpu: "500m"
    systemctl restart kubelet
    
    # 场景二:containerd 卡死
    # 检查 containerd shim 进程数量
    ps aux | grep containerd-shim | wc -l
    # 如果 shim 进程数量异常多,可能存在泄漏
    # 重启 containerd(会导致节点上所有容器重启)
    systemctl restart containerd
    
    # 场景三:磁盘空间不足
    # 清理已退出的容器和未使用的镜像
    crictl rmi --prune
    # 清理 kubelet 的日志
    journalctl --vacuum-size=500M
    # 检查大文件
    du -sh /var/lib/containerd/*
    du -sh /var/log/*
    
    # 场景四:证书过期
    # K8s 1.32 的 kubelet 支持自动轮换证书
    # 确认 kubelet 配置中启用了证书轮换
    # rotateCertificates: true
    # 手动触发证书更新
    kubeadm certs renew all
    systemctl restart kubelet

    预防措施:配置节点级别的监控告警,在磁盘使用率 > 80%、内存使用率 > 90%、kubelet进程消失时立即告警。设置合理的 evictionHard 阈值,让kubelet在资源紧张时主动驱逐低优先级Pod而不是自己挂掉。


    2.8 问题八:DNS解析超时(ndots配置问题)

    2.8.1 现象

    应用访问外部域名(如 api.example.com)时偶发超时,延迟从正常的几毫秒飙升到5-15秒。抓包发现DNS查询被发送了多次,先查询 api.example.com.default.svc.cluster.localapi.example.com.svc.cluster.localapi.example.com.cluster.local,全部失败后才查询 api.example.com

    2.8.2 原因分析

    K8s默认给Pod的 /etc/resolv.conf 配置了 ndots:5,含义是:如果域名中的点号数量少于5个,就先在search域中搜索。

    # Pod 内默认的 /etc/resolv.conf
    nameserver 10.96.0.10
    search default.svc.cluster.local svc.cluster.local cluster.local
    options ndots:5

    api.example.com 只有2个点,少于5个,所以DNS解析器会依次尝试:

    1. api.example.com.default.svc.cluster.local -- 失败
    2. api.example.com.svc.cluster.local -- 失败
    3. api.example.com.cluster.local -- 失败
    4. api.example.com -- 成功

    每次失败的查询都要等待超时,4次查询变成了8次(A记录和AAAA记录各一次),延迟直接翻了好几倍。在CoreDNS负载高的时候,这个问题会被放大。

    2.8.3 排查路径

    # 查看 Pod 的 DNS 配置
    kubectl exec <pod-name> -n <namespace> -- cat /etc/resolv.conf
    
    # 在 Pod 内测试 DNS 解析时间
    kubectl exec <pod-name> -n <namespace> -- time nslookup api.example.com
    
    # 抓包分析 DNS 查询过程
    kubectl exec <pod-name> -n <namespace> -- tcpdump -i eth0 port 53 -nn -c 20
    
    # 查看 CoreDNS 的负载和延迟
    kubectl top pods -n kube-system -l k8s-app=kube-dns
    kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
    
    # 检查 CoreDNS 的 Prometheus 指标
    # coredns_dns_request_duration_seconds 的 p99
    # coredns_dns_responses_total 中 NXDOMAIN 的比例

    2.8.4 解决方案

    # 方案一:在 Pod 级别调整 ndots(推荐)
    apiVersion: apps/v1
    kind: Deployment
    spec:
      template:
        spec:
          dnsConfig:
            options:
            - name: ndots
              value: "2"        # 降低到 2,大部分外部域名不再走 search 域
            - name: single-request-reopen
              value: ""         # 避免 A 和 AAAA 查询使用同一个 socket 导致的竞争
          containers:
          - name: app
            # ...
    # 方案二:外部域名使用 FQDN(末尾加点)
    # 在应用配置中,外部域名统一使用 FQDN 格式
    # api.example.com.  (注意末尾的点)
    # 这样 DNS 解析器会直接查询,不走 search 域
    # 方案三:优化 CoreDNS 配置
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: coredns
      namespace: kube-system
    data:
      Corefile: |
        .:53 {
            errors
            health {
                lameduck 5s
            }
            ready
            kubernetes cluster.local in-addr.arpa ip6.arpa {
                pods insecure
                fallthrough in-addr.arpa ip6.arpa
                ttl 30
            }
            # 启用 DNS 缓存,减少上游查询
            cache 30 {
                success 9984 30
                denial 9984 5
            }
            # NodeLocal DNSCache 配合使用效果更好
            forward . /etc/resolv.conf {
                max_concurrent 1000
            }
            loop
            reload
            loadbalance
        }
    # 方案四:部署 NodeLocal DNSCache(强烈推荐)
    # 在每个节点上运行 DNS 缓存,避免所有 DNS 查询都打到 CoreDNS
    # K8s 1.32 中 NodeLocal DNSCache 已经非常成熟
    kubectl apply -f https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml

    ndots调优原则:如果应用主要访问外部服务,设 ndots: 2。如果应用主要访问集群内服务(通过短名称如 my-service),保持默认 ndots: 5。混合场景下设 ndots: 2 并在访问集群内服务时使用完整的 my-service.namespace.svc.cluster.local


    2.9 问题九:Ingress证书过期导致全站不可用

    2.9.1 现象

    用户访问HTTPS站点时浏览器弹出证书过期警告,部分客户端直接拒绝连接。如果Ingress Controller配置了强制HTTPS重定向,HTTP也无法正常访问——所有流量都被重定向到一个证书过期的HTTPS端点。监控告警显示5xx错误率飙升到100%。

    2.9.2 原因分析

    TLS证书有明确的有效期(Let's Encrypt是90天,商业证书通常1年)。证书过期的常见原因:

    1. 没有自动续期机制:手动申请的证书,到期了没人记得续
    2. cert-manager续期失败:ACME challenge失败(DNS记录没配对、HTTP-01验证路径被WAF拦截)但没有告警
    3. 证书更新了但Ingress Controller没重载:Nginx Ingress Controller的SSL证书热更新有时会失败
    4. 通配符证书覆盖不全*.example.com 不覆盖 example.com(裸域名)和 *.sub.example.com(多级子域名)
    5. Secret被误删:存放证书的Secret被清理脚本或人为操作删除

    2.9.3 排查路径

    # 检查 Ingress 关联的 TLS Secret
    kubectl get ingress -n <namespace> -o json | \
      jq -r '.items[] | .metadata.name + ": " + (.spec.tls[]?.secretName // "NO TLS")'
    
    # 查看证书的过期时间
    kubectl get secret <tls-secret> -n <namespace> -o jsonpath='{.data.tls\.crt}' | \
      base64 -d | openssl x509 -noout -dates -subject
    
    # 批量检查所有命名空间的 TLS 证书过期时间
    for ns in $(kubectl get ns -o jsonpath='{.items
  • .metadata.name}'); do   for secret in $(kubectl get secrets -n $ns --field-selector type=kubernetes.io/tls -o jsonpath='{.items
  • .metadata.name}'); do     expiry=$(kubectl get secret $secret -n $ns -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -enddate 2>/dev/null)     echo "$ns/$secret: $expiry"   done done # 检查 cert-manager 的 Certificate 资源状态 kubectl get certificates -A kubectl describe certificate <name> -n <namespace> # 查看 cert-manager 的续期日志 kubectl logs -n cert-manager -l app=cert-manager --tail=100 | grep -i "error\|renew\|fail"
  • 2.9.4 解决方案

    # 使用 cert-manager 自动管理证书(推荐方案)
    # 1. 创建 ClusterIssuer
    apiVersion: cert-manager.io/v1
    kind: ClusterIssuer
    metadata:
      name: letsencrypt-prod
    spec:
      acme:
        server: https://acme-v02.api.letsencrypt.org/directory
        email: ops@example.com
        privateKeySecretRef:
          name: letsencrypt-prod-key
        solvers:
        # HTTP-01 验证(最简单,适合公网可达的服务)
        - http01:
            ingress:
              ingressClassName: nginx
        # DNS-01 验证(适合内网服务和通配符证书)
        # - dns01:
        #     cloudflare:
        #       email: ops@example.com
        #       apiTokenSecretRef:
        #         name: cloudflare-api-token
        #         key: api-token
    # 2. 在 Ingress 中声明证书需求
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: web-app
      annotations:
        cert-manager.io/cluster-issuer: "letsencrypt-prod"
    spec:
      ingressClassName: nginx
      tls:
      - hosts:
        - app.example.com
        - "*.app.example.com"
        secretName: app-example-com-tls  # cert-manager 自动创建和续期
      rules:
      - host: app.example.com
        http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app
                port:
                  number: 80
    # 3. 配置证书过期告警(Prometheus 规则)
    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      name: cert-expiry-alerts
      namespace: monitoring
    spec:
      groups:
      - name: cert-manager
        rules:
        - alert: CertificateExpiringSoon
          expr: certmanager_certificate_expiration_timestamp_seconds-time()<7*24*3600
          for: 1h
          labels:
            severity: warning
          annotations:
            summary: "Certificate {{ $labels.name }} in {{ $labels.namespace }} expires in less than 7 days"
        - alert: CertificateExpiryCritical
          expr: certmanager_certificate_expiration_timestamp_seconds-time()<24*3600
          for: 10m
          labels:
            severity: critical
          annotations:
            summary: "Certificate {{ $labels.name }} in {{ $labels.namespace }} expires in less than 24 hours"

    证书管理铁律:生产环境必须使用cert-manager或等效的自动化工具管理证书。手动管理证书在规模化场景下必然翻车,不是会不会的问题,是什么时候的问题。


    2.10 问题十:存储卷挂载失败(CSI驱动问题)

    2.10.1 现象

    Pod一直卡在 ContainerCreating 状态,kubectl describe pod 显示 FailedMountFailedAttachVolume 事件。常见的错误信息包括:

    • AttachVolume.Attach failed: rpc error: code = Internal
    • MountVolume.MountDevice failed: rpc error: code = DeadlineExceeded
    • Unable to attach or mount volumes: unmounted volumes=[data], unattached volumes=[data]
    • Multi-Attach error for volume "pvc-xxx" Volume is already exclusively attached to one node

    2.10.2 原因分析

    K8s的存储架构分三层:Provision(创建卷)-> Attach(挂载到节点)-> Mount(挂载到Pod)。CSI(Container Storage Interface)驱动负责实现这三个阶段的具体操作。

    常见的存储卷挂载失败原因:

    1. Multi-Attach冲突:RWO(ReadWriteOnce)类型的PV只能挂载到一个节点。Pod被调度到新节点时,旧节点上的VolumeAttachment还没释放,新节点挂载失败
    2. CSI驱动Pod异常:CSI Node Plugin(DaemonSet)或CSI Controller(Deployment)挂了
    3. 云厂商API限流:大量Pod同时创建时,云盘API被限流导致Attach超时
    4. 存储后端故障:NFS服务器不可达、Ceph集群降级、云盘服务异常
    5. PV/PVC状态不匹配:PVC处于Pending状态(StorageClass不存在或配额不足)
    6. 文件系统损坏:非正常卸载导致文件系统需要fsck修复

    2.10.3 排查路径

    # 第一步:查看 Pod 事件
    kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Events"
    
    # 第二步:检查 PVC 状态
    kubectl get pvc -n <namespace>
    kubectl describe pvc <pvc-name> -n <namespace>
    
    # 第三步:检查 PV 状态
    kubectl get pv | grep <pvc-name>
    kubectl describe pv <pv-name>
    
    # 第四步:检查 VolumeAttachment(关键)
    kubectl get volumeattachment | grep <pv-name>
    # 如果看到 attached=true 但 Pod 在另一个节点,说明是 Multi-Attach 问题
    
    # 第五步:检查 CSI 驱动状态
    kubectl get pods -n kube-system -l app=csi-driver
    kubectl logs -n kube-system <csi-node-pod> -c <driver-container> --tail=50
    
    # 第六步:检查节点上的挂载情况
    # SSH 到节点执行
    mount | grep <pv-name>
    ls -la /var/lib/kubelet/plugins/
    ls -la /var/lib/kubelet/pods/<pod-uid>/volumes/
    
    # 第七步:检查 StorageClass
    kubectl get storageclass
    kubectl describe storageclass <sc-name>

    2.10.4 解决方案

    # 场景一:Multi-Attach 冲突
    # 强制删除旧的 VolumeAttachment(谨慎操作,确认旧 Pod 已不存在)
    kubectl delete volumeattachment <va-name>
    
    # 如果旧 Pod 卡在 Terminating,强制删除
    kubectl delete pod <old-pod> -n <namespace> --grace-period=0 --force
    # 场景二:避免 Multi-Attach,使用 StatefulSet + volumeClaimTemplates
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: database
    spec:
      serviceName: database
      replicas: 3
      template:
        spec:
          containers:
          - name: db
            volumeMounts:
            - name: data
              mountPath: /var/lib/data
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          accessModes: ["ReadWriteOnce"]
          storageClassName: fast-ssd
          resources:
            requests:
              storage: 100Gi
    # 场景三:配置合理的 StorageClass
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: fast-ssd
    provisioner: ebs.csi.aws.com  # 或对应云厂商的 CSI 驱动
    parameters:
      type: gp3
      iops: "3000"
      throughput: "125"
      encrypted: "true"
    reclaimPolicy: Retain          # 生产环境用 Retain,防止误删数据
    allowVolumeExpansion: true     # 允许在线扩容
    volumeBindingMode: WaitForFirstConsumer  # 延迟绑定,避免跨 AZ 调度问题
    mountOptions:
    - noatime                      # 减少不必要的元数据写入
    # 场景四:CSI 驱动故障恢复
    # 重启 CSI Node Plugin
    kubectl rollout restart daemonset/<csi-node-ds> -n kube-system
    
    # 重启 CSI Controller
    kubectl rollout restart deployment/<csi-controller> -n kube-system
    
    # 检查 CSI 驱动注册状态
    kubectl get csinodes
    kubectl describe csinode <node-name>

    存储卷排查口诀:先看PVC状态(Pending/Bound),再看VolumeAttachment(有没有残留),然后看CSI驱动日志(有没有报错),最后看节点挂载(mount命令确认)。


    三、示例代码和配置

    3.1 完整配置示例

    3.1.1 生产级Deployment模板

    把前面提到的所有防护措施整合到一个完整的云原生Deployment配置中:

    # 文件路径:manifests/deployment-production-template.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-app
      namespace: production
      labels:
        app: web-app
        version: v1.0.0
    spec:
      replicas: 3
      revisionHistoryLimit: 5
      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxSurge: 1
          maxUnavailable: 0
      selector:
        matchLabels:
          app: web-app
      template:
        metadata:
          labels:
            app: web-app
            version: v1.0.0
          annotations:
            checksum/config: "PLACEHOLDER_FOR_CI_CD"
        spec:
          serviceAccountName: web-app-sa
          terminationGracePeriodSeconds: 60
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000
            seccompProfile:
              type: RuntimeDefault
          affinity:
            podAntiAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                    - key: app
                      operator: In
                      values: ["web-app"]
                  topologyKey: kubernetes.io/hostname
          topologySpreadConstraints:
          - maxSkew: 1
            topologyKey: topology.kubernetes.io/zone
            whenUnsatisfiable: DoNotSchedule
            labelSelector:
              matchLabels:
                app: web-app
          dnsConfig:
            options:
            - name: ndots
              value: "2"
            - name: single-request-reopen
              value: ""
          containers:
          - name: web
            image: registry.example.com/web-app:v1.0.0
            imagePullPolicy: IfNotPresent
            ports:
            - name: http
              containerPort: 8080
              protocol: TCP
            resources:
              requests:
                memory: "512Mi"
                cpu: "250m"
              limits:
                memory: "1Gi"
                # CPU Limit 不设或设较大值
            startupProbe:
              httpGet:
                path: /healthz
                port: http
              failureThreshold: 30
              periodSeconds: 2
            readinessProbe:
              httpGet:
                path: /healthz
                port: http
              initialDelaySeconds: 5
              periodSeconds: 5
              failureThreshold: 3
              successThreshold: 1
            livenessProbe:
              httpGet:
                path: /livez
                port: http
              initialDelaySeconds: 30
              periodSeconds: 10
              failureThreshold: 3
            lifecycle:
              preStop:
                exec:
                  command: ["sh", "-c", "sleep 10"]
            volumeMounts:
            - name: config
              mountPath: /etc/app/config
              readOnly: true
            - name: tmp
              mountPath: /tmp
            securityContext:
              allowPrivilegeEscalation: false
              readOnlyRootFilesystem: true
              capabilities:
                drop: ["ALL"]
          volumes:
          - name: config
            configMap:
              name: web-app-config
          - name: tmp
            emptyDir: {}

    3.1.2 配套的PDB和ServiceAccount

    # PDB
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: web-app-pdb
      namespace: production
    spec:
      maxUnavailable: 1
      selector:
        matchLabels:
          app: web-app
    ---
    # ServiceAccount(最小权限)
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: web-app-sa
      namespace: production
    automountServiceAccountToken: false

    四、最佳实践和注意事项

    4.1 最佳实践

    4.1.1 集群初始化阶段的防御性配置

    集群搭建完成后、业务上线前,有一批配置必须提前做好。等出了事再补,代价是停机时间和事故报告。

    # 1. 每个命名空间必须有 LimitRange 和 ResourceQuota
    # 防止"裸奔" Pod 和资源无限制消耗
    # 参考 2.2 节的配置模板
    
    # 2. 每个多副本 Deployment 必须有 PDB
    # 参考 2.4 节的配置模板
    
    # 3. 默认 NetworkPolicy:deny-all + 按需放行
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: default-deny-all
      namespace: production
    spec:
      podSelector: {}
      policyTypes:
      - Ingress
      - Egress
    # 4. 开启 API Server 审计日志
    # kube-apiserver 启动参数
    --audit-policy-file=/etc/kubernetes/audit-policy.yaml
    --audit-log-path=/var/log/kubernetes/audit.log
    --audit-log-maxage=30
    --audit-log-maxbackup=10
    --audit-log-maxsize=100
    
    # 5. 配置 etcd 自动压缩
    # etcd 启动参数
    --auto-compaction-mode=periodic
    --auto-compaction-retention=1h

    4.1.2 资源管理黄金法则

    原则 具体做法 常见错误
    Request基于实际用量 取p50-p75的实际使用量 拍脑袋设值,要么太大浪费要么太小被驱逐
    Memory Limit留余量 Request的1.5-2倍 Limit = Request,一个内存尖峰就OOMKill
    CPU Limit慎重设置 不设或设很大的值 设太小导致CFS throttle,延迟飙升
    定期right-sizing 每月根据监控数据调整 设完就忘,半年后实际用量和配置差3倍
    使用VPA辅助决策 部署VPA recommender模式 直接开VPA auto模式,频繁重启Pod
    # 使用 kubectl top 快速查看资源使用情况
    kubectl top pods -n production --sort-by=memory
    kubectl top nodes
    
    # 使用 Prometheus 查询历史资源使用(更准确)
    # container_memory_working_set_bytes 的 p95
    # container_cpu_usage_seconds_total 的平均值

    4.1.3 安全加固清单

    # 1. 检查集群中的高危配置
    # 查找使用 hostNetwork 的 Pod
    kubectl get pods -A -o json | \
      jq -r '.items[] | select(.spec.hostNetwork==true) | "\(.metadata.namespace)/\(.metadata.name)"'
    
    # 查找使用 privileged 的容器
    kubectl get pods -A -o json | \
      jq -r '.items[] | select(.spec.containers[].securityContext.privileged==true) | "\(.metadata.namespace)/\(.metadata.name)"'
    
    # 查找没有设置 securityContext 的 Deployment
    kubectl get deployments -A -o json | \
      jq -r '.items[] | select(.spec.template.spec.securityContext == null) | "\(.metadata.namespace)/\(.metadata.name)"'
    
    # 2. 强制 Pod Security Standards(K8s 1.25+ GA)
    kubectl label namespace production \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/audit=restricted

    4.2 注意事项

    4.2.1 版本升级注意事项

    K8s 1.32相比之前版本有几个重要的行为变化:

    变更项 影响 应对措施
    cgroup v2成为唯一支持 cgroup v1的节点无法加入集群 升级前确认所有节点的cgroup版本
    Flowcontrol API GA APF(API Priority and Fairness)替代max-inflight 检查自定义的FlowSchema配置
    kubectl事件格式变化 kubectl get events 输出格式调整 更新依赖事件输出的脚本
    CSI Migration完成 in-tree存储插件全部迁移到CSI 确认CSI驱动版本兼容性

    4.2.2 常见配置错误速查

    错误现象 根因 快速修复
    Pod反复CrashLoopBackOff OOMKill或应用启动失败 kubectl describe pod 的Last State
    Service无法访问 selector和Pod label不匹配 kubectl get endpoints 确认是否为空
    Ingress返回404 path或host配置错误 检查Ingress Controller日志
    PVC一直Pending StorageClass不存在或配额不足 kubectl describe pvc 看Events
    节点SchedulingDisabled 被cordon了没uncordon kubectl uncordon <node>
    HPA不生效 metrics-server没部署或Resource没设Request 检查 kubectl get hpa 的TARGETS列

    五、故障排查和监控

    5.1 故障排查

    5.1.1 通用排查流程

    不管遇到什么问题,排查思路都是从上到下、从外到内:

    用户请求 -> Ingress/LB -> Service -> Pod -> Container -> Application
                                        |
                                    Node (kubelet/containerd/OS)
                                        |
                                    Cluster (etcd/apiserver/controller)
    # 第一步:确认问题范围
    kubectl get nodes                      # 节点是否健康
    kubectl get pods -n <ns> -o wide       # Pod 状态和分布
    kubectl get events -n <ns> --sort-by='.lastTimestamp' | tail -30  # 最近事件
    
    # 第二步:定位问题 Pod
    kubectl describe pod <pod> -n <ns>     # 详细状态和事件
    kubectl logs <pod> -n <ns> --tail=100  # 应用日志
    kubectl logs <pod> -n <ns> -p          # 上一次崩溃的日志(关键)
    
    # 第三步:检查网络连通性
    kubectl exec <pod> -n <ns> -- wget -qO- --timeout=3 http://<service>:<port>/healthz
    kubectl exec <pod> -n <ns> -- nslookup <service>
    
    # 第四步:检查资源状态
    kubectl top pod <pod> -n <ns>          # 实时资源使用
    kubectl get events -n <ns> --field-selector involvedObject.name=<pod>

    5.1.2 高频问题快速定位表

    症状 第一步排查命令 常见原因
    Pod Pending kubectl describe pod 看Events 资源不足 / nodeSelector不匹配 / PVC未绑定
    Pod CrashLoopBackOff kubectl logs <pod> -p 应用启动失败 / OOMKill / 配置错误
    Pod ContainerCreating kubectl describe pod 看Events 镜像拉取失败 / 存储卷挂载失败 / Secret不存在
    Service不通 kubectl get endpoints selector不匹配 / Pod未Ready / NetworkPolicy拦截
    Node NotReady SSH到节点查 journalctl -u kubelet kubelet挂了 / 磁盘满 / containerd故障
    API Server慢 etcdctl endpoint status etcd磁盘慢 / API Server过载 / 大量LIST请求

    5.1.3 调试工具箱

    # 临时调试 Pod(当目标 Pod 没有 shell 时)
    # K8s 1.25+ ephemeral containers GA
    kubectl debug <pod> -n <ns> -it --image=nicolaka/netshoot --target=<container>
    
    # 在指定节点上启动调试 Pod
    kubectl debug node/<node-name> -it --image=nicolaka/netshoot
    
    # 抓包分析(在调试容器中)
    tcpdump -i eth0 -nn -c 100 port 80
    
    # 查看 iptables/nftables 规则(Service 网络排查)
    # 如果使用 kube-proxy iptables 模式
    iptables-save | grep <service-cluster-ip>
    # 如果使用 Cilium
    cilium service list
    cilium endpoint list

    5.2 性能监控

    5.2.1 必须监控的核心指标

    层级 指标 告警阈值 数据源
    etcd etcd_disk_wal_fsync_duration_seconds p99 > 10ms warning, > 50ms critical etcd metrics
    etcd etcd_server_proposals_failed_total > 0 持续5分钟 etcd metrics
    API Server apiserver_request_duration_seconds p99 > 1s apiserver metrics
    API Server apiserver_request_total 5xx比例 > 1% apiserver metrics
    kubelet kubelet_running_pods 接近 --max-pods 限制 kubelet metrics
    Node 磁盘使用率 > 80% warning, > 90% critical node-exporter
    Node 内存使用率 > 85% warning, > 95% critical node-exporter
    Pod kube_pod_container_status_restarts_total 5分钟内 > 3次 kube-state-metrics
    Certificate certmanager_certificate_expiration_timestamp_seconds < 7天 cert-manager
    CoreDNS coredns_dns_request_duration_seconds p99 > 100ms CoreDNS metrics

    5.2.2 Prometheus告警规则示例

    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      name: k8s-core-alerts
      namespace: monitoring
    spec:
      groups:
      - name: kubernetes-core
        rules:
        # Pod 频繁重启
        - alert: PodCrashLooping
          expr: increase(kube_pod_container_status_restarts_total[1h])>5
          for: 10m
          labels:
            severity: warning
          annotations:
            summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} restarted {{ $value }} times in 1h"
    
        # 节点磁盘即将满
        - alert: NodeDiskPressure
          expr: (node_filesystem_avail_bytes{mountpoint="/"}/node_filesystem_size_bytes{mountpoint="/"})<0.15
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: "Node {{ $labels.instance }} root disk usage > 85%"
    
        # etcd leader 频繁切换
        - alert: EtcdLeaderChanges
          expr: increase(etcd_server_leader_changes_seen_total[1h])>3
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: "etcd leader changed {{ $value }} times in 1h"
    
        # API Server 错误率升高
        - alert: ApiServerErrorRate
          expr: sum(rate(apiserver_request_total{code=~"5.."}[5m]))/sum(rate(apiserver_request_total[5m]))>0.01
          for: 10m
          labels:
            severity: critical
          annotations:
            summary: "API Server 5xx error rate > 1%"

    5.3 备份与恢复

    5.3.1 etcd备份(集群的生命线)

    #!/bin/bash
    # etcd 定时备份脚本
    # 建议通过 crontab 每小时执行一次
    
    BACKUP_DIR="/backup/etcd"
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    ENDPOINTS="https://127.0.0.1:2379"
    CACERT="/etc/kubernetes/pki/etcd/ca.crt"
    CERT="/etc/kubernetes/pki/etcd/server.crt"
    KEY="/etc/kubernetes/pki/etcd/server.key"
    
    # 创建备份
    ETCDCTL_API=3 etcdctl snapshot save "${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db" \
      --endpoints=${ENDPOINTS} \
      --cacert=${CACERT} \
      --cert=${CERT} \
      --key=${KEY}
    
    # 验证备份完整性
    ETCDCTL_API=3 etcdctl snapshot status "${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db" \
      --write-out=table
    
    # 清理 7 天前的备份
    find ${BACKUP_DIR} -name "etcd-snapshot-*.db" -mtime +7 -delete
    
    echo "[$(date)] etcd backup completed: etcd-snapshot-${TIMESTAMP}.db"

    5.3.2 集群资源备份

    # 使用 Velero 进行集群资源和 PV 数据备份
    # 安装 Velero
    velero install \
      --provider aws \
      --bucket k8s-backup \
      --secret-file ./credentials-velero \
      --backup-location-config region=us-east-1 \
      --snapshot-location-config region=us-east-1
    
    # 创建定时备份计划
    velero schedule create daily-backup \
      --schedule="0 2 * * *" \
      --include-namespaces production,staging \
      --ttl 168h
    
    # 恢复操作
    velero restore create --from-backup daily-backup-20260206020000

    六、总结

    6.1 技术要点回顾

    这10个故障场景覆盖了K8s生产环境中最常见的故障场景,核心要点:

    • etcd是集群的命脉:给它独立的NVMe SSD,监控WAL fsync延迟,定期压缩和碎片整理
    • 资源管理是基本功:Request基于实际用量,Memory Limit留余量,CPU Limit慎重设置,LimitRange和ResourceQuota是安全网
    • 零停机更新三件套maxUnavailable=0 + readinessProbe + preStop hook
    • PDB不是可选项:多副本服务必须配PDB,配合Pod反亲和性分散副本
    • RBAC最小权限:从零开始按需授权,禁止cluster-admin滥用,关闭不必要的Token挂载
    • ConfigMap更新有坑:subPath不更新、环境变量不更新,用Reloader或hash注解方案
    • 节点NotReady排查:kubelet -> containerd -> 磁盘/内存/网络 -> 证书,逐层排查
    • DNS ndots必须调优:外部服务为主设 ndots:2,部署NodeLocal DNSCache
    • 证书必须自动化管理:cert-manager + 过期告警,手动管理必翻车
    • 存储卷问题看四层:PVC状态 -> VolumeAttachment -> CSI驱动 -> 节点挂载

    6.2 防御性运维体系

    把事后救火变成事前防御,需要建立三道防线:

    第一道防线:准入控制

    • LimitRange / ResourceQuota防止资源滥用
    • Pod Security Standards防止特权容器
    • OPA/Kyverno策略引擎强制执行组织规范

    第二道防线:监控告警

    • 核心组件指标监控(etcd、apiserver、kubelet)
    • 业务指标监控(Pod重启次数、错误率、延迟)
    • 证书过期、磁盘空间、节点状态告警

    第三道防线:备份恢复

    • etcd定时快照备份
    • Velero集群资源和PV数据备份
    • 定期演练恢复流程,确保备份可用

    附录

    A. 命令速查表

    # 集群状态快速检查
    kubectl get nodes -o wide                    # 节点状态
    kubectl get pods -A | grep -v Running        # 非 Running 的 Pod
    kubectl top nodes                            # 节点资源使用
    kubectl get events -A --sort-by='.lastTimestamp' | tail -20  # 最近事件
    
    # etcd 健康检查
    etcdctl endpoint health --write-out=table
    etcdctl endpoint status --write-out=table
    etcdctl alarm list
    
    # 证书检查
    kubeadm certs check-expiration               # 控制面证书
    kubectl get certificates -A                  # cert-manager 管理的证书
    
    # 存储排查
    kubectl get pvc -A | grep -v Bound           # 未绑定的 PVC
    kubectl get volumeattachment                 # 卷挂载状态
    
    # 网络排查
    kubectl get endpoints -A | awk '$2==""'      # 空 Endpoints 的 Service
    kubectl get networkpolicy -A                 # 网络策略

    B. 关键配置参数速查

    参数 默认值 推荐值 说明
    ndots 5 2(外部服务为主) DNS search域触发阈值
    terminationGracePeriodSeconds 30 60 Pod优雅关闭等待时间
    maxUnavailable 25% 0 滚动更新最大不可用数
    maxSurge 25% 1 滚动更新最大超出数
    node-monitor-grace-period 40s 40s 节点心跳超时判定
    eviction-hard memory.available 100Mi 500Mi 内存驱逐阈值
    eviction-hard nodefs.available 10% 15% 磁盘驱逐阈值
    etcd auto-compaction-retention 0(关闭) 1h etcd自动压缩周期

    如果你在实践过程中遇到了文中未覆盖的其他棘手问题,或者有不同的见解和优化方案,欢迎在云栈社区的技术论坛进行交流讨论,共同探索更稳定、更高效的云原生运维之道。




    上一篇:Mock 与 Spy 的核心哲学:Jest/Sinon 测试中的依赖隔离与有效性风险
    下一篇:Qwen-2.0-Image技术解析:从2K原生画质到专业级排版,AI绘图进入设计时代
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-2-23 11:44 , Processed in 1.011817 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表