Kubernetes 排障实战:从 CrashLoopBackOff 到网络不通

kubectl get pods 一看,CrashLoopBackOffImagePullBackOffOOMKilledPending------满屏红字,不知道从哪下手。K8s 排障和传统服务器排障完全不同:你没法 SSH 上去看日志,Pod 说没就没,网络是软件定义的。但 K8s 排障也有方法论------从 Pod 到 Service 到 Ingress,层层排查,每一层都有明确的命令和检查点。本文用 PMS 项目在 K8s 上的真实故障,讲透每个常见问题的排查过程和根因。


目录

  1. 排障方法论:从下到上四层模型
  2. [Pod 生命周期与常见状态](#Pod 生命周期与常见状态)
  3. CrashLoopBackOff:应用启动失败
  4. OOMKilled:内存不足被杀
  5. [Pending:Pod 调度不上去](#Pending:Pod 调度不上去)
  6. ImagePullBackOff:镜像拉不下来
  7. [Service 不通:Endpoint 为空](#Service 不通:Endpoint 为空)
  8. [Ingress 502/503:从入口到后端](#Ingress 502/503:从入口到后端)
  9. [网络策略与 DNS 问题](#网络策略与 DNS 问题)
  10. [CPU 节流:为什么 limits 设了还是慢](#CPU 节流:为什么 limits 设了还是慢)
  11. [磁盘压力与 PVC 问题](#磁盘压力与 PVC 问题)
  12. [PMS 实战:船岸同步在 K8s 上的三个故障](#PMS 实战:船岸同步在 K8s 上的三个故障)
  13. 必备排障命令速查表
  14. Checklist

1. 排障方法论:从下到上四层模型

K8s 排障遵循一个清晰的层次模型,从底层到上层逐层排查:

复制代码
第 4 层:外部访问层(Ingress / LoadBalancer / DNS)
  │  症状:外部访问 502/503/超时
  │  检查:Ingress 配置、TLS 证书、LB 健康检查
  ↓
第 3 层:服务网络层(Service / Endpoints / DNS / NetworkPolicy)
  │  症状:Pod 间无法通信、Service 解析失败
  │  检查:kubectl get endpoints、CoreDNS、NetworkPolicy
  ↓
第 2 层:Pod 层(容器状态、日志、健康检查、资源)
  │  症状:CrashLoopBackOff、OOMKilled、Pending
  │  检查:kubectl describe pod、kubectl logs、events
  ↓
第 1 层:节点与基础设施层(Node、CPU、内存、磁盘、网络)
     症状:Node NotReady、磁盘压力、网络分区
     检查:kubectl describe node、kubectl top node、系统日志

核心原则:从 Pod 状态开始看,不要一上来就猜网络问题。大多数"网络不通"其实是 Pod 根本没跑起来。


2. Pod 生命周期与常见状态

2.1 Pod 状态机

复制代码
Pending → ContainerCreating → Running → Succeeded/Failed
                              ↓
                         CrashLoopBackOff(反复崩溃重启)

ImagePullBackOff(镜像拉取失败,发生在 ContainerCreating 阶段)

2.2 kubectl get pods 状态列解读

bash 复制代码
kubectl get pods -n pms
状态 含义 紧急程度
Running 正常运行
Pending 已创建但未调度到节点 ⚠️
ContainerCreating 正在创建容器(拉镜像/挂载卷) 正常(长时间不变则有问题)
CrashLoopBackOff 容器反复崩溃重启 🔴
ImagePullBackOff 镜像拉取失败 🔴
OOMKilled 内存超限被杀 🔴
Error 容器异常退出 🔴
Terminating 正在删除 正常(卡住则有问题)
Completed 任务完成(Job/CronJob)

2.3 万能排查第一步

bash 复制代码
# 这一个命令解决 80% 的问题
kubectl describe pod <pod-name> -n <namespace>

重点看输出底部的 Events 区域:

复制代码
Events:
  Type     Reason     Age   From               Message
  ----     ------     ----  ----               -------
  Normal   Scheduled  2m    default-scheduler  Successfully assigned pms/pms-api-xxx to node-1
  Normal   Pulling    2m    kubelet            Pulling image "registry.example.com/pms/api:1.0.0"
  Warning  Failed     1m    kubelet            Failed to pull image: rpc error: code = Unknown
  Normal   BackOff    30s   kubelet            Back-off pulling image

3. CrashLoopBackOff:应用启动失败

3.1 现象

bash 复制代码
$ kubectl get pods -n pms
NAME                       READY   STATUS             RESTARTS   AGE
pms-api-7f8b9c6d4-x2k9p    0/1     CrashLoopBackOff   5          3m

3.2 排查步骤

bash 复制代码
# 第 1 步:看应用日志
kubectl logs pms-api-7f8b9c6d4-x2k9p -n pms
kubectl logs pms-api-7f8b9c6d4-x2k9p -n pms --previous  # 上一次崩溃的日志

# 第 2 步:看 Events
kubectl describe pod pms-api-7f8b9c6d4-x2k9p -n pms

# 第 3 步:看退出码
kubectl get pod pms-api-7f8b9c6d4-x2k9p -n pms -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

3.3 常见退出码

退出码 含义 常见原因
0 正常退出 应用主动退出(可能是后台任务配错成 Deployment)
1 一般错误 未捕获异常、配置错误、连接数据库失败
137 SIGKILL OOMKilled(内存超限)或被 kubectl delete 强杀
139 SIGSEGV 段错误(native 库问题)
143 SIGTERM 优雅关闭(正常终止,不是故障)

3.4 PMS 真实案例:配置文件缺失

现象:部署后 Pod 反复 CrashLoopBackOff,日志显示:

复制代码
Unhandled exception. System.IO.FileNotFoundException:
Could not find file '/app/config/appsettings.Production.json'

根因 :Dockerfile 中 ConfigMap 挂载路径覆盖了 /app/config 目录,但 ConfigMap 里只放了部分配置文件,镜像中原有的 appsettings.Production.json 被遮蔽了。

修复:把所有配置文件统一放到 ConfigMap 中,或用 subPath 挂载单个文件:

yaml 复制代码
volumes:
  - name: config
    configMap:
      name: pms-api-config
volumeMounts:
  - name: config
    mountPath: /app/config/appsettings.Custom.json
    subPath: appsettings.Custom.json  # 只挂载单个文件,不覆盖整个目录

3.5 PMS 真实案例:数据库连接串错误

现象:Pod 启动后立即崩溃,日志:

复制代码
fail: Microsoft.EntityFrameworkCore.Database.Connection[20004]
      A network-related or instance-specific error occurred while establishing
      a connection to SQL Server. The server was not found or was not accessible.
Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904):
A network-related or instance-specific error occurred while establishing
a connection to SQL Server.

排查

bash 复制代码
# 检查连接串配置
kubectl exec -it pms-api-xxx -n pms -- env | grep ConnectionString

# 从 Pod 内测试数据库连通性
kubectl exec -it pms-api-xxx -n pms -- curl -v telnet://pms-db:1433

# 检查数据库 Service 是否有 Endpoint
kubectl get endpoints pms-db -n pms

根因:数据库 Pod 还在启动中(SQL Server 冷启动需要 30-60 秒),API Pod 启动时数据库还没就绪。

修复

yaml 复制代码
# 方法1:设置 initContainer 等待数据库就绪
initContainers:
  - name: wait-for-db
    image: mcr.microsoft.com/mssql-tools:latest
    command:
      - /bin/sh
      - -c
      - |
        until /opt/mssql-tools/bin/sqlcmd -S pms-db -U sa -P $SA_PASSWORD -Q "SELECT 1"; do
          echo "waiting for database..."
          sleep 5
        done
    env:
      - name: SA_PASSWORD
        valueFrom:
          secretKeyRef:
            name: pms-db-secret
            key: sa-password

# 方法2:应用层面加重试策略(推荐)
// Program.cs
builder.Services.AddDbContext<PmsDbContext>(options =>
    options.UseSqlServer(connStr, sql =>
        sql.EnableRetryOnFailure(
            maxRetryCount: 10,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null)));

💬 互动一下:你的应用有没有做"启动时依赖未就绪"的容错?我见过很多应用假设数据库永远可用,一旦数据库重启 30 秒,所有应用 Pod 全部 CrashLoop,等数据库恢复了,应用还在 CrashLoop 的退避倒计时中,反而恢复得更慢。


4. OOMKilled:内存不足被杀

4.1 现象

bash 复制代码
$ kubectl describe pod pms-api-xxx -n pms
...
State:          Terminated
  Reason:       OOMKilled
  Exit Code:    137
Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137

4.2 排查

bash 复制代码
# 查看内存配置
kubectl get pod pms-api-xxx -n pms -o jsonpath='{.spec.containers[0].resources}'

# 查看实际内存使用
kubectl top pod pms-api-xxx -n pms

# 查看历史内存趋势(需要 metrics-server)
kubectl top pods -n pms --sort-by=memory

4.3 PMS 真实案例:大文件导出导致 OOM

现象:用户在 PMS 中导出全年备件报表时,API Pod 被 OOMKilled。

排查

  1. kubectl logs 显示导出开始后约 15 秒,Pod 被杀
  2. kubectl top pod 显示内存从 300MB 飙升到 1.1GB
  3. limits.memory 设的是 1Gi

根因:导出代码把全部数据加载到内存生成 Excel:

csharp 复制代码
// ❌ 一次性加载全部数据到内存
var allData = await dbContext.SpareParts
    .Include(p => p.Equipment)
    .Include(p => p.Warehouse)
    .ToListAsync();  // 10万条数据,内存爆了

var package = new ExcelPackage();
var sheet = package.Workbook.Worksheets.Add("报表");
sheet.Cells.LoadFromCollection(allData);
return File(package.GetAsByteArray(), ...);

修复

csharp 复制代码
// ✅ 流式写入 + 分页查询
public async Task ExportToExcelAsync(
    Stream outputStream, CancellationToken ct)
{
    using var package = new ExcelPackage();
    var sheet = package.Workbook.Worksheets.Add("报表");
    int row = 1;

    // 写入表头
    sheet.Cells[row, 1].Value = "备件编号";
    sheet.Cells[row, 2].Value = "备件名称";
    // ...

    // 分页查询,每页 1000 条
    int pageSize = 1000;
    int pageIndex = 0;
    while (true)
    {
        var page = await dbContext.SpareParts
            .AsNoTracking()
            .OrderBy(p => p.Id)
            .Skip(pageIndex * pageSize)
            .Take(pageSize)
            .Select(p => new { p.PartNo, p.PartName, /* ... */ })
            .ToListAsync(ct);

        if (!page.Any()) break;

        foreach (var item in page)
        {
            row++;
            sheet.Cells[row, 1].Value = item.PartNo;
            sheet.Cells[row, 2].Value = item.PartName;
        }

        pageIndex++;
    }

    package.SaveAs(outputStream);
}

同时把内存 limit 提高到 2Gi,并配置告警:

yaml 复制代码
resources:
  requests:
    memory: 512Mi
  limits:
    memory: 2Gi

4.4 OOM 的两个层次

层次 原因 排查方式
Container OOM 容器内存超过 limits.memory kubectl describe podOOMKilled
Node OOM 节点总内存不足,kubelet 杀 Pod `dmesg

5. Pending:Pod 调度不上去

5.1 现象

bash 复制代码
$ kubectl get pods -n pms
NAME                       READY   STATUS    RESTARTS   AGE
pms-api-7f8b9c6d4-x2k9p    0/1     Pending   0          5m

5.2 排查

bash 复制代码
kubectl describe pod pms-api-xxx -n pms

Events 区域会告诉你原因:

原因 1:资源不足

复制代码
Warning  FailedScheduling  default-scheduler
0/3 nodes are available:
  3 Insufficient memory,
  3 Insufficient cpu.

解决:降低 requests、加节点、清理其他 Pod。

原因 2:节点选择器不匹配

复制代码
0/3 nodes are available:
  3 node(s) didn't match node selector.

检查 nodeSelector

bash 复制代码
kubectl get nodes --show-labels

原因 3:污点(Taint)不匹配

复制代码
0/3 nodes are available:
  1 node(s) had taint {node-role.kubernetes.io/master: },
  2 node(s) had taint {dedicated: gpu}, that the pod didn't tolerate.

5.3 PMS 真实案例:PVC 绑定卡住导致 Pending

现象:数据库 Pod 一直 Pending,Events 显示:

复制代码
pod has unbound immediate PersistentVolumeClaims

排查

bash 复制代码
kubectl get pvc -n pms
kubectl describe pvc mssql-data-pms-db-0 -n pms

根因 :StorageClass 用的 WaitForFirstConsumer 模式,但没有节点匹配 PVC 的访问模式(ReadWriteMany)。

修复 :数据库改用 ReadWriteOnce,文件存储改用支持 RWX 的 StorageClass(NFS/CephFS)。


6. ImagePullBackOff:镜像拉不下来

6.1 现象

复制代码
NAME                       READY   STATUS             RESTARTS   AGE
pms-api-7f8b9c6d4-x2k9p    0/1     ImagePullBackOff   0          2m

6.2 常见原因

bash 复制代码
kubectl describe pod pms-api-xxx -n pms
错误信息 原因 解决
manifest unknown 镜像标签不存在 检查 tag 是否正确
unauthorized 没有镜像仓库权限 配置 imagePullSecrets
dial tcp: i/o timeout 网络不通 检查节点网络/代理/防火墙
no space left on device 节点磁盘满了 清理镜像/扩容
ImagePullBackOff(无具体错误) 退避重试中 看 describe 中的完整错误

6.3 配置私有仓库凭证

bash 复制代码
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=your-username \
  --docker-password=your-password \
  -n pms
yaml 复制代码
spec:
  template:
    spec:
      imagePullSecrets:
        - name: regcred
      containers:
        - name: pms-api
          image: registry.example.com/pms/api:1.0.0

6.4 PMS 真实案例:镜像 tag 拼错

CI/CD 流水线构建了 1.0.0 标签,但部署 YAML 写的是 v1.0.0

bash 复制代码
$ kubectl describe pod pms-api-xxx
Failed to pull image "registry.example.com/pms/api:v1.0.0":
  rpc error: code = NotFound desc = manifest for v1.0.0 not found

修复:统一 CI 脚本中的 tag 命名规范,在 Helm values 中用 Chart.AppVersion 避免手写。


7. Service 不通:Endpoint 为空

7.1 现象

Pod 是 Running 状态,但 Service 访问不通:

bash 复制代码
$ kubectl get svc pms-api -n pms
NAME      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)
pms-api   ClusterIP   10.96.45.123   <none>        80/TCP

$ kubectl get endpoints pms-api -n pms
NAME      ENDPOINTS
pms-api   <none>      # ← 空的!

7.2 排查流程

复制代码
Service Endpoints 为空?
  │
  ├── selector 和 Pod labels 匹配吗?
  │     kubectl get svc pms-api -o jsonpath='{.spec.selector}'
  │     kubectl get pods -l app=pms-api --show-labels
  │
  ├── Pod ready 吗?
  │     kubectl get pods -l app=pms-api
  │     READY 列必须是 1/1,0/1 的 Pod 不会加入 Endpoints
  │
  ├── targetPort 正确吗?
  │     Service port → targetPort → containerPort
  │
  └── 命名空间对吗?
        Service 和 Pod 必须在同一个 Namespace

7.3 PMS 真实案例:readinessProbe 导致 Endpoints 为空

现象:API Pod 显示 Running,但 Service 访问返回 503。

bash 复制代码
$ kubectl get endpoints pms-api -n pms
NAME      ENDPOINTS
pms-api   <none>

$ kubectl get pods -l app=pms-api -n pms
NAME                       READY   STATUS    RESTARTS
pms-api-7f8b9c6d4-x2k9p    0/1     Running   0

排查 :Pod 是 Running 但 READY 是 0/1。说明 readinessProbe 失败了。

bash 复制代码
kubectl describe pod pms-api-xxx -n pms | grep -A5 Readiness

根因 :readinessProbe 路径配置错误------应用内健康检查路径是 /health/ready,YAML 写成了 /health

yaml 复制代码
# ❌ 错误
readinessProbe:
  httpGet:
    path: /health      # 这个路径返回 404
    port: 8080

# ✅ 正确
readinessProbe:
  httpGet:
    path: /health/ready
    port: 8080

7.4 从 Pod 内部测试 Service

bash 复制代码
# 进入一个临时 Pod 测试
kubectl run debug --rm -it --image=curlimages/curl --restart=Never -n pms -- \
  curl -v http://pms-api.pms.svc.cluster.local/api/spareparts

# DNS 解析测试
kubectl run dns-test --rm -it --image=busybox --restart=Never -n pms -- \
  nslookup pms-api.pms.svc.cluster.local

8. Ingress 502/503:从入口到后端

8.1 排查链路

复制代码
客户端 → DNS → LoadBalancer → Ingress Controller → Service → Pod
                                                         │
                                              502: Pod 拒绝连接
                                              503: Service 无 Endpoint
                                              504: Pod 响应超时

8.2 常见问题

503 Service Temporarily Unavailable

bash 复制代码
# 99% 是 Service Endpoints 为空
kubectl get endpoints <service-name> -n <namespace>

502 Bad Gateway

bash 复制代码
# Pod 在但端口不对或应用没在监听
kubectl exec -it <pod> -- curl http://localhost:8080/health/ready

# 检查 containerPort 和 targetPort
kubectl get svc <service-name> -o yaml

504 Gateway Timeout

bash 复制代码
# 应用处理太慢,检查 Ingress 超时配置
kubectl describe ingress <ingress-name>
yaml 复制代码
# 增加超时
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "60"

8.3 查看 Ingress Controller 日志

bash 复制代码
# 找到 Ingress Controller Pod
kubectl get pods -n ingress-nginx

# 看日志
kubectl logs -f ingress-nginx-controller-xxx -n ingress-nginx

# 看 Nginx 配置(排查路由是否正确)
kubectl exec -it ingress-nginx-controller-xxx -n ingress-nginx -- \
  cat /etc/nginx/nginx.conf | grep -A20 "pms-api"

8.4 PMS 真实案例:船岸同步上传 504

现象:船端上传大包(50MB+)同步数据时,经常 504 超时。

根因:Nginx Ingress 默认 proxy-read-timeout 是 60 秒,卫星网络慢时上传超过 60 秒。

修复

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: pms-ingress
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    # 对同步接口单独配置更长超时
    nginx.ingress.kubernetes.io/configuration-snippet: |
      location /api/sync/ {
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
        client_max_body_size 200m;
      }

9. 网络策略与 DNS 问题

9.1 NetworkPolicy 阻断流量

如果集群配置了默认拒绝网络策略,Pod 间通信会被阻断:

bash 复制代码
# 检查 NetworkPolicy
kubectl get networkpolicy -n pms
kubectl describe networkpolicy default-deny -n pms

修复:添加允许规则:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: pms
spec:
  podSelector:
    matchLabels:
      app: pms-db
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: pms-api
      ports:
        - protocol: TCP
          port: 1433

9.2 DNS 解析失败

bash 复制代码
# 在 Pod 内测试 DNS
kubectl exec -it pms-api-xxx -n pms -- nslookup pms-db.pms.svc.cluster.local

# 检查 CoreDNS
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -l k8s-app=kube-dns -n kube-system

常见 DNS 问题

  • CoreDNS Pod 挂了或没就绪
  • ndots 配置导致搜索域追加过多(外部域名解析慢)
  • 节点的 DNS 配置问题
yaml 复制代码
# 自定义 DNS 配置
dnsConfig:
  options:
    - name: ndots
      value: "2"  # 默认是 5,外部域名不需要搜索所有域

10. CPU 节流:为什么 limits 设了还是慢

10.1 现象

应用 P99 延迟偶尔飙高,但 CPU 使用率看起来不高(只有 40%)。

10.2 根因:CPU Throttling

K8s 的 CPU limit 使用 CFS(完全公平调度器)带宽控制。每个周期(默认 100ms)分配 cpu.limit 的时间片。如果容器在一个周期内用完了配额,即使 CPU 空闲也会被节流到下一个周期。

bash 复制代码
# 查看 CPU 节流指标
kubectl get --raw /api/v1/namespaces/pms/pods/pms-api-xxx/proxy/metrics | \
  grep container_cpu_cfs_throttled_periods_total

如果节流比例 >5%,说明 CPU limit 设低了。

10.3 修复

yaml 复制代码
resources:
  requests:
    cpu: 500m    # 保证 0.5 核
  limits:
    cpu: 2000m   # 允许突发到 2 核

关于 CPU limit 的争议

观点 理由
设 CPU limit 防止一个 Pod 占满节点
不设 CPU limit 避免节流延迟,允许突发;只设 memory limit

建议

  • 延迟敏感服务(API):CPU limit 设为 requests 的 2-4 倍,或不设 limit
  • 后台任务:可以设较严格的 limit
  • 监控节流率,超过 5% 就调高 limit

11. 磁盘压力与 PVC 问题

11.1 节点磁盘压力

复制代码
Warning  EvictionThresholdMet  node/worker-1
  Attempting to reclaim ephemeral-storage

节点磁盘满了,kubelet 会驱逐 Pod。

bash 复制代码
# 查看节点磁盘使用
kubectl describe node worker-1 | grep -A5 Conditions

# 查看哪些容器日志占空间
ssh worker-1 "du -sh /var/log/containers/* | sort -rh | head -20"

# 清理未使用的镜像
ssh worker-1 "crictl rmi --prune"

11.2 PVC 卡在 Pending

bash 复制代码
kubectl get pvc -n pms
kubectl describe pvc <pvc-name> -n pms

常见原因:

  • StorageClass 名称不存在
  • 没有可用的 PV(静态配置)
  • 访问模式不支持(如请求 RWX 但存储只支持 RWO)

11.3 PVC 满了

数据库 Pod 可能因为磁盘满而无法写入:

bash 复制代码
# 查看 PVC 使用量
kubectl get pvc -n pms
kubectl describe pvc mssql-data -n pms

# 扩容 PVC(需要 StorageClass 支持 allowVolumeExpansion)
kubectl patch pvc mssql-data -n pms -p \
  '{"spec":{"resources":{"requests":{"storage":"200Gi"}}}}'

12. PMS 实战:船岸同步在 K8s 上的三个故障

12.1 故障一:同步任务时间漂移

现象:船岸同步设定每 5 分钟执行一次,但实际执行间隔不稳定,有时 5 分钟,有时 15 分钟。

排查

bash 复制代码
# 同步任务日志显示时间正常,但触发间隔异常
kubectl logs pms-sync-xxx -n pms | grep "同步开始"

# 发现 Pod 被重新调度过
kubectl get pods -n pms -l app=pms-sync -o wide
# Pod 在不同节点之间漂移

# 进一步看 Pod 的重启次数
kubectl describe pod pms-sync-xxx | grep -i restart

根因 :同步服务用 PeriodicTimer 在内存中维护定时状态,Pod 重启或被重新调度时定时器丢失,重启后要等下一个周期才触发。同时因为没有配置 PodDisruptionBudget,节点维护时同步 Pod 被直接驱逐。

修复

yaml 复制代码
# 1. 配置 PDB,保证同步服务始终可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pms-sync-pdb
  namespace: pms
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: pms-sync
---
# 2. 用 K8s CronJob 替代内存定时器(如果是周期性任务)
apiVersion: batch/v1
kind: CronJob
metadata:
  name: pms-ship-sync
  namespace: pms
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Forbid  # 不允许并发执行
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          containers:
            - name: sync
              image: registry.example.com/pms/sync:1.0.0
              command: ["dotnet", "Pms.Sync.dll", "--mode=once"]
          restartPolicy: OnFailure

12.2 故障二:同步大文件时 OOMKill + 节点磁盘压力

现象:船端上传 200MB 同步包时,同步 Pod 被 OOMKill,同时节点出现磁盘压力。

排查

bash 复制代码
# Pod 被 OOMKill
kubectl describe pod pms-sync-xxx | grep -A5 "Last State"

# 节点磁盘压力
kubectl describe node worker-2 | grep -A3 DiskPressure

# 发现同步包先写到 /tmp,容器磁盘占满节点

根因 :同步服务接收文件后先写到容器临时目录(/tmp),容器的可写层使用节点的 ephemeral storage。大文件加上并发同步,很快把节点磁盘占满,触发节点驱逐。

修复

  1. 配置 ephemeral-storage 限制:
yaml 复制代码
resources:
  requests:
    cpu: 200m
    memory: 512Mi
    ephemeral-storage: 1Gi
  limits:
    cpu: 1000m
    memory: 2Gi
    ephemeral-storage: 5Gi  # 限制临时存储
  1. 大文件流式处理,不落盘:
csharp 复制代码
// ❌ 先存到磁盘再处理
var filePath = Path.Combine(Path.GetTempPath(), file.FileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
    await file.CopyToAsync(stream);
}
// 处理...
// File.Delete(filePath);

// ✅ 流式处理
using var stream = file.OpenReadStream();
await ProcessSyncPackageAsync(stream, ct);
  1. 挂载 emptyDir 作为临时目录:
yaml 复制代码
volumes:
  - name: tmp
    emptyDir:
      sizeLimit: 2Gi
volumeMounts:
  - name: tmp
    mountPath: /tmp

12.3 故障三:滚动更新时同步任务中断

现象:每次发布新版本,正在进行的船岸同步任务会中断,导致数据同步不完整。

排查 :同步任务可能运行 3-5 分钟(卫星网络慢),但默认 terminationGracePeriodSeconds 是 30 秒,K8s 发 SIGTERM 后 30 秒就 SIGKILL。

修复

yaml 复制代码
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 600  # 给 10 分钟宽限
      containers:
        - name: pms-sync
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 30"]

应用端响应取消令牌:

csharp 复制代码
public class SyncBackgroundService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 注册 SIGTERM 处理
        using var shutdownCts = new CancellationTokenSource();
        stoppingToken.Register(() =>
        {
            _logger.LogWarning("收到关闭信号,等待当前同步批次完成...");
            // 不立即取消,给当前任务 10 分钟完成
            shutdownCts.CancelAfter(TimeSpan.FromMinutes(10));
        });

        while (!shutdownCts.IsCancellationRequested)
        {
            try
            {
                await ProcessOneBatchAsync(shutdownCts.Token);
            }
            catch (OperationCanceledException)
            {
                _logger.LogInformation("同步服务优雅关闭");
                break;
            }
        }
    }
}

13. 必备排障命令速查表

13.1 Pod 排查

bash 复制代码
# 查看 Pod 状态
kubectl get pods -n <ns> -o wide

# 查看详细事件
kubectl describe pod <pod> -n <ns>

# 查看日志
kubectl logs <pod> -n <ns> -f --tail=100
kubectl logs <pod> -n <ns> --previous  # 上一次崩溃的日志

# 进入容器
kubectl exec -it <pod> -n <ns> -- /bin/sh

# 端口转发到本地
kubectl port-forward svc/<svc> 8080:80 -n <ns>

# 查看资源使用
kubectl top pods -n <ns> --sort-by=memory
kubectl top pods -n <ns> --sort-by=cpu

13.2 Service/网络排查

bash 复制代码
# Service 和 Endpoints
kubectl get svc -n <ns>
kubectl get endpoints -n <ns>

# DNS 测试
kubectl run dns-test --rm -it --image=busybox --restart=Never -- \
  nslookup <svc>.<ns>.svc.cluster.local

# 网络连通性测试
kubectl run net-test --rm -it --image=curlimages/curl --restart=Never -- \
  curl -v http://<svc>.<ns>.svc.cluster.local

# 查看 NetworkPolicy
kubectl get networkpolicy -n <ns>

13.3 节点排查

bash 复制代码
# 节点状态
kubectl get nodes -o wide
kubectl describe node <node>
kubectl top nodes

# 查看节点上的 Pod
kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=<node>

# 污点和容忍
kubectl taint nodes <node> key=value:NoSchedule

13.4 日志和事件

bash 复制代码
# 集群事件(按时间排序)
kubectl get events --sort-by='.lastTimestamp' -n <ns>

# 警告事件
kubectl get events --field-selector type=Warning -n <ns>

# 查看 Pod 的 YAML(完整配置)
kubectl get pod <pod> -n <ns> -o yaml

13.5 一键诊断脚本

bash 复制代码
#!/bin/bash
# k8s-diagnose.sh <pod-name> [namespace]
POD=$1
NS=${2:-default}

echo "=== Pod 状态 ==="
kubectl get pod $POD -n $NS -o wide

echo -e "\n=== Events ==="
kubectl describe pod $POD -n $NS | tail -20

echo -e "\n=== 容器状态 ==="
kubectl get pod $POD -n $NS -o jsonpath='{range .status.containerStatuses[*]}{.name}{": ready="}{.ready}{" restartCount="}{.restartCount}{"\n"}{end}'

echo -e "\n=== 最后日志(50行)==="
kubectl logs $POD -n $NS --tail=50

echo -e "\n=== 上次崩溃日志(50行)==="
kubectl logs $POD -n $NS --previous --tail=50 2>/dev/null || echo "无上次日志"

echo -e "\n=== 资源使用 ==="
kubectl top pod $POD -n $NS 2>/dev/null || echo "metrics-server 未安装"

echo -e "\n=== Service Endpoints ==="
LABELS=$(kubectl get pod $POD -n $NS -o jsonpath='{.metadata.labels}' | tr ',' '\n' | cut -d'"' -f2,4 | paste -sd, -)
kubectl get endpoints -n $NS -l app=$(kubectl get pod $POD -n $NS -o jsonpath='{.metadata.labels.app}')

14. Checklist

部署前

  • 配置 livenessProbe 和 readinessProbe,路径正确
  • 设置 resources.requests 和 limits(CPU + Memory)
  • 镜像标签固定版本,不用 latest
  • 配置 imagePullSecrets(私有仓库)
  • 非 root 用户运行
  • 配置优雅关闭(preStop + terminationGracePeriodSeconds)
  • 配置 PodDisruptionBudget
  • Secret 不硬编码在 YAML 中

发布时

  • 滚动更新策略 maxUnavailable: 0
  • 观察 rollout status:kubectl rollout status
  • 新 Pod 全部 Ready 后再杀旧 Pod
  • 发布后检查 Events 和日志
  • 准备回滚命令:kubectl rollout undo

出问题时

  • kubectl get pods 看状态
  • kubectl describe pod 看 Events
  • kubectl logs / kubectl logs --previous 看日志
  • 检查 Service Endpoints 是否为空
  • 从 Pod 内部测试网络连通性
  • 检查节点资源:kubectl top nodes
  • 检查节点条件:DiskPressure / MemoryPressure / PIDPressure

监控

  • 安装 metrics-server(kubectl top)
  • 部署 Prometheus + Grafana 监控集群
  • 配置关键告警:Pod 重启、OOMKill、CrashLoop、节点 NotReady
  • 日志采集到集中平台(Loki / ELK)
  • 监控 CPU 节流率
  • 监控 PVC 磁盘使用率
  • 监控 Ingress 5xx 率和延迟

安全

  • RBAC 最小权限
  • NetworkPolicy 默认拒绝
  • Pod Security Standards(restricted 级别)
  • 镜像漏洞扫描
  • etcd 加密 Secret
  • 定期轮换 ServiceAccount Token

总结

K8s 排障没有玄学,所有问题都在数据里。记住核心方法:

  1. 从下到上:Node → Pod → Service → Ingress,逐层排查
  2. 三个命令走天下kubectl get(看状态)、kubectl describe(看事件)、kubectl logs(看日志)
  3. 80% 的问题集中在:配置错误、资源不足、健康检查失败、网络不通
  4. 最好的排障是预防:健康检查、资源限制、优雅关闭、监控告警

当你能熟练使用 kubectl describe pod 看 Events,并能快速判断是哪个层出了问题,K8s 排障就不再令人恐惧了。

💬 最后互动:你在 K8s 上遇到过最诡异的 bug 是什么?我先来------一个 Pod 白天正常,每晚 3 点准时 CrashLoopBackOff,排查了三天,最后发现是凌晨 3 点有一个备份 CronJob 把节点的 CPU 和内存占满,导致 API Pod 被 OOMKill。备份任务和生产应用跑在同一个节点上,没有做资源隔离。加上 Node Affinity 和资源 limits 后问题解决。评论区聊聊你的故事。

相关推荐
javachen__1 小时前
Docker一键清理 + 全局限制,告别日志报满
java·docker·容器
三垣网安2 小时前
记一次渗透测试 | 教育src漏洞分享(2)
网络·安全·web安全·网络安全
mengge.cloud2 小时前
Linux三剑客 grep sed awk 小白基础教程
linux·运维·服务器·网络
彭友圈1013 小时前
K8s 集群部署方法与原理总结
云原生·容器·kubernetes
dongchen。3 小时前
K8s部署实操
云原生·容器·kubernetes
xiaoxiangsiyan3 小时前
全网IPv6规模化改造实战指南
运维·网络·笔记·云原生·自动化
高卧怡怡4 小时前
Kubernetes的部署和环境配置
云原生·容器·kubernetes
微学AI4 小时前
我把 800 行报错日志压成一张排障卡:用蓝耘元生代做 FastAPI 日志根因分析
网络·oracle·fastapi
IanSkunk4 小时前
眼视光设备全周期台账:从验收入库到使用效果数据化的管理闭环
大数据·网络·人工智能