
本文适合有 Java 开发经验、Docker 基础、想系统入门 K8s 的工程师前言为什么 Java 开发者必须学 Kubernetes如果你曾经被以下问题困扰过那这篇文章就是为你写的上线新版本时要停服维护用户投诉不断大促期间服务器撑不住手动扩容永远比流量来得慢十几个微服务各有各的配置部署一次耗费半天某个实例挂了凌晨三点被告警短信吵醒Kubernetes简称 K8s正是为解决这些问题而生的。它是 Google 内部运行十几年的 Borg 系统的开源版本如今已成为云原生时代事实上的容器编排标准。对于 Java SaaS 开发者而言K8s 不只是运维的事——它深刻影响着你的应用如何打包、如何暴露接口、如何感知配置变化。更重要的是随着 AI 大模型推理服务越来越多地以容器形式交付K8s 成了连接 Java 业务层与 AI 能力层的核心底座。本文是系列的开篇我们会用大量类比和图示把 K8s 中最核心的四个概念——Pod、Deployment、Service、Namespace——讲透讲明白并全程结合 Java SaaS 场景。一、K8s 整体架构速览在深入具体对象之前先建立一张地图。┌─────────────────────────────────────────────────────┐ │ Kubernetes 集群 │ │ │ │ ┌──────────────────────┐ ┌─────────────────────┐ │ │ │ Control Plane │ │ Worker Nodes │ │ │ │ ┌────────────────┐ │ │ ┌───────────────┐ │ │ │ │ │ API Server │ │ │ │ kubelet │ │ │ │ │ ├────────────────┤ │ │ ├───────────────┤ │ │ │ │ │ Scheduler │ │ │ │ kube-proxy │ │ │ │ │ ├────────────────┤ │ │ ├───────────────┤ │ │ │ │ │ etcd │ │ │ │ 容器运行时 │ │ │ │ │ ├────────────────┤ │ │ │(containerd)│ │ │ │ │ │ Controller │ │ │ └───────────────┘ │ │ │ │ │ Manager │ │ │ │ │ │ │ └────────────────┘ │ │ Pod Pod Pod Pod │ │ │ └──────────────────────┘ └─────────────────────┘ │ └─────────────────────────────────────────────────────┘你需要记住的核心角色组件类比职责API Server前台接待所有操作的入口kubectl命令最终都发给它etcd数据库存储集群所有状态是 K8s 的大脑记忆Scheduler调度员决定 Pod 运行在哪个 Node 上Controller Manager保安队长持续确保实际状态 期望状态kubelet节点执行者在每个 Worker Node 上执行命令管理 PodJava 开发者类比如果把 K8s 集群比作一个微服务架构Control Plane 就是注册中心 配置中心 任务调度器的合体Worker Node 就是执行业务逻辑的服务实例。二、PodK8s 的最小调度单元2.1 Pod 是什么很多人第一次看到 Pod 这个概念时会问它跟容器有什么区别一句话区分容器Container是隔离的运行环境Pod 是 K8s 管理容器的最小单元一个 Pod 里可以运行一个或多个紧密协作的容器它们共享网络命名空间和存储卷。想象你的 Java SaaS 应用需要一个主服务容器旁边还跑着一个日志收集的 sidecar 容器。这两个容器封装在同一个 Pod 里它们像室友一样共享localhost网络但对外是统一的 IP。┌─────────────────────── Pod ───────────────────────┐ │ │ │ ┌────────────────┐ ┌────────────────────────┐ │ │ │ Java 主容器 │ │ Fluentd 日志 sidecar │ │ │ │ :8080 │ │ │ │ │ └────────────────┘ └────────────────────────┘ │ │ │ │ 共享 Network Namespace同一 IP │ │ 共享 Volume日志目录挂载点 │ └───────────────────────────────────────────────────┘ │ Pod IP:10.244.1.232.2 写第一个 Pod YAML理解了概念动手写一个部署 Spring Boot 应用的 Pod这段 YAML 是你与 K8s对话的语言。每个字段都是一次声明你期望的状态是什么K8s 负责实现它。# java-saas-pod.yamlapiVersion:v1kind:Podmetadata:name:java-saas-appnamespace:production# 所属命名空间后文详解labels:app:java-saasversion:1.0.0tier:backendspec:containers:-name:spring-boot-appimage:your-registry/java-saas:1.0.0ports:-containerPort:8080# 容器监听端口仅声明用不等于对外暴露env:-name:SPRING_PROFILES_ACTIVEvalue:production-name:DB_HOSTvalue:mysql-service# 通过 Service 名称访问数据库后文详解resources:requests:# 调度时保证分配的最低资源memory:512Micpu:250m# 250 毫核 0.25 个 CPU 核心limits:# 容器能使用的最大资源超出会被限速/OOM Killmemory:1Gicpu:1000mreadinessProbe:# 就绪探针何时接受流量httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:30# 容器启动后等 30 秒再检查periodSeconds:10livenessProbe:# 存活探针何时重启容器httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:60periodSeconds:15failureThreshold:3# 连续失败 3 次才重启避免误杀⚠️踩坑记录 #1资源 requests/limits 不设会有什么后果笔者曾在测试集群忘记设置resources导致某个内存泄漏的 Java 应用把整个 Node 的内存耗尽节点上所有 Pod 全部被驱逐引发雪崩。生产环境务必设置limits同时requests不要设得太低否则 JVM 在 Node 上启动时争抢内存会导致 GC 频繁。Java 应用的memory limits建议比 JVM-Xmx大 20-30%为 JVM 堆外内存Metaspace、DirectBuffer 等留余量。2.3 为什么不直接用 Pod 部署生产应用Pod 本身没有自愈能力。如果 Pod 所在的 Node 宕机Pod 不会自动迁移到其他 Node。这就是为什么实际生产中我们几乎不直接创建 Pod而是通过 Deployment 来管理。三、Deployment让 Pod 具备生产级能力3.1 Deployment 解决的三个核心问题想象你是一家 SaaS 公司的架构师你的 Java 服务需要高可用至少 3 个实例同时运行任意一个挂了自动拉起滚动发布新版本逐步替换旧版本不停服一键回滚发现新版本有 Bug能立刻退回上一版Deployment 正是为此而生。它在 Pod 之上增加了一个控制层声明我想要 3 个副本K8s 的 Controller Manager 就会持续让现实与声明保持一致。Deployment │ └── ReplicaSet副本集由 Deployment 自动管理 │ ├── Pod#1 (java-saas-app-7d9f8b-xk2p1)├── Pod#2 (java-saas-app-7d9f8b-mn4q7)└── Pod#3 (java-saas-app-7d9f8b-pz8r3)ReplicaSet 是 Deployment 和 Pod 之间的中间层每次更新 Deployment 的镜像都会创建一个新的 ReplicaSet滚动替换旧 ReplicaSet 的 Pod。你不需要直接操作 ReplicaSet这是 Deployment 的内部实现。3.2 生产级 Deployment YAML 详解下面这份配置包含了你在实际项目中会需要的大部分重要字段# java-saas-deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:java-saas-deploymentnamespace:productionlabels:app:java-saasannotations:# 发布说明便于 kubectl rollout history 查看kubernetes.io/change-cause:v1.2.0: 新增 AI 摘要功能优化 JVM GC 参数spec:replicas:3# 期望副本数selector:matchLabels:app:java-saas# Deployment 通过 Label 找到它管理的 Podstrategy:type:RollingUpdate# 滚动更新策略默认值rollingUpdate:maxSurge:1# 更新期间最多多出 1 个 Pod超出 replicas 数maxUnavailable:0# 更新期间最多 0 个 Pod 不可用零停机的关键template:# Pod 模板Deployment 根据此创建 Podmetadata:labels:app:java-saasversion:1.2.0spec:# 优雅终止给 Java 应用 60 秒处理完当前请求再关闭terminationGracePeriodSeconds:60containers:-name:spring-boot-appimage:your-registry/java-saas:1.2.0ports:-containerPort:8080env:-name:JAVA_OPTS# JVM 参数在 K8s 中必须用 UseContainerSupport# 否则 JVM 会读取宿主机内存而非容器 limitsvalue:--XX:UseContainerSupport-XX:MaxRAMPercentage75.0-XX:UseG1GC-Xlog:gc*:file/var/log/gc.log:time,uptimeresources:requests:memory:512Micpu:250mlimits:memory:1Gicpu:1000mreadinessProbe:httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:30periodSeconds:10successThreshold:1failureThreshold:3livenessProbe:httpGet:path:/actuator/health/livenessport:8080initialDelaySeconds:60periodSeconds:15failureThreshold:3# Pod 反亲和性避免 3 个副本都调度到同一个 Nodeaffinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:-weight:100podAffinityTerm:labelSelector:matchLabels:app:java-saastopologyKey:kubernetes.io/hostname⚠️踩坑记录 #2不加-XX:UseContainerSupport的灾难这是 Java 开发者上 K8s 最常见的坑之一。容器的内存limits设为 1Gi但 JVM 按宿主机 64Gi 来计算默认堆大小约 25% 即 16GiJVM 申请的内存远超limits触发 Linux OOM KillerPod 被杀死且报错仅有OOMKilled找起来极为费劲。Java 8u191、Java 10 均已支持-XX:UseContainerSupportJava 11 默认开启请务必确认你的基础镜像版本。3.3 常用 Deployment 操作命令# 应用配置create 或 update--record 已弃用改用 annotations 记录原因kubectl apply-fjava-saas-deployment.yaml# 查看部署状态会实时刷新直到 rollout 完成kubectl rollout status deployment/java-saas-deployment-nproduction# 查看发布历史前提是在 annotations 中记录了 change-causekubectl rollouthistorydeployment/java-saas-deployment-nproduction# 紧急回滚到上一个版本kubectl rollout undo deployment/java-saas-deployment-nproduction# 回滚到指定版本通过 history 查到的版本号kubectl rollout undo deployment/java-saas-deployment --to-revision2-nproduction# 手动扩缩容临时方案生产建议用 HPA详见第 04 篇kubectl scale deployment/java-saas-deployment--replicas5-nproduction滚动更新期间会发生什么以replicas3, maxSurge1, maxUnavailable0为例创建 1 个新版本 Pod → 集群共 4 个 Pod新 Pod 通过 readinessProbe → 摘掉 1 个旧 Pod → 集群共 3 个 Pod重复步骤直到所有旧 Pod 替换完毕整个过程始终有 ≥3 个可用实例实现真正零停机发布。四、ServicePod 的稳定门牌号4.1 为什么 Pod IP 不能直接用前面提到每个 Pod 有自己的 IP但这个 IP 是不稳定的——Pod 重启或重新调度后 IP 会变化。如果你的前端直接记住后端 Pod 的 IP那每次部署都要更新一遍完全不可维护。Service 解决了这个问题。它提供一个固定的虚拟 IPClusterIP底层通过 Label Selector 动态路由到符合条件的健康 Pod 上。外部/内部请求 │ ▼ ┌─────────────┐ │ Service │ ← 稳定的 ClusterIP:10.96.100.50 │(java-saas)│ 或 DNS: java-saas-service.production.svc.cluster.local └─────────────┘ │ ┌──────────┼──────────┐ ▼ ▼ ▼ Pod#1 Pod #2 Pod #3 ← Label: appjava-saas(10.244.1.5)(10.244.2.8)(10.244.3.2)(IP 可能随时变化)4.2 四种 Service 类型及使用场景K8s 提供了四种 Service 类型搞清楚它们的区别是生产部署的必备知识类型访问范围典型场景ClusterIP集群内部微服务间通信默认类型NodePort集群外部通过 NodeIP:Port测试环境快速暴露生产慎用LoadBalancer集群外部通过云厂商 LB云环境生产暴露AWS ELB、阿里云 SLBExternalName集群内访问外部服务将外部数据库映射为集群内 DNS4.3 配置 Java SaaS 的 Service先为后端 API 配置一个集群内部 Service其他微服务访问用# java-saas-service.yamlapiVersion:v1kind:Servicemetadata:name:java-saas-service# 这个名字就是集群内的 DNS 名namespace:productionlabels:app:java-saasspec:type:ClusterIP# 集群内部访问selector:app:java-saas# 匹配所有带此 Label 的 Podports:-name:httpprotocol:TCPport:80# Service 对外暴露的端口targetPort:8080# 转发到 Pod 的端口同一命名空间内其他 Java 服务通过http://java-saas-service即可访问。跨命名空间访问则用完整 DNShttp://java-saas-service.production.svc.cluster.local。对于需要对外暴露的场景配合 Ingress Controller详见第 03 篇使用比 NodePort/LoadBalancer 更优雅。但如果你只是需要快速测试# 快速测试用 NodePort不建议生产spec:type:NodePortports:-port:80targetPort:8080nodePort:30080# 固定端口号30000-32767 范围⚠️踩坑记录 #3Service selector 匹配不上导致 502有次发布后 Service 一直返回 502排查了半天发现新的 Deployment 的 Pod label 从app: java-saas改成了app: java-saas-api命名规范调整但 Service 的 selector 没有同步更新。Service 找不到任何匹配的 Pod请求无处可发。排查命令kubectl get endpoints java-saas-service -n production如果ENDPOINTS一列显示none说明 selector 匹配不上任何 Pod立刻检查 Label 是否一致。五、Namespace集群内的隔离舱5.1 Namespace 解决什么问题假设你们公司有三个团队共用一个 K8s 集群后端团队、AI 团队、前端团队。没有隔离的话资源互相抢占权限难以控制一个团队的误操作可能影响全局。Namespace 提供了一个软隔离机制资源隔离不同 Namespace 里的 Service 名称可以重复互不干扰权限隔离通过 RBAC 给不同团队不同 Namespace 的操作权限详见第 07 篇资源配额给每个 Namespace 设置 CPU/Memory 上限防止一个团队吃光集群┌────────────────────── K8s 集群 ──────────────────────┐ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ production │ │ staging │ │ ai-team │ │ │ │ │ │ │ │ │ │ │ │ java-saas │ │ java-saas │ │ llm-service │ │ │ │(v1.2.0)│ │(v1.3.0-rc)│ │(vllm)│ │ │ │ │ │ │ │ │ │ │ │ mysql-svc │ │ mysql-svc │ │ gpu-pool │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ 默认内置: kube-system / kube-public / default │ └─────────────────────────────────────────────────────┘5.2 Namespace 实战配置创建 Namespace 并配置资源配额防止无限制消耗集群资源# namespace-production.yamlapiVersion:v1kind:Namespacemetadata:name:productionlabels:env:productionteam:backend---# 为 production namespace 设置资源配额apiVersion:v1kind:ResourceQuotametadata:name:production-quotanamespace:productionspec:hard:# Pod 数量上限pods:50# CPU/Memory 总量限制所有 Pod 的 requests 之和不超过此值requests.cpu:20requests.memory:40Gilimits.cpu:40limits.memory:80Gi# Service/ConfigMap 等对象数量限制services:20configmaps:50persistentvolumeclaims:20---# LimitRange为没有设置 resources 的 Pod 设置默认值# 避免忘了写 resources的 Pod 无限制消耗资源apiVersion:v1kind:LimitRangemetadata:name:production-limitsnamespace:productionspec:limits:-type:Containerdefault:# 未指定 limits 时的默认值cpu:500mmemory:512MidefaultRequest:# 未指定 requests 时的默认值cpu:100mmemory:128Mimax:# 单个容器的最大值cpu:4memory:8Gi常用 Namespace 管理命令# 创建 namespacekubectl apply-fnamespace-production.yaml# 查看所有 namespacekubectl get namespaces# 查看 namespace 的资源配额使用情况kubectl describe resourcequota production-quota-nproduction# 为当前 kubectl 会话设置默认 namespace避免每次 -n productionkubectl config set-context--current--namespaceproduction最佳实践强烈建议用defaultNamespace 只做临时测试所有正式的工作负载都应该在专门命名的 Namespace 下。一个清晰的 Namespace 规划方案production/staging/development按环境划分infraPrometheus、Cert-Manager 等基础设施组件ai-platformLLM 推理服务详见第 08 篇六、综合实战部署一个最小化 Java SaaS 系统现在把前面学到的对象组合起来部署一个包含「Java API 服务 MySQL 数据库」的最小化 SaaS 系统。这段配置展示了真实项目中各对象的协作关系也是后续篇章所有例子的基础# complete-java-saas.yaml# 1. 命名空间apiVersion:v1kind:Namespacemetadata:name:my-saas---# 2. MySQL 部署简化版生产应用 StatefulSet详见第 02 篇apiVersion:apps/v1kind:Deploymentmetadata:name:mysqlnamespace:my-saasspec:replicas:1selector:matchLabels:app:mysqltemplate:metadata:labels:app:mysqlspec:containers:-name:mysqlimage:mysql:8.0env:-name:MYSQL_ROOT_PASSWORDvalue:changeme# 生产应使用 Secret详见第 02 篇-name:MYSQL_DATABASEvalue:saasdbports:-containerPort:3306resources:requests:memory:256Micpu:100mlimits:memory:1Gicpu:500m---# 3. MySQL Service仅集群内可访问apiVersion:v1kind:Servicemetadata:name:mysql-servicenamespace:my-saasspec:selector:app:mysqlports:-port:3306targetPort:3306---# 4. Java SaaS API DeploymentapiVersion:apps/v1kind:Deploymentmetadata:name:java-saas-apinamespace:my-saasspec:replicas:2selector:matchLabels:app:java-saas-apistrategy:type:RollingUpdaterollingUpdate:maxSurge:1maxUnavailable:0template:metadata:labels:app:java-saas-apispec:terminationGracePeriodSeconds:60containers:-name:apiimage:your-registry/java-saas:latestenv:-name:JAVA_OPTSvalue:-XX:UseContainerSupport -XX:MaxRAMPercentage75.0-name:SPRING_DATASOURCE_URL# 通过 Service 名称访问 MySQL无需关心具体 Pod IPvalue:jdbc:mysql://mysql-service:3306/saasdb-name:SPRING_DATASOURCE_PASSWORDvalue:changeme# 生产应使用 Secretports:-containerPort:8080resources:requests:memory:512Micpu:250mlimits:memory:1Gicpu:1000mreadinessProbe:httpGet:path:/actuator/health/readinessport:8080initialDelaySeconds:30periodSeconds:10---# 5. Java SaaS API ServiceapiVersion:v1kind:Servicemetadata:name:java-saas-api-servicenamespace:my-saasspec:type:NodePort# 快速测试用生产替换为 Ingressselector:app:java-saas-apiports:-port:80targetPort:8080nodePort:30080一键部署并验证# 部署所有资源kubectl apply-fcomplete-java-saas.yaml# 观察 Pod 启动状态-w 参数实时监听变化kubectl get pods-nmy-saas-w# 预期输出等待约 60 秒# NAME READY STATUS RESTARTS AGE# mysql-7c9d8f6b4-xk2p1 1/1 Running 0 2m# java-saas-api-5b8c9f7d6-mn4q7 1/1 Running 0 90s# java-saas-api-5b8c9f7d6-pz8r3 1/1 Running 0 90s# 访问 API替换 NODE_IP 为任意 Worker Node IPcurlhttp://NODE_IP:30080/actuator/health# 测试 Pod 间通信进入 api pod 内部 curl mysqlkubectlexec-itdeployment/java-saas-api-nmy-saas --\curlmysql-service:3306七、K8s 与 AI 大模型系列后续预览本文建立了 K8s 的基础认知框架。在后续篇章中我们会逐步深入第 02 篇用ConfigMap管理 Spring Boot 配置用Secret存储数据库密码和 API Key包括 OpenAI/通义千问的 Key第 08 篇在 K8s 中调度 GPU 资源部署 vLLM 推理服务Java 客户端通过 Service 调用大模型 APIAI 大模型与 K8s 的结合点在于大模型推理服务本质上是一个 HTTP 服务只是需要特殊的 GPU 资源调度。通过 K8s你可以像管理普通 Java 微服务一样管理 LLM 推理实例享受弹性伸缩、灰度发布、监控告警等一切能力。八、常见问题 FAQQ1K8s 和 Docker Compose 有什么本质区别Docker Compose 是单机多容器编排工具适合本地开发。K8s 是跨多台机器的容器集群管理系统提供高可用、弹性伸缩、服务发现等生产级能力。可以理解为Compose 是单机版K8s 是分布式版。Q2Deployment 和 StatefulSet 选哪个Deployment 适合无状态服务Java API 服务、Web 服务——每个 Pod 完全等价可以随意创建/销毁。StatefulSet 适合有状态服务MySQL、Redis、Kafka——每个 Pod 有固定的网络标识和持久存储删除再重建后数据不丢失。第 02 篇会详细讲解 StatefulSet。Q3kubectl apply和kubectl create有什么区别kubectl create只能创建不存在的资源资源已存在则报错。kubectl apply是声明式操作资源不存在则创建已存在则更新到 YAML 描述的状态。实际工作中几乎只用apply。Q4为什么我的 Java Pod 一直 CrashLoopBackOff最常见的原因① 镜像拉取失败检查镜像名和 registry 凭证② 应用启动报错kubectl logs pod-name -n namespace查看日志③ 资源不足被 OOM Kill检查kubectl describe pod pod-name看 Events 和Last State④ 健康检查超时readinessProbe 的initialDelaySeconds设得太短。Q5一个 Java SaaS 系统最少需要几个 Namespace最简配置production生产和staging预发两个就够了。随着团队规模扩大可按环境 职能双维度拆分加入infra基础设施、ai-platformAI 服务等。总结本文用 2500 字的 YAML 2500 字的散文讲解带你理解了 K8s 四大核心对象对象核心职责Java SaaS 使用场景Pod运行容器的最小单元承载 Spring Boot 应用容器Deployment管理 Pod 副本提供滚动更新/回滚部署无状态 Java API 服务Service为 Pod 提供稳定的访问入口服务发现、负载均衡Namespace集群内软隔离多环境、多团队资源隔离一句话总结K8s 的核心思想是声明式——你告诉它想要的状态它负责达到并维持这个状态。这与 Java 中的响应式编程、数据绑定框架的思想一脉相承。第02篇K8s 存储与配置管理ConfigMap、Secret、PV/PVC 实战——Java SaaS 多租户配置最佳实践将深入讲解如何优雅地管理 Spring Boot 的外部化配置以及在 K8s 中安全存储数据库密码、AI API Key 的完整方案。系列文章持续更新中欢迎关注、收藏、点赞三连支持如有问题欢迎评论区交流笔者看到必回。参考资料Kubernetes 官方文档Spring Boot on Kubernetes 最佳实践Java 容器化最佳实践 - Adoptium