K8S核心三件套:Pod、Deployment、Service与Spring AI部署实战

发布时间:2026/9/17 15:19:08
K8S核心三件套:Pod、Deployment、Service与Spring AI部署实战 1. 先别急着敲命令搞懂 K8S 到底在解决什么聊 K8S 之前我想先吐槽一个特别常见的现象网上铺天盖地的部署教程一上来就让你kubectl create deployment结果你照抄跑通了但 Pod 换个 IP 服务就断滚动更新到一半服务不可用节点一重启整套环境趴窝。你以为是命令敲错了其实是你压根没搞懂 K8S 里这三层概念的设计意图。我这个文章标题写的是10 分钟讲透但说实话K8S 想 10 分钟完全讲透是不可能的我能做到的是把最核心的 Pod、Deployment、Service 三个对象讲到你心里有数让你在部署 Spring AI 这类 AI 应用时不再对着 YAML 文件发愁知道每一行配置到底在干嘛。先回答一个问题为什么有了 Docker 还不够非要搞出 K8SDocker 解决的是单机容器化的问题。你写了一个 Spring AI 应用docker build打个镜像docker run就能跑起来确实很方便。但生产环境不是这样的——你会有多个实例分摊流量会有版本迭代会有某个实例挂了需要自动拉起会有服务之间互相调用的地址发现。Docker 不管这些docker run只能在你手动执行的那一刻拉起一个容器容器崩了它就崩了没人管。K8S 的定位就是容器编排平台。说白了你告诉它我要跑 3 个副本它负责保证这 3 个副本一直存在你说我要升级镜像版本它负责让新旧版本平滑切换你说我要暴露服务给外部访问它负责给你一个稳定的入口。这些能力分别对应本文的主角Deployment、Pod、Service。我见过不少做 AI 全栈的开发者模型调得飞起一遇到部署就头疼。其实从开发到部署的思维转变核心就一句话开发时你关心代码逻辑部署时你关心的是运行环境的状态。K8S 就是帮你管理这种状态的工具而 Pod、Deployment、Service 就是描述状态的三种语言。2. PodK8S 里最小的运行单元不是你想象的单容器很多新手第一次见 Pod 都会困惑K8S 不是管容器的吗怎么最小单位不是容器而是 Pod先用生活类比解释容器就像集装箱里的单个物品Pod 则是打包好的托盘。K8S 不直接搬运单个物品而是以托盘为单位搬运。为什么这样设计因为有些应用场景下多个容器必须待在同一个托盘上——共享网络、共享存储、同生共死。2.1 Pod 的本质共享网络命名空间与存储卷一个 Pod 里的多个容器共享同一个网络命名空间。什么意思就是同一个 Pod 里的容器通过localhost就能互相访问端口不能冲突。它们还共享存储卷——Pod 挂载的 Volume容器 A 往里写文件容器 B 能读到。典型例子你的 Spring AI 应用需要边跑业务边把日志收集到中心化平台。你可以把 Spring Boot 容器和 Filebeat 日志采集容器放进同一个 PodFilebeat 直接读共享 Volume 里的日志文件。这两个容器生命周期强绑定要么一起起要么一起挂。2.2 单容器 Pod 与多容器 Pod 的取舍实际生产中绝大多数 Pod 是单容器的。多容器 Pod 只用在容器间耦合极强的场景比如Sidecar 模式主容器跑业务Sidecar 容器负责日志收集、代理转发、配置同步等辅助任务。Adapter 模式主容器产生的日志格式不规范Adapter 容器帮忙转成监控系统能识别的格式。Ambassador 模式主容器访问外部服务时Ambassador 容器充当代理屏蔽外部环境差异。有一个热词提问很有代表性一个 Pod 中如果包含多个容器如果其中一个容器没有成功启动可能会影响其它容器吗答案是会而且比你想的更直接。Pod 的启动过程是先拉取所有容器的镜像然后按顺序启动 Init 容器如果有最后并发启动业务容器。任何一个容器失败退出K8S 会根据 restartPolicy 决定是否重启整个 Pod。注意是整个 Pod 一起重启不是单独重启某个容器。这就是 Pod 作为最小调度单位的含义——容器们共享同一个命运。2.3 Pod 的 IP 是临时工别把它当固定资产Pod 是 K8S 里最不值钱的对象因为它随时可能被销毁重建。节点宕机、Deployment 更新镜像、手动删 Pod都会导致 Pod 消失、新 Pod 在别的地方被创建。新 Pod 的 IP 大概率变了。这就是为什么你不能直接把 Pod IP 写死在配置文件里。我见过有人把服务 A 调用服务 B 的地址写成了192.168.1.100:8080结果 Pod 一重启就全乱套。Pod 是临时工Service 才是正式工这个理念请先记住。3. Deployment你负责声明想要什么它负责保证一直如此Deployment 是我最喜欢的 K8S 对象因为它完美诠释了声明式 API的设计哲学。你不用告诉它怎么做只需要告诉它最终状态是什么。比如你说我要 3 个 Nginx 副本。Deployment 会持续监控当前状态只要发现实际副本数少于 3就自动创建 Pod 补齐多于 3就销毁多余 Pod。这个机制称为Reconcile Loop调谐循环K8S 一切自动化能力的底层都是它。3.1 通过 Deployment 创建 Pod不要裸建 Pod新手最容易犯的错误就是直接用kubectl run或者 YAML 文件裸建 Pod不走 Deployment。裸建的 Pod 挂了就真的挂了Deployment 挂了你不用管它会自愈。所以生产环境请永远不要直接创建 Pod至少要用 Deployment 管理。看一个标准 Deployment YAMLapiVersion: apps/v1 kind: Deployment metadata: name: spring-ai-app spec: replicas: 3 selector: matchLabels: app: spring-ai-app template: metadata: labels: app: spring-ai-app spec: containers: - name: spring-ai-server image: registry.cn-hangzhou.aliyuncs.com/your-namespace/spring-ai-server:1.0.0 ports: - containerPort: 8080 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi env: - name: SPRING_AI_MODEL_PROVIDER value: deepseek这里面有几个关键信息replicas: 3声明副本数为 3。selector.matchLabelsDeployment 通过标签选择器找到它管理的 Pod。这个字段极其重要Pod 的 labels 必须与 selector 匹配否则 Deployment 管不到任何 Pod。template这是 Pod 的模板。Deployment 创建 Pod 时就是拿这个模板去创建的就像复印机一样。resources资源请求与限制。们待会专门讲。3.2 滚动更新Deployment 的拿手好戏假设你要把镜像从1.0.0升级到2.0.0。在传统方式下你可能需要先停掉所有旧容器再启动新容器中间有一段时间服务不可用。Deployment 提供滚动更新策略RollingUpdate默认配置下它会先创建一个新 Pod等新 Pod 就绪后再销毁一个旧 Pod依次交替直到全部替换。这样做的直接收益整个升级期间始终有 Pod 在提供服务实现不停服发布。而且如果新版本启动失败Pod 无法通过健康检查滚动更新会暂停旧版本 Pod 不会被全部销毁你可以快速回滚。滚动更新相关参数spec: strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1maxUnavailable: 0升级过程中不允许有 Pod 不可用牺牲一点速度换取高可用。maxSurge: 1允许额外多出一个新 Pod保证升级期间总有足够副本消化流量。3.3 Deployment 回滚出事了别慌哪怕做了滚动更新新版本还是可能有问题。Deployment 天然支持回滚# 查看历史版本 kubectl rollout history deployment/spring-ai-app # 回滚到上一个版本 kubectl rollout undo deployment/spring-ai-app # 回滚到指定版本 kubectl rollout undo deployment/spring-ai-app --to-revision2回滚操作本身也是一次滚动更新不会粗暴地停掉所有 Pod。这个能力在大模型应用迭代频繁的场景下特别有用——模型版本、Prompt 版本更新后出问题一键回滚不用手忙脚乱地重新部署。3.4 资源请求与限制K8S 调度与保护的钥匙这里必须展开讲因为 AI 应用部署翻车大多翻在资源上。requests你向 K8S申请的资源量。调度器根据各节点的剩余资源决定把 Pod 调度到哪台机器。比如你申请了cpu: 500m调度器会找一台至少能提供 500m CPU 的节点。limits硬性上限。容器最多能用多少资源超过会被杀掉CPU 会被 throttled内存超限直接 OOM Kill。热词里有2c4g的pod支持的并发量这种问题其实没有标准答案。2C4G2 核 CPU、4GB 内存的 Pod 能承受多少并发取决于你的应用每个请求消耗多少资源。但通过requests和limits你可以做压测估算先设limits为 2C4G用 JMeter 逐步加压观察 CPU 使用率和内存曲线找到拐点就是你的容量上限。经验之谈给 Spring AI 应用设置资源时requests别设太小否则调度器可能把 Pod 挤到一台资源紧张的机器上运行时会和邻居抢 CPU。但requests也别设太大否则集群资源浪费严重。先按requests为整机资源的 40%、limits为整机资源的 80% 起步压测后再调整。4. ServicePod 的浮动 IP稳定访问的定海神针前面强调过Pod IP 是临时工。那怎么让客户端稳定访问答案就是 Service。Service 是 K8S 里的负载均衡器 稳定的虚拟 IPClusterIP。你创建一个 ServiceK8S 会为它分配一个固定的 ClusterIP 和 DNS 名称客户端只管访问这个固定地址Service 负责把流量转发给背后的 Pod 们。4.1 Service 的三种类型ClusterIP、NodePort、LoadBalancer这三种类型解决了三个不同层级的访问需求类型访问方式使用场景ClusterIP集群内部通过 ClusterIP/DNS 访问服务间调用比如 Spring AI 应用调用 Redis 服务NodePort通过节点 IP NodePort 端口访问需要集群外部访问但没有云负载均衡器LoadBalancer通过云厂商负载均衡器访问生产环境对外暴露服务如阿里云 SLB部署在云上的生产环境对外入口推荐 LoadBalancer。K8S 创建 LoadBalancer 类型的 Service 时会调用云厂商的 API 自动创建一个负载均衡实例并把流量转发到各节点上的 NodePort再转到 Pod。4.2 Service 如何找到 PodSelector 标签选择器Service 和 Pod 之间靠标签选择器关联。看一个典型 Service 配置apiVersion: v1 kind: Service metadata: name: spring-ai-service spec: selector: app: spring-ai-app ports: - protocol: TCP port: 80 targetPort: 8080 type: ClusterIP这里的selector.app值为spring-ai-app它会匹配所有带app: spring-ai-app标签的 Pod。客户访问 Service 的 80 端口流量会被转发到任意一个符合条件的 Pod 的 8080 端口。这就是解耦的精髓Service 不关心背后的 Pod 有几个、IP 是什么、在哪个节点上只要 Pod 的标签匹配就参与负载均衡。Deployment 滚动更新时旧 Pod 销毁、新 Pod 创建Service 自动感知因为新 Pod 也带相同的标签。4.3 集群内服务发现DNS 名称直接访问K8S 内置了 DNS 组件CoreDNS每个 Service 创建后自动获得一个 DNS 名称。比如spring-ai-service创建在default命名空间那么别的 Pod 可以用这个名字访问它http://spring-ai-service:80跨命名空间访问http://spring-ai-service.default.svc.cluster.local:80这破解了微服务架构里的服务地址维护难题。你不需要在配置中心里手写每个服务的 IP 和端口K8S 的 DNS 已经把服务名变成了活地址。热词里有dubbo mesh(k8s service mesh)、K8S 之外的微服务治理话题。实际上 Service 只是解决了基础的流量负载均衡更细粒度的熔断、限流、灰度发布需要 Service Mesh如 Istio来做。但对于大部分 Spring AI 应用Service Deployment 已经足够。4.4 一个 Pod 中多个容器时的 targetPort 选择如果你的 Pod 是多容器 Pod比如主容器是 Spring AI 服务Sidecar 是日志采集器那 Service 的targetPort应该指向真正对外提供服务的容器端口。这不是一个技术难题但我在实际项目中见过因为targetPort写错把流量转发到了日志采集器的端口上导致请求全部超时的低级错误。多容器 Pod 的端口规划务必在 YAML 里写清楚。5. 三个对象串起来一个 Spring AI 应用的完整部署链路光讲理论没用我们来走一个完整的实操链路。假设我本地用 Docker 部署了一个 Spring AI 应用对接的是 DeepSeek 这类大模型接口现在要搬到 K8S 集群里跑。5.1 先把应用容器化这一步不是 K8S 的范畴但不到位后面全白搭。Dockerfile 示例FROM openjdk:17-jdk-slim WORKDIR /app COPY target/spring-ai-server.jar app.jar EXPOSE 8080 ENV JAVA_OPTS-Xms512m -Xmx1024m ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]构建并推送到镜像仓库docker build -t registry.cn-hangzhou.aliyuncs.com/your-namespace/spring-ai-server:1.0.0 . docker push registry.cn-hangzhou.aliyuncs.com/your-namespace/spring-ai-server:1.0.05.2 编写 Deployment YAML把大模型相关的配置通过环境变量注入不要写死在代码里apiVersion: apps/v1 kind: Deployment metadata: name: spring-ai-server labels: app: spring-ai-server spec: replicas: 2 selector: matchLabels: app: spring-ai-server template: metadata: labels: app: spring-ai-server spec: containers: - name: spring-ai-server image: registry.cn-hangzhou.aliyuncs.com/your-namespace/spring-ai-server:1.0.0 ports: - containerPort: 8080 name: http resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi env: - name: SPRING_AI_MODEL_API_KEY valueFrom: secretKeyRef: name: model-api-key key: api-key - name: SPRING_AI_MODEL_BASE_URL value: https://api.deepseek.com - name: SPRING_AI_MODEL_NAME value: deepseek-chat readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 20注意几个关键点readinessProbe就绪探针只有当/actuator/health返回 200 时Service 才会把流量转发到该 Pod。这是滚动更新不中断流量的核心保障。livenessProbe存活探针如果应用死锁或者假死探针失败多次后 K8S 会重启容器。Secret 管理API Key 不要直接写在 YAML 里用 K8S Secret 挂载。创建方式kubectl create secret generic model-api-key --from-literalapi-key你的keySpring AI 对接 DeepSeek 这类平台时用的就是 OpenAI 兼容的 API 格式环境变量名随 Spring AI 版本不同略有差异但思路一样模型服务地址、API Key、模型名称都是可配置的。5.3 编写 Service YAMLapiVersion: v1 kind: Service metadata: name: spring-ai-server spec: selector: app: spring-ai-server type: ClusterIP ports: - protocol: TCP port: 80 targetPort: 80805.4 部署并验证链路kubectl apply -f deployment.yaml kubectl apply -f service.yaml # 查看 Pod 状态 kubectl get pods -w # 查看 Service 和 Endpoints kubectl get svc spring-ai-server kubectl get endpoints spring-ai-serverkubectl get endpoints这一步很关键。Endpoints 列出了 Service 背后当前所有 Pod 的 IP 列表。如果 Endpoints 为空说明 Service 的 selector 没匹配到任何 Pod去检查标签是否一致。这是我排障时第一个看的地方。5.5 集群外访问再加一层 IngressClusterIP 只能在集群内部访问。外部用户要走 IngressapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: spring-ai-ingress spec: ingressClassName: nginx rules: - host: ai.example.com http: paths: - path: / pathType: Prefix backend: service: name: spring-ai-server port: number: 80这样用户访问http://ai.example.com请求经过 Ingress Controller → Service → Pod一条完整的链路就通了。6. 实战踩坑单节点集群部署 Spring AI 的资源陷阱与处理热词里我看到了单节点 k8s 上的若依微服务整套环境准不停服、不丢数据地迁移到阿里云 ECS迁移完成后压测人员用 JMeter 脚本做高并发测试这种真实需求。单节点集群部署微服务听起来简单实际坑特别多。我把踩过的坑整理出来够你少走很多弯路。6.1 单节点的镜像拉取策略问题单节点集群没有多节点的天然隔离性镜像拉取策略imagePullPolicy很关键。如果你用latest标签默认策略是Always每次部署都重新拉镜像如果镜像仓库在公网部署速度会被拖慢。但如果你用固定版本号标签默认策略是IfNotPresent本地有镜像就不拉了。经验本地开发环境把镜像策略改成IfNotPresent能大幅加快部署速度测试环境用固定标签 IfNotPresent保证每次拉到一个合理版本。6.2 资源 requests 设太低的血泪教训有一次我在单节点部署 Spring AI 应用配了requests.cpu: 100m内存100Mi。当时想法是单节点资源紧张能省则省。结果应用启动后频繁出现超时查看节点状态才知道同一个节点上还有 Redis、MySQL、Nacos 等多个 Pod大家都在挤 CPU。Spring AI 应用启动初期需要大量 CPU 做类加载和初始化但因为 requests 太低调度器认为这个 Pod 只需要很少的 CPU把它塞到了资源紧张的位置运行时不断被系统强制让出 CPU。后来把requests调到一个合理值500m并限制了其他中间件的资源整个环境就稳定了。单节点部署资源的是二次分配问题宁可 requests 不设太小给 K8S 调度器一个诚实的资源画像。6.3 不停服迁移到云 ECS 的实操套路从本地单机迁移到阿里云 ECS 这样的云服务器前提是 K8S 集群已经搭好。推荐顺序是先搭好云上集群确认kubectl能连上目标集群。把镜像推送到可访问的镜像仓库比如阿里云 ACR。用 Deployment Service 在云上创建整套服务但先把副本数设为 0。逐步把副本数调整为预期规模观察资源水位。做数据迁移如果有数据库按数据库迁移工具来。切换流量入口从域名解析层面切到云上 Ingress。压测验证用 JMeter 脚本跑高并发场景观察云上环境的表现和本地环境的对比。其中第 3 步先创建 Deployment 再调副本数是个小技巧可以先验证 YAML 语法和配置是否正确不会因为立即创建完整副本而影响本地环境。6.4 2C4G 的 Pod 到底能扛多少并发这是压测人员最喜欢问的问题。我只能说2C4G 只是一个基础条件没有固定并发答案。我在一次压测中发现Spring AI 应用如果只是做接口转发接收 Prompt → 调大模型 → 返回结果2C4G 可以支撑几十到一百左右的并发瓶颈往往不在 CPU/内存而在上游大模型 API 的响应延迟。如果你的应用还包含向量检索、本地模型推理那 2C4G 远远不够推理任务本身吃 CPU/GPU 很凶。想准确评估必须用 JMeter 或 wrk 做阶梯加压50 并发、100 并发、200 并发观察 TP99 延迟和错误率。不要凭经验猜压测数据才是最诚实的。6.5 节点 NotReady 后的自愈机制单节点集群最尴尬的是节点挂了整个环境全挂。K8S 的自愈能力默认依赖节点上的 kubelet 上报状态如果节点直接宕机Deployment 并不知道 Pod 已经挂了它会等待一个阈值时间默认 5 分钟后才会在别的节点重建 Pod。但对单节点集群来说没有别的节点所以自愈不起来。应对策略如果是开发测试环境单节点够用如果是生产环境至少搞个三节点集群1 主 2 从才能发挥 K8S 高可用的设计优势。6.6 Deployment 更新时 Unexpected 状态的排查热词里有一条unexpected status 503 service unavailable: cc switch local proxy failed while这种问题在代理环境里很常见。如果你的 K8S 节点配置了代理或者应用本身有本地代理Deployment 在拉镜像或访问外部服务时可能被代理拦截表现就是 Pod 反复 CrashLoopBackOff。排查思路kubectl logs pod名看容器日志区分是应用层错误还是 K8S 层错误。kubectl describe pod名看 Events重点看有没有Failed to pull image或NetworkPluginNotReady。检查节点上的/etc/resolv.confDNS 配置不对会导致 Service 域名解析失败。这类问题没有标准答案核心是养成看 Events 的习惯。K8S 的所有状态变化都会记录在 Events 里排障的第一站永远是describe而不是百度。6.7 零停机更新的验证手法要在准不停服、不丢数据的前提下更新应用我的建议是完整走一遍下面这个流程先做一轮压测拿到当前版本的基准数据。修改镜像版本号执行kubectl rollout restart deployment/spring-ai-server或更新镜像版本。观察滚动更新过程新 Pod 进入 Running 且 Ready 后旧 Pod 才被终止。压测过程中持续观察确认请求没有大量超时、错误率没有显著上升。如果滚动更新过程中出现大量超时大概率是readinessProbe配置不合理。新 Pod 还没完全就绪就被 Service 拉入流量池请求打到尚未初始化的实例上。解决方法是把readinessProbe.initialDelaySeconds调大确保应用先完成初始化再去接流量。7. 一个容易忽略的细节Pod 生命周期中的探针与优雅退出这部分内容来自我几次线上事故的总结非常值得你看完。7.1 崩溃容器是否会影响同 Pod 的其它容器回到热词里那个经典问题一个 Pod 中如果包含多个容器如果其中一个容器没有成功启动可能会影响其它容器吗结论是不只是可能而是必然带来影响但影响方式取决于 restartPolicy 和容器启动顺序。K8S 启动一个多容器 Pod 时会先启动 Init 容器如果有Init 容器失败则整个 Pod 一直处于初始化状态业务容器根本不会启动。业务容器启动后任何一个容器非正常退出kubelet 会根据restartPolicy默认 Always尝试重启整个 Pod。重启不是只重启那个崩溃的容器而是整个 Pod 被重新拉起所有容器重新经历启动过程。如果某个容器是sidecar模式比如日志收集这个容器崩溃导致 Pod 重启主业务容器的服务也会中断。所以我对多容器 Pod 的态度是能拆就拆别为了省事把多个服务塞进一个 Pod。除非你真的需要共享存储、共享网络、强生命周期绑定否则老老实实用单容器 Pod 独立 Deployment故障隔离性更好。7.2 优雅退出为什么kill -9是最后的手段Deployment 滚动更新时K8S 会给被终止的 Pod 发送 SIGTERM 信号等它优雅退出graceful shutdown。Spring Boot 应用应该监听 SIGTERM关闭线程池、释放数据库连接、保存当前处理中的请求然后再退出。如果你的应用没有处理 SIGTERM或者处理超时K8S 会再过一段时间默认 30 秒发送 SIGKILL 强杀。强杀会导致正在处理的请求被中断在高并发场景下可能产生大量 5xx 错误。Spring Boot 2.3 本身就支持优雅停机需要配置server.shutdowngraceful spring.lifecycle.timeout-per-shutdown-phase30s然后在容器启动命令里加上 Java 对 SIGTERM 的处理。没有这个配置滚动更新时你会看到不少 POST 请求报连接重置。7.3 探针配置的三个常见误区initialDelaySeconds设太小Spring AI 应用启动可能需要 30-60 秒加载模型配置、初始化连接池如果探针太早开始探测失败次数积累多了会被 K8S 误杀。periodSeconds设太小频繁探测会增大应用负载特别是/actuator/health本身要查询数据库、调用外部服务时别把探针间隔设成 1 秒。failureThreshold设太小默认 3 次失败就重启网络抖动可能导致无辜重启。建议设置 3-5 次。8. 结合 Spring AI 部署的实际编排把复杂配置放进 ConfigMap 与 SecretSpring AI 应用部署时配置文件的组织是个大问题。代码里不能写死模型服务地址也不能把 API Key 提交到 Git 仓库。这就需要 K8S 的 ConfigMap 和 Secret 配合使用。8.1 用 ConfigMap 保存非敏感配置比如模型名称、推理参数、超时时间等可以放进 ConfigMapkubectl create configmap spring-ai-config \ --from-literalspring.ai.model.namedeepseek-chat \ --from-literalspring.ai.model.temperature0.7 \ --from-literalspring.ai.http.timeout60s然后在 Deployment 里通过环境变量引用envFrom: - configMapRef: name: spring-ai-config这样改配置不用重新构建镜像只需更新 ConfigMap 并重启应用。但要注意Spring Boot 应用中环境变量不能完全替代application.yml的层级结构不同版本的 Spring AI 读取配置的 key 名称略有差异建议先本地验证再上 K8S。8.2 用 Secret 保存 API KeySecret 和 ConfigMap 用法相似但值会经过 Base64 编码存储注意这只是编码不是加密生产环境建议开 etcd 加密kubectl create secret generic spring-ai-secret \ --from-literalspring.ai.model.api-keysk-xxxxxx kubectl create secret docker-registry regcred \ --docker-serverregistry.cn-hangzhou.aliyuncs.com \ --docker-username你的账号 \ --docker-password你的密码第二个 Secretregcred是镜像仓库凭据如果你的镜像仓库需要认证必须在 Deployment 里配置imagePullSecrets否则拉镜像时一直报ImagePullBackOffspec: template: spec: imagePullSecrets: - name: regcred containers: - name: spring-ai-server image: registry.cn-hangzhou.aliyuncs.com/your-namespace/spring-ai-server:1.0.08.3 配置热更新真的不用重启吗有人会问ConfigMap 更新后应用能自动感知吗答案取决于你用的 Spring Cloud 还是原生 Spring Boot。原生 Spring Boot 默认不会自动刷新环境变量你需要重启 Pod 才能让配置生效。Spring Cloud Kubernetes 提供了动态刷新能力但会增加复杂度。对于 AI 应用我建议部署初期不要追求热更新先保证环境稳定配置变更就kubectl rollout restart一秒钟的事。9. 排障手册我用过的十几条 kubectl 命令与排查链路最后一节分享一份我自己整理的 K8S 排障手记不追求面面俱到只挑高频实用的。9.1 定位问题的第一反应describe 而不是 logs很多人的第一反应是kubectl logs这没错但不够。正确顺序是# 1. 看 Pod 状态 kubectl get pods -o wide # 2. 看 Pod 详细事件 kubectl describe pod pod-name # 3. 看容器日志 kubectl logs pod-name --tail200 # 4. 如果 Pod 之前崩溃过看上一次的日志 kubectl logs pod-name --previous # 5. 如果 Pod 一直处于 Pending查看调度原因 kubectl describe node node-namekubectl describe里最值得关注的是Events 段的Reason和Message。比如FailedScheduling、Insufficient cpu、FailedMount这些信息直接告诉你卡在哪一步。9.2 常见状态的速查表状态含义常见原因PendingPod 未调度到节点资源不足、节点污点、PVC 不存在ContainerCreating正在创建容器镜像拉取中、存储卷挂载失败Running正常运行Go/Spring 服务正常监听端口CrashLoopBackOff启动后反复崩溃启动命令错误、依赖服务没就绪、探针失败Error容器运行出错应用本身异常退出Terminating正在终止优雅退出超时、Finalizer 阻塞9.3 压测发现 503 Service Unavailable 的排查链路热词里那条unexpected status 503 service unavailable: cc switch local proxy failed式的问题如果发生在 K8S 环境排查链路大概是先确认服务本身是否正常kubectl get pods看 Pod 都是 Running 状态吗确认 Service 有没有后端kubectl get endpoints service名如果 Endpoints 不是期望的 IP 列表检查 selector。确认 Ingress 是否正常kubectl get ingress、kubectl logs -n ingress-nginx看是否有 503 转发的日志。确认 Pod 内应用日志kubectl logs pod名区分是网关问题还是应用问题。如果涉及外部代理/防火墙检查节点安全组、NAT 规则可能是出口代理把 Service 的请求拦截了。整个排查的核心理念是逐层剥离客户端 → Ingress → Service → Endpoints → Pod → 容器日志。K8S 提供了每一层的可观测手段关键是养成这套逐层排查的思维而不是一上来就重启一切。9.4 单节点集群上部署微服务整套环境的终极大坑把若依RuoYi这种微服务整套跑到单节点 K8S 上还要迁移到阿里云 ECS——最容易被忽略的是服务间调用的超时设置。微服务架构下服务 A 调服务 B服务 B 调服务 C超时链路一长任何一个环节的网络抖动都会被放大。单节点集群中所有 Pod 都在同一台机器理论上内网延迟极低但 CPU 资源被业务 Pod 争抢后应用层处理变慢超时照样发生。我的建议是在单节点部署微服务时先把基础设施Nacos、Redis、MySQL、Nginx的资源 requests 固定好再给业务服务分配剩余资源最后压测调整。资源规划的顺序不能乱否则业务一抖基础设施也跟着抖整个环境就雪崩了。10. 写在最后K8S 不是银弹但部署 AI 应用确实绕不开每次聊到 K8S 都有人问我一个小团队就一个应用有必要搞这么复杂吗我的答案是如果你的应用只需要一个实例、没有版本更新需求、挂了重启一下没人在意那确实不需要 K8SDocker Compose 就够了。但只要你有一个对外服务的应用哪怕并发不高你也会遇到发版期间服务中断的问题、实例挂了要手动拉起的问题、环境不一致复制不了问题的烦恼。K8S 的 Deployment 和 Service 这两个对象已经能解决 80% 的部署痛苦。回到 Spring AI 部署这个场景AI 应用迭代极其频繁模型参数、提示词、模型版本几乎每周都在变。每次变更都要经历构建镜像、推仓库、更新环境变量、重启应用的全流程如果没有一套编排系统兜底光靠手工操作早晚会出错。K8S 的好处是把这套流程变成声明式配置——代码即配置配置即真相。我个人在部署 Spring AI 应用时最常用的三步操作# 更新配置后重启 kubectl rollout restart deployment/spring-ai-server # 查看滚动更新进度 kubectl rollout status deployment/spring-ai-server # 查看当前 Pod IP kubectl get pods -o wide这三条命令加上对 Pod、Deployment、Service 三者关系的理解真的能覆盖 80% 以上的日常部署工作。最后分享一个我自己的体会学习 K8S 最忌讳上来就背概念先拿一个真实的 Spring AI 应用去部署遇到问题再回头看概念效率是最高的。Pod、Deployment、Service 这三个词你亲手部署一遍、升级一遍、回滚一遍、排障一遍就再也忘不掉了。