资讯详情

阿里云K8s部署Vue2+Nginx+SpringBoot2.5+Nacos2.0.3实战指南

📅 2026/10/20 9:59:48 | 华诺云谱 👁 阅读
阿里云K8s部署Vue2+Nginx+SpringBoot2.5+Nacos2.0.3实战指南
简介这是一套面向阿里云环境的Kubernetes一站式部署方案整合了Vue2前端、Nginx反向代理、Spring Boot 2.5后端服务以及Nacos 2.0.3注册与配置中心适合已掌握基础容器操作、希望快速搭建云原生微服务架构的中高级开发与运维人员。包体共16个文件压缩包约34.69MB核心包含8个YAML编排文件、2个Dockerfile镜像构建脚本、2个Shell一键部署脚本另有SQL初始化脚本、Nginx配置、业务JAR包及前端静态资源压缩包能够覆盖从数据库初始化、Nacos集群部署到前端与后端服务上线的完整链路。目前已有1341人学习下载。借助这套资源使用者可以直接参考其YAML划分方式与部署顺序理解Kubernetes下StatefulSet、Service、Ingress等对象在真实项目中的组合用法同时通过Dockerfile与部署脚本快速复现一套可运行的VueSpring BootNacos演示环境节省环境搭建与配置调试时间。1. 阿里云 K8s 上部署 Vue2 Nginx SpringBoot2.5 Nacos2.0.3为什么这次我不再用 Docker Compose说实话第一次在阿里云 K8s 上部署这套 Vue2 Nginx SpringBoot2.5 Nacos2.0.3 的组合时我踩了不少坑。这套技术栈很经典Vue2 做前端、Nginx 做静态资源与反向代理、SpringBoot2.5 提供 API、Nacos 负责注册中心和配置中心。但把它们拆成一个个容器再编排进 K8s跟你平时 docker-compose up 一下完全是两回事——镜像构建、配置注入、服务发现、健康检查、滚动更新每一层都有各自的性子尤其是 SpringBoot2.5 和 Nacos2.0.3 那一堆版本兼容问题稍不留神就会翻车。这篇笔记会把我自己走通这套流程的完整思路记下来包括镜像怎么做、K8s 清单里哪些配置不能省、Nacos 怎么接以及我在阿里云环境里遇到的那些坑。如果你是正在准备上 K8s 的 Java 开发或者被前端镜像、注册中心问题卡住的运维这篇内容应该能省你不少排查时间。我默认你已经有能用的 K8s 集群也了解基本概念但我尽量做到每一步都可复现。我会用模拟项目 X 来做示范说明部署思路和配置细节核心原则从这套组合出发换其他项目也是通用的。2. 镜像构建Vue2 静态资源与 SpringBoot2.5 应用的 Dockerfile 设计2.1 前端 Vue2 镜像多阶段构建从 node 到 nginx 的静态资源迁移Vue2 项目在本地跑的时候npm run dev 是开发模式资源都是实时编译的但上了生产环境你要的是 npm run build 产出的 dist 目录。这个目录里是编译后的 HTML、JS、CSS 和静态资源只需要一个 Web 服务器就能跑。我一般用 Nginx 来托管这些静态文件顺便把 /api 之类的请求代理到后端服务。前端镜像的 Dockerfile 我习惯写成多阶段构建先在一个带 Node 环境的镜像里把依赖装上、把项目构建出来然后再把构建产物拷到一个干净的 Nginx 镜像里。这样做的最大好处是最终的镜像体积会小很多——不需要带着几百兆的 node_modules 跑也减少攻击面。# 阶段一构建 Vue2 项目 FROM node:16-alpine AS build-stage WORKDIR /app # 先拷贝 package.json 和 package-lock.json利用 Docker 缓存层加速 COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com # 再拷贝源码并构建 COPY . . RUN npm run build # 阶段二Nginx 托管静态资源 FROM nginx:1.25-alpine # 拷贝构建产物到 Nginx 默认静态目录 COPY --frombuild-stage /app/dist /usr/share/nginx/html # 拷贝自定义 Nginx 配置覆盖默认配置 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]这段 Dockerfile 有两个值得注意的地方。我把 npm install 和源代码的 COPY 分成两步这是利用 Docker 的层缓存机制——只要 package.json 没变后面重新构建镜像时 npm install 这层就会复用缓存省掉大批重复的依赖安装时间。另外注册源我换成了国内镜像源在阿里云环境构建时速度快很多这个看你的网络情况。至于 Node 版本Vue2 项目一般用 16 版本比较稳用太新的 Node 偶尔会遇到 OpenSSL 相关的构建报错那是另一类麻烦。Nginx 的 alpine 版本体积小适合 K8s 环境——镜像越小在节点上的拉取和启动速度就越快。2.2 Nginx 配置反向代理 /api 到 SpringBoot 服务处理好 history 路由Vue2 项目如果是 history 模式的路由Nginx 必须配置 try_files 把所有非静态资源请求都指向 index.html否则你一刷新页面就 404。这是前端部署最容易犯的错误之一很多人本地跑没问题上了服务器刷新就白屏基本都是这个原因。另一个必须处理的是把 /api 请求代理到后端服务。server { listen 80; server_name _; # 前端静态资源根目录 root /usr/share/nginx/html; index index.html; # SPA history 路由支持非资源请求全部交给 index.html 处理 location / { try_files $uri $uri/ /index.html; } # 静态资源缓存 location /assets/ { expires 7d; add_header Cache-Control public, immutable; } # 接口反向代理到 SpringBoot 服务 location /api/ { proxy_pass http://backend-service:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这里有一个细节值得多说几句proxy_pass 的 URL 末尾带不带斜杠会导致完全不同的路径映射行为。我现在写的是 http://backend-service:8080末尾不带斜杠这样请求 /api/user/list 会被原样转发到后端的 /api/user/list。如果你写成 http://backend-service:8080/那么请求会被转发到 /user/list也就是把 /api 前缀吃掉了。这个差异我见过不少人在配置 Nginx 给别人做代理时搞混。另外backend-service 这个名字不是随便起的它就是后面 K8s Service 的名称这样 Nginx 就能通过 K8s 内部的 DNS 解析到后端服务的 ClusterIP。前端和后端在同一个命名空间的话直接用 Service 名字就行跨命名空间要写成 backend-service.some-namespace.svc.cluster.local。2.3 SpringBoot2.5 镜像Java 应用镜像的两种写法与 JVM 参数选择SpringBoot2.5 的镜像比前端复杂一点因为 Java 应用要打包成可执行 JAR而且 JVM 参数、时区、字符集这些都是需要在镜像里提前考虑的。最常见的做法是用 eclipse-temurin 的 JDK 镜像来运行 JAR 包。# 构建 SpringBoot 应用镜像 FROM eclipse-temurin:8-jre-alpine # 设置时区避免应用日志时间和宿主机不一致 RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone WORKDIR /app # 拷贝构建产物 COPY target/demo-app-0.0.1-SNAPSHOT.jar app.jar # JVM 参数通过环境变量传入方便 K8s 里动态调整 ENV JAVA_OPTS-Xms512m -Xmx512m -Dfile.encodingUTF-8 EXPOSE 8080 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]注意这里用 8-jre-alpine 是因为 SpringBoot2.5 的模拟项目 X 是基于 Java 8 的如果你的项目是 Java 11 或者 17换成对应标签就行。JAVA_OPTS 我特意留成了环境变量注入因为在 K8s 里你可能要根据实例规格动态调整堆内存——比如把 -Xmx 放到 Deployment 的 env 里设置而不是写死在镜像里这样同一个镜像在开发环境和生产环境可以有不同的内存配置。还有一点SpringBoot 内置的 Tomcat 默认端口是 8080但你不需要在代码里硬编码用环境变量覆盖就行。后面我会在 Deployment 里通过 SERVER_PORT 环境变量来控制这样更灵活。2.4 镜像仓库配置阿里云 ACR 并处理私有镜像拉取凭证K8s 节点在拉私有镜像的时候需要一个叫 imagePullSecret 的凭证否则一直报 ImagePullBackOff。我最初不知道要配置这个浪费了很多时间在排查网络问题上结果问题就出在凭证上。假设你用的是阿里云容器镜像服务 ACR配置凭证需要在 K8s 里创建一个 Secret。# 登录阿里云 ACR有专门的工具拉取登录口令 # 创建 secret 用于拉取私有镜像 kubectl create secret docker-registry acr-secret \ --docker-serverregistry.cn-hangzhou.aliyuncs.com \ --docker-username你的账号 \ --docker-password你的密码 \ -n demo # 在 Deployment 中显式声明使用该 secret这里如果你用 Docker Hub命令格式一模一样只是 docker-server 换成 https://index.docker.io/v1/。在阿里云上如果节点本身有权限访问 ACR也可以考虑给节点 RAM 角色授权就能免掉 secret 这一步。但用 kubectl create secret 的方式最通用也最容易排查问题。你的部署清单里只要在 template.spec 下加上 imagePullSecrets 配置把它指向这个 secret节点拉镜像时就会用它做认证后续的滚动更新也都会带上这个凭证不需要每个 Pod 重新设置。3. K8s 资源编排从 ConfigMap 到 Deployment 的整套清单3.1 命名空间与 ConfigMap独立 namespace 隔离环境敏感配置放 Secret我先创建一个独立的命名空间然后在里面部署整套应用。这样做的好处是后面清理环境时kubectl delete ns demo一下全都没了也不会和其他项目的资源混在一起。# 创建命名空间 kubectl create namespace demo命名空间本身很便宜没有任何性能损失。我见过很多人不管什么环境全塞在 default 里用名字来区分环境——这样有点危险因为一旦某天误操作资源很难理清。接着是 ConfigMap我习惯把 SpringBoot 应用的非敏感配置都放进去比如 Nacos 的地址、应用日志级别这些。apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: demo data: # Nacos 服务地址K8s 内部通过 Service 名访问 NACOS_SERVER_ADDR: nacos-headless:8848 # 注册到 Nacos 的命名空间 ID NACOS_NAMESPACE: public # Spring 激活的 profile SPRING_PROFILES_ACTIVE: k8s这里注意NACOS_SERVER_ADDR我用了nacos-headless这个服务名对应 Nacos 的 headless Service。这样设计的好处是让 SpringBoot 应用通过 K8s 内部 DNS 访问 Nacos而不用去记某个节点的 IP。配置分离之后同一个镜像在开发、测试、生产环境只需要替换 ConfigMap 就可以镜像本身不用重新构建。3.2 后端 Deployment镜像拉取策略、存活探针与优雅终止后端 Deployment 是整个部署的核心我一般会写得很细。资源请求和限制这些不用我多说你自己按实际调整即可比如你申请了 1 核 1G 的节点规格那资源 request 和 limit 就要做相应调整。但有几个字段是我觉得大家都应该注意的。apiVersion: apps/v1 kind: Deployment metadata: name: backend-app namespace: demo spec: replicas: 2 selector: matchLabels: app: backend-app template: metadata: labels: app: backend-app spec: # 使用 acr-secret 拉取私有镜像 imagePullSecrets: - name: acr-secret containers: - name: backend-app image: registry.cn-hangzhou.aliyuncs.com/demo/backend-app:1.0.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 name: http env: # 从 ConfigMap 中加载环境变量 - name: NACOS_SERVER_ADDR valueFrom: configMapKeyRef: name: app-config key: NACOS_SERVER_ADDR - name: NACOS_NAMESPACE valueFrom: configMapKeyRef: name: app-config key: NACOS_NAMESPACE - name: SERVER_PORT value: 8080 resources: requests: memory: 512Mi cpu: 250m limits: memory: 768Mi cpu: 500m # 存活探针应用假死时自动重启容器 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 # 就绪探针只有健康检查通过后才开始接收流量 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 lifecycle: preStop: exec: command: - sh - -c - sleep 15这里有很多细节是我后来才注意到的。存活探针的路径我指向了 /actuator/health这个得在 SpringBoot 项目的 pom.xml 里引入 Spring Boot Actuator 依赖并配置暴露端点否则探针会一直失败。我之前就把探针配置在根路径 / 上也能有响应——但那个响应不反映应用本身是否健康。用 actuator 的 health 端点才是检查应用自身状态的正解它会在数据库连接失败等情况时返回 DOWN恰好能被探针捕获。initialDelaySeconds 设成 60 秒是因为 SpringBoot2.5 应用在 Nacos 里完成注册需要时间——如果刚启动就去探测大概率是失败。另一个是.lifecycle.preStop上的 sleep 15这是优雅终止的关键。当 Pod 要销毁时K8s 会先发送 SIGTERM 信号给容器但在此之前如果你在 lifecycle.preStop 里写了 sleep它会先等这段时间再发信号给应用。这段时间内Service 已经从 Endpoints 列表里摘掉这个 Pod新的流量不会过来但正在处理的请求还有时间完成。我之前不写这个发布的时候经常出现 502。3.3 前端 Deployment 与 ServiceClusterIP 暴露后端接口注意镜像更新策略前端 Deployment 部署的是 Nginx 容器配置和后面 Service 紧密相关。部署好以后Nginx 的配置文件里的 backend-service 这个名字最终要能通过 K8s 内部的 Service DNS 解析到后端 Pod 的 ClusterIP。apiVersion: apps/v1 kind: Deployment metadata: name: frontend-app namespace: demo spec: replicas: 2 selector: matchLabels: app: frontend-app template: metadata: labels: app: frontend-app spec: imagePullSecrets: - name: acr-secret containers: - name: frontend-app image: registry.cn-hangzhou.aliyuncs.com/demo/frontend-app:1.0.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: http resources: requests: memory: 64Mi cpu: 50m limits: memory: 128Mi cpu: 100m readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5前端的运行机制是 Nginx静态资源托管本身不依赖后端所以 Nginx 容器只需要一个很轻的资源 request。但要注意一个问题前端的镜像每次构建都会因为 dist 内容的不同而生成新镜像如果复用旧标签K8s 节点上就会缓存旧镜像导致更新不生效——这个问题后面我在避坑篇里详细说。对于imagePullPolicy我一般都会把前端镜像的策略设置得更严格一些避免缓存带来的坑。3.4 Service 与 IngressClusterIP 内部通信Ingress 统一入口后端 Service 用 ClusterIP 就够了它只需要给前端 Nginx 和外部流量访问提供内部入口不需要暴露到集群外部。前端 Service 需要暴露到集群外部可以通过 Ingress 实现——在阿里云上最简单的方式是使用负载均衡服务暴露 80 和 443 端口。下面是我项目里的 Service 清单。# 后端 Service集群内部访问 apiVersion: v1 kind: Service metadata: name: backend-service namespace: demo spec: selector: app: backend-app ports: - port: 8080 targetPort: 8080 type: ClusterIP --- # 前端 ServiceNginx 入口 apiVersion: v1 kind: Service metadata: name: frontend-service namespace: demo spec: selector: app: frontend-app ports: - port: 80 targetPort: 80 type: ClusterIP这套配置里集群内部的 Nginx 访问backend-service:8080用的就是 K8s 的 Service DNS 解析而外部流量通过 Ingress 转发到 frontend-service再由 Nginx 代理给后端。如果你不想现在就用 Ingress Controller更直接的办法是把前端 Service 改成 LoadBalancer 类型阿里云会自动创建负载均衡实例给你开公网端口但这样费用会更高也不便于后面统一管理。我这里用了 ClusterIP然后由 Ingress 接入阿里云负载均衡实例。需要注意Ingress 的注解annotations在阿里云上是跟具体 Ingress Controller 绑定在一起的能力我用的阿里云 ACK 自带的 Nginx Ingress Controller它的 annotation 很多比如可以限制公网访问带宽等第一次用的时候你需要先确认自己的集群用的具体是哪个 Ingress Controller再去查对应支持的注解写法。这是一个比较通用的潜在问题。4. Nacos2.0.3 接入与配置中心版本对齐和关键配置项4.1 SpringBoot2.5 与 Nacos2.0.3 的版本对齐spring-cloud-alibaba 依赖坐标SpringBoot2.5 和 Nacos2.0.3 的配合关键在于选对 spring-cloud-alibaba 的版本。很多初次接触这个技术栈的人都会在版本踩坑——比如你在 pom.xml 中引入随便一个 spring-cloud-alibaba 版本它可能会把 Nacos 客户端强制升级到一个兼容的版本最后版本不一致导致各种诡异问题。先说结论我的模拟项目 X 用的是 spring-cloud-alibaba 2021.0.1.0 这个版本它对应 Spring Cloud 2020.0.5专门为 SpringBoot2.5.x 做了适配。如果你用 2021.1 或者更老的版本有可能 Nacos 客户端版本变成 1.4.x在 Nacos 2.0.3 服务端可以正常工作但有些新特性用不上如果版本太新又可能要求 SpringBoot 2.6 及以上。!-- 父工程管理 Spring Boot 版本 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.5.4/version relativePath/ /parent !-- Spring Cloud Alibaba 依赖管理 -- dependencyManagement dependencies dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-alibaba-dependencies/artifactId version2021.0.1.0/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement !-- 应用需要引用的 Nacos 相关组件 -- dependencies dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-config/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency /dependencies这个版本对应关系不是玄学spring-cloud-alibaba 官网给过一张版本说明表核心规律是SpringCloud 版本建立在 SpringBoot 版本之上SpringCloud Alibaba 又建立在 SpringCloud 版本之上。你用 SpringBoot2.5.x那 SpringCloud 必须是 2020.0.x对应的 Alibaba 就是这个 2021.0.1.0。如果升级到 SpringBoot2.6就要换 2021.0.4.0 或 2021.0.5.0这个记牢了能省很多时间。4.2 Nacos 部署方案在 K8s 里部署 Nacos2.0.3 的选型与单机模式配置Nacos2.0.3 的部署有两种思路一是直接用阿里云的 MSE Nacos 托管服务省心二是在自己的 K8s 集群里部署。模拟项目 X 是在自己集群里部署的用的是 Nacos 官方提供的 K8s 部署方式给的是单机模式。因为注册中心在演示环境里只需要一个实例不用高可用。单机模式部署 Nacos 有几个注意事项。首先 Nacos 2.x 需要两个端口主端口 8848 负责 HTTP 和 gRPC另一个偏移量 1000 的端口 9848 负责 gRPC 客户端通信。在 K8s 里你如果只用 Service 暴露 8848服务注册和配置获取可能正常但 Nacos 控制台的一些功能会出问题——比如查看注册的服务列表如果 nodePort 没配置正确可能就看不到。所以我的 Service 会把两个端口都暴露出来。apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: demo spec: clusterIP: None selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848用 headless Service 主要是为了保证 Nacos 2.0.3 的 gRPC 通信能正常工作。Nacos 2.x 的客户端默认会走 gRPC 长连接连接到服务端主端口加 1000 的端口上如果你用普通 ClusterIP ServiceK8s 会对连接做负载均衡但 gRPC 长连接不太适合这样反复重连。headless Service 直接返回后端 Pod 的 IP让客户端直接连到 Pod避免多一层转发。4.3 注册中心配置命名空间、分组、集群 IP 与端口覆盖SpringBoot 应用要注册到 Nacos核心配置在 application.yml 里。我在 ConfigMap 中设置了环境变量然后在 yml 里用环境变量引用它们。这样的配置设计比较灵活同名配置可以在不同环境覆盖。spring: application: name: demo-app cloud: nacos: server-addr: ${NACOS_SERVER_ADDR:nacos-headless:8848} discovery: namespace: ${NACOS_NAMESPACE:public} group: DEFAULT_GROUP # 集群名称可在 K8s 中用于按节点划分 cluster-name: aliyun-hangzhou config: namespace: ${NACOS_NAMESPACE:public} group: DEFAULT_GROUP file-extension: yaml refresh-enabled: truenamespace 的语义要搞清楚Nacos 里的 namespace 是租户级别的隔离不是环境dev/test/prod标签。如果你用 public 这个默认命名空间那所有服务都在同一个命名空间里。想要按环境隔离的话建议在 Nacos 控制台创建 dev、test、prod 三个命名空间然后把命名空间 ID 填进来而不是直接用名字。namespace 参数的取值是命名空间 ID一串很长的字符串不是命名空间名称。还有cluster-name这个配置它是 Nacos 里的集群概念不是 K8s 集群的意思。比如你在阿里云杭州和上海都有 K8s 集群部署了同一个服务的多个实例可以用 cluster-name 区分地域。SpringCloud 的负载均衡组件可以做到优先调用同一个 cluster 内的实例减少跨地域的调用延迟。4.4 Nacos 配置中心的使用把数据库连接等配置从 JAR 中抽离Nacos 2.0.3 的另一个核心功能是配置中心。我会把数据源连接、Redis 地址、日志级别这类容易变动的配置放到 Nacos 而不是打包进 JAR 或写死在 ConfigMap 里这样调整配置时不必重新构建镜像Nacos 会自动推送新配置给应用。配置中心的加载规则是${spring.application.name}.${file-extension}也就是 demo-app.yaml。另外还有一个带 profile 的形式demo-app-k8s.yaml优先级比前者高。有了这个机制我可以在 Nacos 里放一套基础的 demo-app.yaml再为不同环境准备带后缀的 profile 文件。# bootstrap.yml或者 spring.config.import取决于版本 spring: cloud: nacos: config: # 对应 Nacos 中的 dataId 格式 name: demo-app file-extension: yaml在 Nacos 控制台创建一个配置Data ID 填 demo-app.yaml配置内容大概是这样server: port: 8080 spring: datasource: url: jdbc:mysql://mysql-service:3306/demo_db?useUnicodetruecharacterEncodingutf8 username: demo_user password: ${DB_PASSWORD:demo_pass} driver-class-name: com.mysql.cj.jdbc.Driver # 自定义业务配置 demo: config: enabled: true retry-count: 3使用Value(${demo.config.enabled})或ConfigurationProperties就能在代码中读到这些配置。当你在 Nacos 上修改配置并发布后应用会通过长轮询感知到变更并自动刷新。这里需要确保在配置类上加上RefreshScope注解尤其对于不想在修改配置后重启应用的情况这个注解是必须的。如果是数据库连接池这类高级组件刷新可能有局限性到时候你可能需要重启应用才能让配置完全生效。5. K8s 部署避坑指南镜像拉取、健康检查、Nacos 异常与端口覆盖5.1 镜像拉取失败的坑ImagePullBackOff、镜像缓存与私有仓库认证现象Pod 启动一直卡在 ImagePullBackOff 或者 ErrImagePullkubectl describe pod 能看到拉取镜像失败的错误信息但排查网络发现节点能正常访问 ACR。原因这个坑的出现一方面可能是imagePullSecrets没有配置节点无法认证私有仓库导致拉取失败另一方面可能是你先部署了一次镜像比如开发环境用了某个 tag 的镜像后面重新构建并上传了完全相同的 tag 到仓库但 K8s 节点上已经有这个 tag 的镜像且标志为最新imagePullPolicy默认的 IfNotPresent 就跳过了拉取导致运行的是旧镜像。解决先创建imagePullSecrets并在 Deployment 中引用其次在测试阶段给每个构建产物用唯一 tag例如1.0.0-${BUILD_ID}便于定位问题。如果一定要复用同一个 tag可以把imagePullPolicy改成 Always强制每次拉取。另外用kubectl get events查看事件的顺序与内容可以高效定位问题。5.2 健康检查导致频繁重启探针配置不合理的翻车现场现象SpringBoot 容器启动起来不到一分钟就被 K8s 杀掉重启状态一直是 CrashLoopBackOff。原因存活探针配置的 initialDelaySeconds 太短应用还没完成 Spring 容器初始化和 Nacos 注册时就已经到了第一次探测时间探针收到非 200 响应后连续几次失败就会把容器杀掉。我最初的配置把 initialDelaySeconds 设成了 10 秒SpringBoot 应用慢启动加上 Nacos 交互随便就超过 30 秒了所以频繁被误杀。解决先把探针路径从/换成/actuator/health再做两件事。第一给存活探针的 initialDelaySeconds 增加到 60 秒给 JVM 启动和 Spring 容器初始化留足时间第二可以先在本地单独启动容器测试健康检查接口的响应时间然后根据实际耗时决定 initialDelaySeconds而不是拍脑袋写个数值。如果应用在极端情况下启动需要 90 秒那 initialDelaySeconds 设成 60 就还是会被误杀你需要结合实际情况来调整。5.3 Nacos 2.0.3 服务在 K8s 中异常gRPC 端口未暴露与地址注册错误现象SpringBoot 应用通过本地 IP 能正常注册到 Nacos但在 K8s 里注册不上或者注册后 Nacos 控制台显示的服务 IP 无法访问。原因Nacos 2.0 的服务端和客户端之间除了 8848 端口的 HTTP 接口还有 9848 端口的 gRPC 长连接。如果你在 K8s 里只暴露了 8848客户端握手可能成功但 gRPC 通道建立失败导致服务上下线状态异常。最常见的是用 LoadBalancer 暴露 Nacos 时负载均衡只配置了 8848 的监听没把 9848 带上客户端连不上。另一个情况是 Pod 注册到 Nacos 时用的是 Pod IP 容器端口如果 register-enabled 开启且service.port没覆盖成 8080那注册的端口就可能是随机端口这个在 K8s 动态 IP 环境下是不稳定的。解决确认 Nacos 的 Service 把 8848 和 9848 两个端口都暴露了。另外在 application.yml 里强制指定注册端口和 IP 优先使用 K8s Pod 信息——这可以通过 spring-cloud-kubernetes 的机制实现或者直接用环境变量覆盖 Nacos 客户端读到的spring.cloud.nacos.discovery.ip参数。我最后是直接用 K8s 的 Downward API 把 Pod IP 注入到环境变量再配置spring.cloud.nacos.discovery.ip: ${POD_IP}确保控制台显示的是正确的 Pod IP。5.4 本地正常部署到 K8s 后接口 404/502Service 端口和 Nginx 代理配置不一致现象本地 Docker Compose 跑起来一切正常打成镜像部署到 K8s 后前端能访问但所有/api请求全部 502 或 404。原因这是 K8s 里最经典的排查点——前端 Nginx 代理的后端服务地址写错了。我最初的配置里后端 Service 名叫 backend-service但 nginx.conf 里写的还是 localhost:8080那是本地连的或者 Service 端口用了 8080 但 targetPort 指向了 8081健康检查又恰好把 Pod 认为是就绪的外部请求就打到了不存在的端口上。解决先kubectl exec进前端 Pod用 curl 测backend-service:8080/actuator/health是否能通然后再检查 Service 的 selector 是否匹配到后端 Pod。Service 的 endpoint 用kubectl get endpoints看有没有绑定 Pod IP没有的话说明 selector 写错了。修复后把 nginx.conf 的后端地址改成 Service 域名重新构建镜像。我后面一般会在 nginx.conf 里写变量而不是硬编码域名这样调试的时候还能用环境变量临时替换指向。5.5 滚动更新时出现的服务中断忘记配置 PodDisruptionBudget 或 maxUnavailable 不合理现象发版时虽然配置了三个副本但更新过程中总有那么几秒钟接口报错或者直接出现连接拒绝。原因默认滚动更新策略可能把maxUnavailable设为 25%也就是三副本最多允许一个不可用这样连续更新时剩余的可用实例数可能不够应对持续请求。更关键的是没有配置 preStop hook 也没有等待就绪后再结束旧副本导致流量打到正在终止的 Pod 上。解决Deployment 的 rollingUpdate 配置里设maxUnavailable: 0maxSurge: 1确保更新过程中可用副本数不下降。前端 Nginx 的话本来就有多个 Pod但后端 Java 应用启动慢销毁快所以需要 preStop sleep 配合就绪探针切换。再给关键服务配置 PodDisruptionBudget 保证主动运维时最少可用副本数不被突破。这样做已经足够应付大多数场景之后我每次发版都要在正式操作前先检查一遍这些配置。6. 验证部署与排障流程从本地到集群的四个检查脚本部署完成后我有一套自己固定会跑的验证流程能发现 90% 的隐藏问题。分清它们能避免你在不同问题之间互相猜疑。6.1 本地容器预检先跑通镜像再上集群镜像推送到仓库之后我会先在本地跑一次同样的容器验证配置和代码逻辑在容器内没问题免得在集群里反复排查问题。模拟项目 X 发布时我会执行这样几条命令# 本地启动后端容器配置 Nacos 指向本地服务 docker run -d --name test-backend \ -e NACOS_SERVER_ADDR127.0.0.1:8848 \ -p 8080:8080 \ registry.cn-hangzhou.aliyuncs.com/demo/backend-app:1.0.0 # 本地启动前端容器nginx 代理地址用环境变量注入的方式 docker run -d --name test-frontend \ -e BACKEND_SERVICE_URLhttp://127.0.0.1:8080 \ -p 3000:80 \ registry.cn-hangzhou.aliyuncs.com/demo/frontend-app:1.0.0 # 验证接口是否通 curl http://127.0.0.1:3000/api/health注意我这里的 nginx.conf 要支持环境变量覆盖后端地址不然本地测起来很麻烦。前端 Nginx 镜像里可以用 envsubst 模板替换具体做法是在 Dockerfile 的 CMD 里先执行envsubst nginx.conf.template /etc/nginx/conf.d/default.conf再启动 Nginx。这个技巧也是后面我总结出来的不然每次测试都要重新构建一遍镜像很浪费时间。6.2 集群内网络连通性验证exec 进入 Pod 探测 DNS 与端口上了集群之后第一件事是确认内部网络通不通。前端 Pod 和后端 Pod 之间的通信、后端 Pod 和 Nacos 之间的通信都是依赖 K8s DNS 的这部分不通的话其他都是白搭。# 进入前端 Pod 测试后端 Service 连通性 kubectl exec -it frontend-app-xxxxx -n demo -- curl -s http://backend-service:8080/actuator/health # 检查后端 Pod 到 Nacos 的 gRPC 端口是否可达 kubectl exec -it backend-app-xxxxx -n demo -- nc -zv nacos-headless 9848 # 查看 Service 后端挂载了哪些 Pod kubectl get endpoints backend-service -n democurl 通了说明 Service 没问题不通就按刚才的顺序排查Pod 是否存在kubectl get pods、标签是否匹配kubectl get pod --show-labels、Service 端口是否对应kubectl describe svc。这一步能快速定位到问题层不必靠猜。6.3 日志与事件排查只看关键信息的两个高频命令很多初学者一上来就看全部日志几万行日志刷屏后根本找不到真正的问题点。我一般会分两步走先看 K8s 事件再看应用日志的关键部分。# 看 K8s 事件通常能直接给出失败原因 kubectl get events -n demo --sort-by.lastTimestamp # 看后端最近 200 行日志重点关注启动过程中 Nacos 相关异常 kubectl logs -l appbackend-app -n demo --tail200 # 查看上一次崩溃的 Pod 日志 kubectl logs backend-app-old -n demo --previousSpringBoot 启动日志里如果出现NacosException基本可以确定注册或配置中心有问题。比较常见的错误关键字包括Client not connected、Server check fail这些都能直接定位到网络层或版本层。看日志的时候多留个心眼后端启动日志里有没有出现register成功的关键字成功的话说明注册没问题接下来可以集中查网关或路由层。6.4 配置临时修正与主动刷新快速验证 ConfigMap 加更新很多时候排查问题需要临时改配置验证但改配置文件再重启应用的过程很长。针对这种验证场景我用 K8s 的原生能力来提高效率。这里先说 ConfigMap 的挂载更新后文件会自动更新到容器里但 SpringBoot 应用的配置从环境变量读取后修改 ConfigMap 不会让应用马上感知到环境变化要验证环境变量配置项是否正确通常还是会重启 Pod。对于 Nacos 上的配置则不需要改了以后 Nacos 会自动推送。# 修改 ConfigMap 后滚动重启 Deployment 让配置生效 kubectl rollout restart deployment/backend-app -n demo # 打补丁临时修改环境变量用于验证不建议长期使用 kubectl set env deployment/backend-app -n demo NACOS_NAMESPACEdemo-nsrollout restart是一个常用的操作它能通过创建一个新 ReplicaSet 触发 Pod 重建从而让新配置生效。要注意如果 ConfigMap 是作为 volume 挂载的文件而不是环境变量那文件内容更新后容器内的文件也会自动变新有些应用能自动感知有些需要重启这个取决于应用本身行为。我一般会优先把配置放在 Nacos 上而不是 ConfigMap 环境变量因为 Nacos 支持热更新不用重启应用。6.5 回滚与快速恢复image tag 不变时的后悔药方案有一次我在发布时构建了一个带 bug 的镜像并且直接覆盖了原有的 1.0.0 tag——结果 Pod 全部启动失败没法简单退回去。这个过程对我来说是难忘的教训。从那以后每次上线前我会强制走一遍完整流程检查镜像 tag 是否唯一、通过端点确认健康状态、提前演练回滚方案确保已经走通且不依赖任何临时手工步骤。发布后如果发现问题就用kubectl rollout undo回到上一个 ReplicaSet这样即便配置错误也能快速恢复。# 回滚到上一个版本 kubectl rollout undo deployment/backend-app -n demo # 查看滚动状态 kubectl rollout status deployment/backend-app -n demo # 指定回滚到某个历史版本 kubectl rollout history deployment/backend-app -n demo这个回滚方法有个前提你的 Deployment 清单里没有反复修改容器镜像否则历史版本可能会和你预期的对不上。所以我现在做镜像时永远用带版本号的 tag构建号 commit short SHA 都拼上比如1.0.0-20240511-abc1234tag 不变这件事真的别做尤其是在多环境共用一个仓库的时候。发布流程从那次以后我都强制走一遍完整检查——镜像唯一 tag、本地预检、集群内连通性测试、健康检查确认、回滚方案确认这套内容才真正落地了我的流程。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑