kubectl get pods一看,CrashLoopBackOff、ImagePullBackOff、OOMKilled、Pending------满屏红字,不知道从哪下手。K8s 排障和传统服务器排障完全不同:你没法 SSH 上去看日志,Pod 说没就没,网络是软件定义的。但 K8s 排障也有方法论------从 Pod 到 Service 到 Ingress,层层排查,每一层都有明确的命令和检查点。本文用 PMS 项目在 K8s 上的真实故障,讲透每个常见问题的排查过程和根因。
目录
- 排障方法论:从下到上四层模型
- [Pod 生命周期与常见状态](#Pod 生命周期与常见状态)
- CrashLoopBackOff:应用启动失败
- OOMKilled:内存不足被杀
- [Pending:Pod 调度不上去](#Pending:Pod 调度不上去)
- ImagePullBackOff:镜像拉不下来
- [Service 不通:Endpoint 为空](#Service 不通:Endpoint 为空)
- [Ingress 502/503:从入口到后端](#Ingress 502/503:从入口到后端)
- [网络策略与 DNS 问题](#网络策略与 DNS 问题)
- [CPU 节流:为什么 limits 设了还是慢](#CPU 节流:为什么 limits 设了还是慢)
- [磁盘压力与 PVC 问题](#磁盘压力与 PVC 问题)
- [PMS 实战:船岸同步在 K8s 上的三个故障](#PMS 实战:船岸同步在 K8s 上的三个故障)
- 必备排障命令速查表
- 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。
排查:
kubectl logs显示导出开始后约 15 秒,Pod 被杀kubectl top pod显示内存从 300MB 飙升到 1.1GB- 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 pod 看 OOMKilled |
| 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。大文件加上并发同步,很快把节点磁盘占满,触发节点驱逐。
修复:
- 配置 ephemeral-storage 限制:
yaml
resources:
requests:
cpu: 200m
memory: 512Mi
ephemeral-storage: 1Gi
limits:
cpu: 1000m
memory: 2Gi
ephemeral-storage: 5Gi # 限制临时存储
- 大文件流式处理,不落盘:
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);
- 挂载 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 排障没有玄学,所有问题都在数据里。记住核心方法:
- 从下到上:Node → Pod → Service → Ingress,逐层排查
- 三个命令走天下 :
kubectl get(看状态)、kubectl describe(看事件)、kubectl logs(看日志) - 80% 的问题集中在:配置错误、资源不足、健康检查失败、网络不通
- 最好的排障是预防:健康检查、资源限制、优雅关闭、监控告警
当你能熟练使用 kubectl describe pod 看 Events,并能快速判断是哪个层出了问题,K8s 排障就不再令人恐惧了。
💬 最后互动:你在 K8s 上遇到过最诡异的 bug 是什么?我先来------一个 Pod 白天正常,每晚 3 点准时 CrashLoopBackOff,排查了三天,最后发现是凌晨 3 点有一个备份 CronJob 把节点的 CPU 和内存占满,导致 API Pod 被 OOMKill。备份任务和生产应用跑在同一个节点上,没有做资源隔离。加上 Node Affinity 和资源 limits 后问题解决。评论区聊聊你的故事。