K8s 容器编排:Java 微服务团队的踩坑实录与收益账
Java Web 微服务系列 · 第 3 篇 · K8s 容器编排 阅读时长:约 35 分钟 本文写于 2026 年 6 月
引子:凌晨 3 点的告警电话
2021 年某日深夜,监控大屏突然飘红——某物流公司的订单服务挂了一台机器。值班同学爬起来 SSH 上去,看了一眼:进程没了。systemctl restart order-service 一把梭,服务起来了。然后第二台挂了,第三台挂了。值班同学对着黑漆漆的屏幕,骂了一句脏话。
第二天复盘,发现是部署脚本的 bug——新版本包没拷全,旧进程在内存里撑了 2 个小时才崩。2 个小时里这个服务有 6 个实例在跑,但其中 4 个已经"假死"——能 ping 通但 HTTP 503。更要命的是,值班同学是按 IP 一台台 SSH 上去重启的,光是重启就花了一个半小时。
“这日子没法过了。”
半年后,这家公司把所有 Java 微服务全部搬到了 K8s 集群。三年过去,再没人在凌晨 3 点因为"进程没了"被叫起来过。
但 K8s 也不是银弹。上 K8s 本身就是一个大坑——只是它把你从一个深渊里捞出来,又把你推进了另一个深渊。本文就按"为什么要上 / 解决什么痛点 / 收益是什么 / 有什么坑"四问,把这条路的全貌讲清楚。
一、为什么要上 K8s 集群:业务驱动的演进
1.1 Java 微服务部署的 4 大历史形态
从 2010 年到现在,Java 微服务的部署方式大概经历了 4 个阶段。每个阶段都不是"上一个错了",而是"业务规模逼着你升级"。
| 阶段 | 部署方式 | 代表工具 | 适用规模 | 典型痛点 |
|---|---|---|---|---|
| 单机直跑 | 物理机 / 虚拟机 + Tomcat / jar | scp、shell 脚本 | 1-5 个服务、< 50 QPS | 部署靠人、环境不一致、扩容靠买机器 |
| 脚本化 | 自动化脚本 + 进程管理 | Ansible、SaltStack、Supervisor | 5-20 个服务、< 500 QPS | 脚本维护成本高、回滚慢、灰度靠脚本拼 |
| 容器化(无编排) | Docker + docker-compose | Docker、docker-compose | 20-50 个服务、< 2000 QPS | 跨主机调度、容器自愈、滚动升级都靠人 |
| 容器编排 | K8s / Swarm / Mesos | K8s、Docker Swarm、Mesos、Nomad | 50+ 服务、2000+ QPS | 集群本身复杂度、运维门槛陡升 |
💡 原理:为什么"容器"不等于"K8s"
Docker 只解决了"打包一致"——开发机、测试机、生产机跑同一个 image。但 Docker 解决不了"50 个服务怎么调度到 30 台机器"“某台机器挂了容器怎么办"“滚动升级时如何不停机”。这些是"容器编排"要解决的问题。K8s 是容器编排的事实标准,不是因为它最好,而是因为它最早做大、生态最全。
1.2 业务量爆炸的拐点
什么时候"必须上 K8s”?没有标准答案,但有几个硬指标:
- 服务数 > 30:人工维护部署脚本开始失控——一个新人改一行配置要培训 1 周
- 实例数 > 100:单 IP 重启模式失效——光 SSH 串行重启就要 1 小时
- QPS > 1000 + 长尾延迟敏感:必须做弹性扩缩容——大促前临时扩容 50%,大促后缩回去
- 机房数 > 1:跨机房调度、流量切换需要平台化——脚本扛不住
上述任一指标突破,都应该认真评估 K8s。不要"为了上 K8s 而上 K8s"——K8s 本身的运维成本比裸 Docker 高 5-10 倍。
1.3 选型对比:K8s vs Swarm vs Mesos vs Nomad
“K8s 是唯一的答案吗?”不是,但事实上是。看个对比表:
| 维度 | K8s | Docker Swarm | Mesos | Nomad |
|---|---|---|---|---|
| 生态 | ⭐⭐⭐⭐⭐ CNCF 一哥 | ⭐⭐ Docker 官方但已停摆 | ⭐⭐⭐ 老牌但社区萎缩 | ⭐⭐ HashiCorp 出品,小而美 |
| 学习曲线 | 陡(概念多) | 平(Docker 用户秒上手) | 极陡(论文级) | 中(HashiCorp 一贯简洁) |
| 适用规模 | 数千节点、数万 Pod | 数百节点、几千容器 | 数万节点 | 数千节点 |
| 自愈能力 | 强(多种控制器) | 弱(基本靠 restart) | 中(依赖 Marathon) | 中(基础功能) |
| 服务网格 | Istio / Linkerd 成熟 | 无 | 无 | Consul Connect |
| 存储编排 | 强(CSI 标准) | 弱 | 强(Mesos + 各种 framework) | 中(CSI 支持中) |
| 运维成本 | 高(要专职团队) | 低 | 极高 | 中 |
| 招聘难度 | 容易(人才多) | 容易 | 难 | 中 |
📌 实践:为什么 99% 的团队最终选 K8s
- 生态绑死:所有云厂商默认托管 K8s(EKS / AKS / GKE / 阿里 ACK),自建也有 kubeadm / kOps
- 招聘市场最大:K8s 工程师池子 10 倍于 Mesos/Nomad
- 学习成本虽然高但只付一次:学会 K8s 概念后,5 年内不会被淘汰
- 二次开发友好:CRD + Operator 让任何复杂业务都能抽象
选 K8s 不是"技术最优",是"综合 ROI 最高"。
二、解决了什么痛点:5 大类问题
K8s 解决的不是"一两个痛点",而是Java 微服务规模化后的一整片痛。下面按"团队最痛的"到"运营层面的"排序。
2.1 部署自动化:告别凌晨 3 点的发布
传统部署的核心问题是"人工介入太多"——scp 包、kill 进程、start 进程,串行、慢、易错。K8s 把这一切抽象成一份 YAML:
| |
一次
kubectl apply完成的事:
- 拉镜像(v1.2.3)
- 按 maxSurge/maxUnavailable 策略滚动升级(每次只停 1 个,起来 1 个)
- 等 readinessProbe 通过才继续下一个
- 任意一步失败自动回滚
- 全程无需 SSH 任何机器
| 场景 | 传统方式 | K8s 方式 |
|---|---|---|
| 滚动升级 | 写脚本、串行执行、人工盯日志 | 改 YAML、kubectl apply、自动滚 |
| 灰度发布 | 维护两个集群、路由脚本切流量 | 改 replicas 比例、调整 Service selector |
| 回滚 | 找回老包、停止服务、重新部署 | kubectl rollout undo(秒级) |
| 扩缩容 | 买机器 → 装机 → 部署服务 | kubectl scale / HPA 自动(分钟级) |
2.2 自愈能力:进程挂了不用爬起来重启
K8s 的核心思想是"声明式期望状态"——你告诉它"我想要 3 个 order-service 实例在跑",它永远会努力维持这个状态。
- Pod 挂了 → kubelet 立刻重启(同节点)
- 节点挂了 → controller-manager 30s 内发现,在其他节点重新拉起
- 健康检查失败 → readinessProbe 不通过 → 流量切走 → livenessProbe 失败 → 重启
💡 原理:K8s 自愈的三个层次
- Pod 级别:kubelet 监控容器,挂了就在同节点重启
- 节点级别:kube-controller-manager 的 NodeController 30s 心跳,丢失后重新调度
- 集群级别:kube-scheduler 在 Pod 不可调度时(如资源不够)找其他节点
传统方式下"凌晨 3 点 SSH 上去重启"的事,K8s 在 30 秒内自动完成。
2.3 服务发现与负载均衡:告别 IP 漂移
Java 微服务最头疼的事之一:A 服务调用 B 服务,B 的 IP 变了,调用失败。K8s 用 Service + DNS 解决:
| |
部署后,集群 DNS(CoreDNS)会自动注册 order-service.default.svc.cluster.local → 后端 Pod 的虚拟 IP。Java 代码用 order-service:80 调用就行,IP 漂移全自动屏蔽。
| 维度 | 传统方式(Eureka / Nacos) | K8s 方式 |
|---|---|---|
| 注册中心 | 独立部署 Eureka / Nacos 集群 | 内置(Service + kube-proxy) |
| 客户端依赖 | Spring Cloud 组件 + SDK | 无(DNS 解析即可) |
| 健康检查 | Spring Boot Actuator 主动上报 | kubelet 探针(被动) |
| 跨语言支持 | Java 友好,其他语言要写 SDK | 任何语言都行 |
| 配置复杂度 | 中(要维护注册中心集群) | 低(YAML 写一遍就行) |
📌 实践:K8s DNS 与 Nacos 的取舍
简单服务用 K8s 内置 DNS 足够。但有状态服务(配置中心、服务治理、可观测)Nacos 仍是首选——它能管理服务元数据、配置、流量规则,K8s DNS 只能做"名字→IP"。
2.4 配置与密钥管理:告别 .properties 走天下
传统 Java 应用的配置问题:
- 改一个数据库地址 → 改 30 台机器的
application.properties→ 重启服务 - 密码明文写在配置文件里 → 提交到 Git → 安全事故
K8s 用 ConfigMap + Secret 抽象:
| |
热更新:用 Reloader 之类的 Operator,配置变更自动触发滚动重启。密钥管理:Secret 配合 etcd 加密 + RBAC 权限控制。
🎯 避坑点:Secret 默认是 base64 编码,不是加密!
任何能
kubectl get secret的人都能解出明文。生产环境必须:① 启用 etcd 加密;② 用外部密钥管理(Vault / 阿里云 KMS);③ RBAC 收紧到"运维 + 必要开发"。
2.5 资源隔离与调度:告别"那个服务又把内存吃光了"
传统物理机/虚拟机的痛:A 服务 OOM 杀掉了同机器的 B 服务。K8s 用 cgroup + namespace 做硬隔离:
| |
| QoS 等级 | 设置条件 | 行为 |
|---|---|---|
| Guaranteed | requests == limits | 最高优先级,最后被驱逐 |
| Burstable | requests < limits(有 request) | 中等优先级,资源紧张时被驱逐 |
| BestEffort | 没设 requests/limits | 最低优先级,资源紧张时最先被杀 |
🛑 误区警示:limits 设太低 = 性能不稳
JVM 堆内存默认会"贪"到 limit 才 GC。
memory.limits=512Mi但 JVM-Xmx没设 → JVM 启动后申请 1GB 内存 → OOM Kill。Java 容器必须显式设-XX:MaxRAMPercentage=70.0或-Xmx(详见第四章 4.7)。
三、收益是什么:可量化的账
老板最关心"花了多少钱,赚回多少"。这一节给能写进年度预算答辩的数字。
3.1 资源利用率:从 20% 到 60%
传统物理机部署:
- 平均 CPU 利用率 15-25%(高峰要预留 3 倍容量)
- 平均内存利用率 40%(JVM 堆固定分配)
K8s 部署:
- 平均 CPU 利用率 50-65%(requests/limits + 混部 + HPA)
- 平均内存利用率 55-70%(动态堆 + 超卖)
📌 实践:10 台物理机 vs 30 台物理机的故事
某电商公司搬 K8s 前用 30 台物理机跑 200 个微服务实例(平均 6.7 实例/机)。搬 K8s 后用 10 台物理机(多核高配)+ 充分利用 requests/limits + HPA,实例数提升到 350 个(平均 35 实例/机)。节省 20 台物理机 × 5 万/年 = 100 万/年。
3.2 发布效率:从 30 分钟到 3 分钟
| 步骤 | 传统 | K8s | 收益 |
|---|---|---|---|
| 打镜像 + 推仓库 | 5 分钟 | 1 分钟(Maven + Jib/Dockerfile) | 4 分钟 |
| 传包到 10 台机器 | 10 分钟 | 0(Pod 拉镜像) | 10 分钟 |
| 重启服务(滚动) | 10 分钟 | 1 分钟(K8s 自动) | 9 分钟 |
| 验证 + 监控 | 5 分钟 | 1 分钟 | 4 分钟 |
| 合计 | 30 分钟 | 3 分钟 | 节省 27 分钟 |
按每天发布 10 次算 = 节省 4.5 小时/天。开发体验直接拉满。
3.3 故障恢复时间:MTTR 从小时级到秒级
| 故障场景 | 传统 MTTR | K8s MTTR |
|---|---|---|
| 进程崩溃 | 5-30 分钟(人工发现 + 重启) | < 30 秒(自动重启) |
| 节点宕机 | 10-60 分钟(人工迁移) | < 2 分钟(自动迁移) |
| 机房故障 | 30-120 分钟(脚本切流量) | < 5 分钟(DNS/Ingress 切) |
| 配置错误 | 5-15 分钟(人工改文件 + 重启) | < 1 分钟(ConfigMap + Reloader) |
| 版本回滚 | 10-30 分钟(找回老包 + 部署) | < 10 秒(kubectl rollout undo) |
🎯 避坑点:MTTR < 30s 不是无脑的
K8s 自动重启 / 调度是默认的,但自动迁移不等于自动恢复。“Pod 在新节点起来了” ≠ “流量切过来了”——Service selector 要对、readinessProbe 要过、DNS 缓存要刷新。生产环境要配 PodDisruptionBudget + 反亲和 + 健康检查三件套,K8s 的"自愈"才能真正救你。
3.4 团队协作:从"运维是瓶颈"到"开发自服务"
传统流程:
| |
K8s 流程:
| |
开发自服务带来的收益无法直接量化,但让运维从"操作工"变成"平台工程师",团队效率指数级提升。
3.5 业务连续性:机房级故障 RTO < 30s
承接系列第 1 篇「异地多活」的话题:K8s 是异地多活的基础设施底座。
- 多集群联邦(Karmada / Kubefed)→ 跨机房调度
- Ingress + Global DNS → 跨机房流量切
- ArgoCD / GitOps → 配置漂移检测与自动修复
- Velero → 集群级备份与恢复
💡 原理:K8s 是"数据中心即一台机器"的抽象
上一篇文章讲异地多活是"把系统分散到多机房"。K8s 把这件事的成本降到 1/10——你不再需要写机房切换脚本,集群联邦 + DNS 切流 + 健康检查就能在 30 秒内完成 RTO < 30s 的机房级故障切换。
四、有什么坑:真实踩雷清单
这一节是全文最长的部分——因为"坑"是 K8s 学习曲线最陡的部分。不讲坑,上 K8s = 找死。下面 8 节按"踩坑顺序"展开。
4.1 二进制部署的复杂度:6 大子系统
K8s 集群不是"装一个软件"——它有 6 大核心组件要装、要配、要互相通信:
| 组件 | 角色 | 装在哪 | 启动参数数量 |
|---|---|---|---|
| etcd | 分布式 KV 存储(集群大脑) | 3 个 master | 3-5 个 |
| kube-apiserver | API 网关(唯一对外入口) | 3 个 master | 28+ 个 |
| kube-controller-manager | 控制器集合(保 reconcile 循环) | 3 个 master | 18+ 个 |
| kube-scheduler | 调度器(决定 Pod 放哪) | 3 个 master | 8+ 个 |
| kubelet | 节点 agent(管容器) | 所有节点 | 30+ 个 |
| kube-proxy | 网络代理(管 Service) | 所有节点 | 15+ 个 |
装完这 6 大件只是开始——还要装 CNI 网络插件(Calico/Flannel)、CoreDNS、Ingress Controller、Metrics Server、Helm、Dashboard……一套完整生产集群有 20+ 组件。
🛑 误区警示:kubeadm 不是"一键安装"
很多人以为
kubeadm init就是装好 K8s 了。真相是:kubeadm 只装 4 大件(apiserver / controller-manager / scheduler / kubelet),etcd 还得自己装,CNI 还得自己装,Ingress 还得自己装,所有生产级配置都得自己加。kubeadm 是"半成品"——它给你"会跑"的环境,不给你"能生产"的环境。
4.2 节点规划的血泪
K8s 集群"装在哪"是个大问题。生产集群一般分 3 种:
| 模式 | 节点组成 | 适用规模 | 复杂度 |
|---|---|---|---|
| all-in-one | 1 master + N worker(小集群) | < 50 Pod | 低 |
| 3 master + N worker | 3 master(HA)+ N worker | 50-500 Pod | 中 |
| 多 master 多机房 | 9+ master 跨机房 | 500+ Pod | 极高 |
🛑 坑:异构硬件混部是大忌
见过某公司用 5 台 IBM 老服务器(E5-2660 v2,20 核,64GB)+ 5 台消费级 i7/i9(64GB)+ 1 台 Xeon Gold 80 核 64GB。混部后:
- 资源利用率算不清:i9 内存 64G 是 2 根 32G,老服务器是 8 根 8G,节点间 OOM 行为不一致
- 调度不公平:80 核机器被大 Pod 吃掉,20 核机器闲着
- 故障域混乱:i7/i9 单点故障率高于服务器级 CPU
生产集群应该同构硬件(同型号、同 CPU 架构、同内存配置),最多分 2 档(master vs worker)。别想着"反正都是 x86,能跑就行"——K8s scheduler 会按资源调度,异构会让它的判断出 bug。
端口范围也是坑:默认 K8s Service NodePort 范围是 30000-32767,很多公司的安全策略会拦这个段。要么改范围(--service-node-port-range=8000-9000),要么提前和安全团队对齐。
4.3 系统优化的 20 个坑
K8s 对底层 Linux 有 20+ 项硬性要求。每一项不满足都可能让集群"跑不起来"或"跑着跑着崩":
4.3.1 swap 必须关
| |
不关会怎样:kubelet 默认要求关闭 swap(--fail-swap-on=true),开了 swap 容器会性能骤降(swap 拖慢 GC),且 kubelet 启动失败。
4.3.2 防火墙必须关
| |
不关会怎样:iptables 规则混乱,Pod 之间通信失败。
4.3.3 systemd-resolved 必须删
| |
🎯 避坑点:systemd-resolved 是 Ubuntu 上 K8s 的大坑
Ubuntu 22.04 默认启用 systemd-resolved,它会拦截 53 端口。CoreDNS 装上后起不来(端口冲突),表现为 coredns Pod 一直 CrashLoopBackOff,但
kubectl logs看不到明显错误——因为 coredns 进程根本启动失败。99% 的"CoreDNS 装不上"都是这个原因。
4.3.4 ulimit 必须调大
| |
不调会怎样:高并发下 Java 进程的"too many open files"错误。
4.3.5 ipvs 模块必须加载
| |
不加载会怎样:kube-proxy 退化为 iptables 模式,1000 个 Service 就会让 iptables 规则爆炸(性能 O(n²))。
4.3.6 内核参数 20+ 项
| |
这些参数少一个都可能在高并发下出怪事——比如"Syn_sent 状态堆积"、“nf_conntrack table full”。
生产建议:用 ansible 一键配置 20+ 项 sysctl + 加载 9 个内核模块,不要手敲。我在这上面栽过 3 次——每次都是少一个参数导致"线上跑得好好的,新机器扩上去就抖"。
4.4 证书管理的麻烦
K8s 集群里有 3 套独立的 CA(证书颁发机构):
| CA 用途 | 证书 | 数量 | 过期 |
|---|---|---|---|
| etcd-ca | etcd 集群内部通信 | 1 CA + 3 server cert | 默认 1 年 |
| kubernetes-ca | apiserver / kubelet / controller-manager / scheduler | 1 CA + 10+ cert | 默认 1 年 |
| front-proxy-ca | apiserver 聚合层(metrics-server 等用) | 1 CA + 1 cert | 默认 1 年 |
生成工具:cfssl 三件套(cfssl + cfssljson + cfssl-certinfo)。
🛑 坑:证书 hostname 漏一个 = 集群起不来
生成 apiserver 证书时,hostname 列表必须包含:
- 所有 master 的 hostname(master1, master2, master3)
- 所有 master 的 IP(10.0.0.x 格式)
- VIP(虚拟 IP,对外服务的)
- 集群内部地址(kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local)
- 127.0.0.1、localhost
漏任何一个,etcd 报 “x509: certificate is valid for …, not …”,表现是 apiserver 反复重启但 kubectl 连不上。
证书自动续期(1 年期限):生产集群必须用 cert-manager 之类的工具自动化,手动续期等于给自己挖坑。
TLS Bootstrapping:新 worker 节点加入集群时,自动签发 kubelet 客户端证书(避免把 CA 私钥分发到所有节点):
| |
🛑 坑:token-id 和 token-secret 暴露 = 任意节点能加入集群
这个 token 实际是"加入集群的入场券"。生产环境必须:① 缩短 token 有效期(默认 24h);② 审计 token 使用记录;③ 上线后立即吊销。不要把 token 提交到 Git 仓库(记忆库里有真实事故:某 bootstrap token 在私人笔记里登记,已泄露到 git 历史——前车之鉴)。
4.5 组件配置的 28+ 个参数
光 kube-apiserver 一个组件,启动参数就有 28+ 个:
| 类别 | 参数示例 | 作用 |
|---|---|---|
| 网络 | --bind-address、--secure-port、--advertise-address | 监听地址 |
| etcd | --etcd-servers、--etcd-cafile、--etcd-certfile | 后端存储 |
| 认证 | --client-ca-file、--tls-cert-file | TLS 双向认证 |
| 授权 | --authorization-mode=Node,RBAC | RBAC 权限控制 |
| admission | --enable-admission-plugins=... | 准入控制(12+ 种) |
| 聚合 | --requestheader-* | 聚合层(apiserver 内部 API 转发) |
3 个 master 几乎一样的配置,只有 --advertise-address 不同。手动复制 = 易漏:
🎯 避坑点:3 master 配置文件必须用 sed 自动化生成
见过有人手抄 3 份,结果 master2 漏了
--feature-gates=RemoveSelfLink=false,导致 1.28 之后整个集群升级不上去(RemoveSelfLink 已经在 1.16 弃用、1.18 删除)。正确做法:用脚本生成,参数化只有 IP 不同的部分。
controller-manager / scheduler / kubelet / kube-proxy 也有大量参数。生产集群必须有 1 份"配置审计"清单——记录每个参数的原因、默认值、生产值。别"反正跑起来了"——下次升级、扩容、排查时你会感谢现在的自己。
4.6 跑起来之后的坑
集群装好、组件跑起来、Pod 能起——但生产环境会冒出新的问题:
4.6.1 kubelet 启动失败
| |
原因:服务器重启后服务启动顺序乱了——kubelet 比 containerd 启动早,连不上 CRI socket。
解决:加 systemd After 依赖:
| |
4.6.2 代理后访问不到 Ingress
开发同学用 Charles / Fiddler 抓包时,发现 https://<your-domain> 进不来——Ingress 域名被代理拦截。
解决:代理软件里 bypass 集群内网域名(如 *.cluster.local、10.0.0.0/8)。
4.6.3 hostNetwork + hostAliases 解决内网域名
有些服务要访问内网老系统的固定 IP(不是 DNS),Pod 默认隔离网络访问不到。
| |
🛑 坑:hostNetwork = 失去 Pod 网络隔离
开了 hostNetwork,Pod 直接用宿主机网络,Service / DNS / 端口分配全失效。能不用就不用——只在调试老系统迁移时用,生产环境应该用 Service + Endpoint 解决。
4.6.4 metrics-server 起不来 = HPA 失效
HPA(Horizontal Pod Autoscaler)依赖 metrics-server 提供 Pod 资源数据。metrics-server 装好但kubectl top node 报 “Metrics API not available”——99% 是证书问题:metrics-server 的 --kubelet-certificate-authority 没指对。
4.7 Java 应用特定的坑
Java 跑在 K8s 里有几个"老问题"必须重新理解——不是 K8s 的锅,是 Java 自己的坑:
4.7.1 时区问题
Pod 默认 UTC 时区。new Date() 拿到的时间比东八区少 8 小时——日志时间、数据库时间、订单时间全错位。
解决(三选一):
| |
4.7.2 JVM 内存感知
JVM 早期版本不识别容器 cgroup 内存限制——Runtime.getRuntime().maxMemory() 返回宿主机内存,JVM 启动时按 1/4 算堆,结果容器 OOM Kill。
解决:JDK 8u191+ 加上 JDK 10+ 已支持 cgroup v1;JDK 11+ 支持 cgroup v2。但仍需显式声明:
| |
| 模式 | 优点 | 缺点 |
|---|---|---|
MaxRAMPercentage | 自动适配容器内存 | 调优时不可预测峰值 |
-Xmx 写死 | 行为可预测 | 换机器要重算 |
OpenJ9 memoryLimit | 准生产级,OpenJ9 内部感知 cgroup | 团队要学 OpenJ9 |
🎯 避坑点:JVM 调优必须和 Pod limits 配合
memory.limits=1Gi+JAVA_OPTS=-Xmx2g→ JVM 启动时被 K8s OOM Kill。memory.limits=1Gi+JAVA_OPTS=-XX:MaxRAMPercentage=70.0→ JVM 自动算 700MB 堆,正合适。
4.7.3 探针配置
| |
| 探针 | 失败后果 | 失败阈值 | 配多少合适 |
|---|---|---|---|
| readinessProbe | 流量切走(不杀进程) | 9 × 10s = 90s | 给应用启动时间 |
| livenessProbe | 重启 Pod | 6 × 10s = 60s | 比 readinessProbe 更严格 |
🛑 坑:livenessProbe 太严 = 雪崩
配
livenessProbe.failureThreshold=1→ 应用 GC 抖一下就重启。正确做法:livenessProbe 阈值 ≥ 2 次失败(至少 20s),给 GC / 业务高峰一点缓冲。见过某公司 livenessProbe 阈值 1,结果大促时 Java Full GC 1 秒卡顿 → livenessProbe 失败 → Pod 重启 → Full GC 又卡 → 雪崩式重启。
4.8 三种工作负载选错 = 数据丢失
K8s 有 3 种核心工作负载控制器,选错 = 业务崩:
| 控制器 | 适用场景 | 关键特性 | 典型应用 |
|---|---|---|---|
| Deployment | 无状态服务 | 副本数随机命名、Pod IP 不固定、可任意扩缩 | Web 服务、API 服务、网关 |
| StatefulSet | 有状态服务 | 副本名固定(pod-0/pod-1/…)、稳定的持久卷和 DNS | 数据库、消息队列、ZooKeeper |
| DaemonSet | 节点级守护 | 每个节点跑 1 个 Pod,新节点自动拉起 | 日志收集、监控 agent、存储插件 |
| 场景 | 选错结果 |
|---|---|
| MySQL 用 Deployment | 三个 Pod 各自写不同 PVC → 数据 3 份不一致 → 业务崩 |
| Redis 哨兵用 Deployment | Pod 名字带随机后缀 → Sentinel 配置里写的 redis-0 找不到 → 集群不可用 |
| Fluentd 日志收集用 Deployment | 副本数少于节点数 → 部分节点日志丢失 |
| Fluentd 日志收集用 StatefulSet | 副本数固定,新节点加入后不自动拉起 → 新节点没日志 |
🛑 避坑点:状态用 Deployment = 数据全丢
某团队图省事,MySQL 主从用 Deployment 部署。某天 K8s 升级,Deployment 滚动升级 → 旧 Pod 被 kill → 从库没来得及同步 → 数据丢失。正确做法:数据库永远用 StatefulSet + 稳定的 PVC + PodDisruptionBudget 严格控制。
五、给想上 K8s 的 Java 团队忠告
5 条经过血泪教训的务实建议:
- 不要为了上 K8s 而上 K8s——服务数 < 30、QPS < 1000 的团队,docker-compose + 1 个老运维就够了,别给团队加复杂度。
- 从托管 K8s 开始(EKS/AKS/ACK/GKE),别自建——自建 K8s 需要 1 个全职 SRE 团队维护,省下的人工费比托管费多。
- Java 应用先做容器化适配再上 K8s——时区、JVM 内存、启动时间、配置外置、探针,每一项都要测。
- 灰度上 K8s——新业务先上、存量业务后上。一个 200 服务的团队,半年内推 10% 业务到 K8s 已经很快了。
- 必须配专职或半专职 SRE——K8s 学习曲线 6-12 个月,让一个开发兼运维的团队会让所有人都痛苦。
💡 最后一条建议:把 K8s 当作"工具"而不是"银弹"
K8s 解决的是"规模化后的部署和运维效率"问题,它不解决业务本身的问题。业务架构差、代码质量差、监控缺失,上 K8s 不会让这些问题消失,只会让它们更复杂。
六、下一篇预告
系列第 4 篇(计划)会讲 Spring Cloud Gateway 在 K8s 上的部署与流量治理——Ingress 怎么配置、Service Mesh 要不要上、灰度发布怎么做。敬请期待。
附录:本文资源
📌 延伸阅读
- 系列第 1 篇:异地多活:Java Web 微服务的高可用终极形态
- 关联:系统架构 / K8s 分类下还有 5 篇前置文章(containerd / master 组件 / 高可用 / 单机环境 / 集群插件),建议按顺序读。
