k8s pod经常重启,导致代码执行了一半,这种问题如何解决呢?

1 个回答

  • 小四
    天一亮,时间就不属于我了。
    # 0. 先判因(5 分钟排查清单) ```bash # 看容器退出原因/次数 kubectl get pod <pod> -o wide kubectl describe pod <pod> # Events 里常见:OOMKilled、Liveness probe failed、Evicted、NodeNotReady 等 kubectl logs <pod> -c <container> # 当前日志 kubectl logs <pod> -c <container> --previous # 上一次崩溃前的日志 ``` 常见根因与现象: * `OOMKilled` → 内存不够/堆设置不当 * `Liveness probe failed` → 存活探针过苛刻或应用抖动 * `CrashLoopBackOff` + 非 137/143 退出码 → 代码 Bug/未捕异常 * `Evicted` (DiskPressure/MemoryPressure) → 节点资源压力 * `NodeNotReady`/网络问题 → 节点不稳 * 批处理/长事务被掐断 → 架构设计缺少**幂等+断点续跑** # 1. 治标:让“半途而废”不再伤害数据 ## 1.1 幂等 + 去重(必须) * 关键写操作**带唯一幂等键**(如订单号/任务ID)+ 唯一索引,重复提交直接 no-op。 * 事件流用 Kafka/RocketMQ:**至少一次投递 + 业务侧去重表**。 ## 1.2 断点续跑 / Checkpoint * 批任务把进度写**持久化存储**(DB/Redis):`task(id).stage=... offset=...` * 任务逻辑拆小步,每步“读→算→写(原子提交)”,失败可从最近 checkpoint 续跑。 * 文件/大对象处理:记录 byte offset / 分片号。 ## 1.3 优雅停机(优先落地) * 捕获 **SIGTERM**,停止接新活、flush、写 checkpoint、再退出。 * 配置: ```yaml spec: terminationGracePeriodSeconds: 60 containers: - name: app lifecycle: preStop: exec: { command: ["/bin/sh","-c","curl -s localhost:8080/health/quit || true; sleep 5"] } readinessProbe: { httpGet: {path: /health, port: 8080}, periodSeconds: 5, failureThreshold: 3 } livenessProbe: { httpGet: {path: /live, port: 8080}, initialDelaySeconds: 20 } ``` 服务端处理: * `/health/quit` 设置内部标志位:不再接收新请求/新任务(Readiness 变 `false`),等待在途完成再退出。 # 2. 治本:消除“经常重启”的根因 ## 2.1 资源与 JVM * **给够 requests/limits**,别让 Pod 因节点压力被驱逐: ```yaml resources: requests: { cpu: "500m", memory: "1Gi", ephemeral-storage: "1Gi" } limits: { cpu: "2", memory: "2Gi", ephemeral-storage: "5Gi" } ``` * **Java**: * JDK 8u191+ 默认识别 cgroup;仍建议显式:`-XX:+UseContainerSupport -Xms1g -Xmx1g -XX:MaxRAMPercentage=60` * 避免 Xmx≈limit(留出 metaspace、线程栈、直接内存、缓冲区) * 观察 GC 日志,确认不是频繁 Full GC 触发探针失败 ## 2.2 探针别太“狠” * 常见误杀:启动慢/偶发抖动 → liveness 连续失败触发重启。 * 建议: * 增大 `initialDelaySeconds`/`timeoutSeconds`/`failureThreshold` * 引入 **startupProbe**,启动阶段仅用它判活,启动完再启用 liveness: ```yaml startupProbe: { httpGet: {path: /live, port: 8080}, failureThreshold: 30, periodSeconds: 2 } ``` * 调试期间可以**暂时移除 liveness**(只留 readiness),防止误重启。 ## 2.3 调度与节点稳定性 * 节点经常 `NotReady`/`Evicted`: * 给关键服务加 **PodDisruptionBudget (PDB)** 与优先级: ```yaml apiVersion: policy/v1 kind: PodDisruptionBudget spec: { minAvailable: 1, selector: { matchLabels: { app: core-svc } } } ``` * 设 **anti-affinity** 让副本分散节点;检查节点磁盘与 `imageGC`、`evictionHard`。 * 有状态/本地缓存型任务:考虑 **StatefulSet** + 本地持久卷。 ## 2.4 区分“服务”与“作业” * **长时批处理/单次任务** → 用 **Job/CronJob**,不是 Deployment。 * `restartPolicy: OnFailure`,`backoffLimit` 控制重试,`ttlSecondsAfterFinished` 回收历史。 * 任务内部实现**幂等+断点**,失败可自动续跑。 * **在线请求处理** → Deployment + 优雅停机 + 去重。 # 3. 预防复发:发布与流量治理 * **滚动发布**参数:`maxUnavailable: 0`、`maxSurge: 1`,配合 `minReadySeconds`,避免未就绪承压。 * 网关/客户端:对长耗时动作做**重试 + 幂等**,设置**超时**与**断路器**(避免探针抖动放大)。 * 熔断/限流:在高峰期避免探针失败→重启→雪崩。 # 4. 快速模板(你可以直接套) **A. Java 应用启动参数(示例)** ```bash JAVA_TOOL_OPTIONS=" -XX:+UseContainerSupport -XX:MaxRAMPercentage=60 -XX:InitialRAMPercentage=60 -XX:MaxMetaspaceSize=256m -XX:+ExitOnOutOfMemoryError " ``` **B. 任务级幂等伪码** ```java if (!repo.tryStart(taskId)) return; // 基于唯一索引的“抢占”行 try { Step s = repo.loadCheckpoint(taskId); while (hasNext(s)) { doStep(s); // 步幂等:写入用 UPSERT/唯一键 repo.saveCheckpoint(taskId, s.next()); } repo.markSuccess(taskId); } catch (Exception e) { repo.markFailed(taskId, e); throw e; } ```
    发布于 2025-02-11 00:08 •IP属地 湖北
    举报
    评论
    收藏