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


目录

  1. 排障方法论:从下到上四层模型
  2. Pod 生命周期与常见状态
  3. CrashLoopBackOff:应用启动失败
  4. OOMKilled:内存不足被杀
  5. Pending:Pod 调度不上去
  6. ImagePullBackOff:镜像拉不下来
  7. Service 不通:Endpoint 为空
  8. Ingress 502/503:从入口到后端
  9. 网络策略与 DNS 问题
  10. CPU 节流:为什么 limits 设了还是慢
  11. 磁盘压力与 PVC 问题
  12. 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 状态列解读

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

2.3 万能排查第一步

# 这一个命令解决 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 现象

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

3.2 排查步骤

# 第 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一般错误未捕获异常、配置错误、连接数据库失败
137SIGKILLOOMKilled(内存超限)或被 kubectl delete 强杀
139SIGSEGV段错误(native 库问题)
143SIGTERM优雅关闭(正常终止,不是故障)

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 挂载单个文件:

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.

排查

# 检查连接串配置
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 启动时数据库还没就绪。

修复

# 方法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 现象

$ 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 排查

# 查看内存配置
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:

// ❌ 一次性加载全部数据到内存
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(), ...);

修复

// ✅ 流式写入 + 分页查询
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,并配置告警:

resources:
  requests:
    memory: 512Mi
  limits:
    memory: 2Gi

4.4 OOM 的两个层次

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

5. Pending:Pod 调度不上去

5.1 现象

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

5.2 排查

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

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

排查

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 常见原因

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 配置私有仓库凭证

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=your-username \
  --docker-password=your-password \
  -n pms
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

$ 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 访问不通:

$ 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。

$ 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 失败了。

kubectl describe pod pms-api-xxx -n pms | grep -A5 Readiness

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

# ❌ 错误
readinessProbe:
  httpGet:
    path: /health      # 这个路径返回 404
    port: 8080

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

7.4 从 Pod 内部测试 Service

# 进入一个临时 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

# 99% 是 Service Endpoints 为空
kubectl get endpoints <service-name> -n <namespace>

502 Bad Gateway

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

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

504 Gateway Timeout

# 应用处理太慢,检查 Ingress 超时配置
kubectl describe ingress <ingress-name>
# 增加超时
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 日志

# 找到 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 秒。

修复

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 间通信会被阻断:

# 检查 NetworkPolicy
kubectl get networkpolicy -n pms
kubectl describe networkpolicy default-deny -n pms

修复:添加允许规则:

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 解析失败

# 在 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 配置问题
# 自定义 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 空闲也会被节流到下一个周期。

# 查看 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 修复

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。

# 查看节点磁盘使用
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

kubectl get pvc -n pms
kubectl describe pvc <pvc-name> -n pms

常见原因:

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

11.3 PVC 满了

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

# 查看 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 分钟。

排查

# 同步任务日志显示时间正常,但触发间隔异常
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 被直接驱逐。

修复

# 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,同时节点出现磁盘压力。

排查

# 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 限制:
resources:
  requests:
    cpu: 200m
    memory: 512Mi
    ephemeral-storage: 1Gi
  limits:
    cpu: 1000m
    memory: 2Gi
    ephemeral-storage: 5Gi  # 限制临时存储
  1. 大文件流式处理,不落盘:
// ❌ 先存到磁盘再处理
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 作为临时目录:
volumes:
  - name: tmp
    emptyDir:
      sizeLimit: 2Gi
volumeMounts:
  - name: tmp
    mountPath: /tmp

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

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

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

修复

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

应用端响应取消令牌:

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 排查

# 查看 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/网络排查

# 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 节点排查

# 节点状态
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 日志和事件

# 集群事件(按时间排序)
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 一键诊断脚本

#!/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 后问题解决。评论区聊聊你的故事。

更多推荐