简介:这是一份面向阿里云Kubernetes环境的微服务部署资料,整合了Vue2前端、Nginx反向代理、Spring Boot 2.5后端以及Nacos 2.0.3注册中心的完整部署方案。资源针对前后端分离项目在云端集群中的落地场景,涵盖容器镜像构建、Deployment/Service编排、Ingress接入、Nacos有状态服务及MySQL初始化等关键环节,适合已有一定K8s基础、希望直接复用或深入学习部署流程的运维与开发人员。资源包共16个文件,整体约34.69MB,其中8个YAML编排文件用于K8s对象定义,2个Shell脚本实现一键部署,2个Dockerfile负责镜像构建,另有SQL初始化脚本、Nginx配置、后端JAR包和前端静态包,各文件类型用途清晰,便于按需替换和二次修改。目前已有1341人学习下载。借助该方案,读者可快速获得一条可运行的部署链路,理解Nacos集群在K8s中的搭建方式,并可将示例中的配置与脚本迁移到自有项目中,节省环境搭建与排错时间。1. 装这套栈之前:先把四个组件的版本坑位摆清楚“阿里云K8s + vue2 + nginx + springboot2.5 + nacos2.0.3”这套组合,在过去一年里我先后在两个项目上搭过。第一次我信心满满,以为按着老教程一步步来,半小时就能跑通。结果从上午耗到晚上,直到把内网镜像地址、gRPC 端口和 health 探针这三层坑踩完才算消停。这类环境的坑不在于单个组件有多难,而是四件套凑到一起后会形成“版本叠加坑”:前端 vue2 构建对 node 版本挑食,换到 node 18 可能压出诡异产物;后端 springboot2.5 对接 nacos2.0.3,服务发现不再只靠 8848,还多了 9848 这个 gRPC 长连接端口;中间再加一套 MySQL8 认证插件兼容问题,几乎把“本地能跑、K8s 上挂”的剧本演全了。适合谁?手里有 K8s 集群,想把 vue2 和 springboot 微服务从 docker-compose 迁到 K8s 的开发者。这篇笔记会按“镜像准备 → Nacos 上集群 → 业务编排 → 排错 → 验证”往下走,照着能复现,踩过的坑也一并写出来。2. 镜像准备:vue2+nginx 前端和 springboot2.5 后端镜像一次打好所有 Pod 在集群里跑的都是同一份镜像。镜像层面的问题会传染到后面每一个环节,所以第一步先把前端和后端的 Dockerfile 做扎实。2.1 先做前端镜像:node16 构建、nginx1.24 收尾vue2+nginx 的镜像,我的 Dockerfile 固定长这样:# Dockerfile.vue FROM node:16-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:1.24-alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80node:16-alpine 这个构建基础镜像我已经固定了很久。vue2 项目里只要带着 sass-loader、node-sass 这类依赖,在 node18 上很容易报 module build failed,同样的源码切回 node16 就一次通过。npm ci 依赖 package-lock.json,做的是全量安装,比 npm install 更可控;如果项目没提交 lock 文件,就退回 npm install。后面 nginx 层只做两件事:把 build 产物拷进镜像,再把自定义的 nginx.conf 覆盖到 conf.d 目录。这里有个细节,nginx 默认镜像的 /etc/nginx/nginx.conf 会 include /etc/nginx/conf.d/*.conf,所以写进 default.conf 就能被加载,不用去改主配置文件。构建命令:docker build -f Dockerfile.vue -t app-web:1.0.0 .2.2 后端镜像:maven 多阶段把体积降下来后端 springboot2.5 的镜像,我一般拆两段:# Dockerfile.api FROM maven:3.6.3-jdk-8 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B FROM openjdk:8-jre-alpine WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-XX:+UseContainerSupport","-Duser.timezone=GMT+08","-jar","/app/app.jar"]有人会问,AC K8s 里跑着 2025 年的基础组件,为什么后端还要用 JDK8?因为 springboot2.5 官方支持 JDK8,nacos2.0.3 服务端也需要 JDK8+,三个组件用同一套 Java 运行时,底座最不容易出偏差。maven 构建环境和 JRE 运行环境拆开,最终镜像里只留 JAR 包和 JRE,体积能控制在 150MB 上下。-XX:+UseContainerSupport这个参数是必加的。没有它,JVM 会把宿主机内存当成可用内存,在 K8s 设置了 resources.limits 时容易触发 OOMKilled。-Duser.timezone顺手解决日志时间差 8 小时的问题。2.3 推送到镜像仓库:ACK 节点走内网地址拉取镜像打完后要推送到仓库,ACK 集群里推荐用容器镜像服务 ACR:docker tag app-web:1.0.0 registry.cn-xxxx.aliyuncs.com/yourns/app-web:1.0.0 docker push registry.cn-xxxx.aliyuncs.com/yourns/app-web:1.0.0tag 的地址前缀要以 ACR 控制台里显示为准。ACK 节点拉镜像时,尽量选 ACR 提供的内网地址,也就是控制台标注为“内网”的那一个,下载速度快也更稳。镜像地址要和集群在同一地域,跨地域拉镜像在流量高峰期往往慢到怀疑人生。imagePullPolicy 我一般设为 Always,避免节点缓存了旧镜像导致发布后还在跑老版本。这个坑在频繁发版时最容易碰上,镜像 tag 不变只改代码,节点默认 IfNotPresent 就不重新拉取。3. Nacos 2.0.3 上集群:数据库初始化、ConfigMap 和 gRPC 端口一个都不能少Nacos 2.0.3 要跑成高可用,就不能图省事用 Deployment 单副本跑。这里我直接用 StatefulSet 部署三节点,顺带把持久化和端口一次配齐。3.1 先准备 MySQL:建库建用户,导入官方 SQLNacos 服务端把配置和服务元数据都放 MySQL。先手工建库建用户:CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'nacos'@'%' IDENTIFIED BY 'Nacos#2025'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES; USE nacos_config; SOURCE /path/to/mysql-schema.sql;mysql-schema.sql 从 nacos2.0.3 官方安装包里 conf 目录拿,必须用 2.0.3 同版本的 SQL 初始化。我见过有人拿 1.x 的 schema 去初始化 2.0 服务端,控制台能开,但服务列表和配置记录查着查着就缺字段,排查起来非常费劲。MySQL 实例我建议跑在单独的命名空间甚至单独的 StatefulSet 里,别和 Nacos 挤在同一份资源里。两者生命周期完全独立,能避免一次故障连带两个组件一起挂。3.2 用 StatefulSet 跑三节点:稳定网络标识和持久化Nacos 集群节点之间要互相感知,依赖的是固定主机名。StatefulSet 里 Pod 名是 nacos-0、nacos-1、nacos-2 这种稳定序号,配合 Headless Service 就能得到固定域名。用 Deployment 的话,Pod 名每次重建都换,Nacos 节点列表会对不上。先写 ConfigMap:# nacos-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: nacos-config namespace: infra data: NACOS_SERVERS: "nacos-0.nacos-headless.infra:8848 nacos-1.nacos-headless.infra:8848 nacos-2.nacos-headless.infra:8848" MYSQL_SERVICE_HOST: "mysql-svc.infra" MYSQL_SERVICE_DB_NAME: "nacos_config" MYSQL_SERVICE_USER: "nacos" MYSQL_SERVICE_PASSWORD: "Nacos#2025" MODE: "cluster" JVM_XMS: "1g" JVM_XMX: "1g"NACOS_SERVERS 变量必须用空格分隔三个节点地址,末尾不能带分号。MYSQL_SERVICE_* 是 Nacos 官方镜像约定要读的环境变量,直接通过 envFrom 注入,比改配置文件省事。JVM_XMS 建议给到 1g,Nacos 2.0 相对吃内存,太小的堆容易在启动阶段就异常退出。StatefulSet 完整配置:# nacos-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: infra spec: serviceName: nacos-headless replicas: 3 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: registry.cn-xxxx.aliyuncs.com/yourns/nacos-server:2.0.3 envFrom: - configMapRef: name: nacos-config ports: - containerPort: 8848 - containerPort: 9848 - containerPort: 9849 volumeMounts: - name: data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10GivolumeClaimTemplates 是 StatefulSet 和 Deployment 的关键区别。Nacos 的 data 目录里放着集群节点元数据,如果只是 ephemeral 存储,Pod 重建后节点信息全丢,集群会反复重新选主。阿里云集群里 storageClassName 我一般选云盘 ESSD,性能和数据可靠性都比 emptyDir 稳妥。3.3 gRPC 端口映射:9848 和 9849 别漏Nacos 2.0 的端口模型和 1.x 不一样了。8848 还负责 HTTP 接口和控制台,但客户端连接走的是 gRPC 长连接,端口固定是 8848+1000 也就是 9848;9849 则是服务端之间通信用的偏移端口。规则是固定的:偏移量 1000。对应的 Headless Service 必须把这几个端口都暴露:# nacos-service.yaml apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: infra spec: clusterIP: None selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848 - name: grpc-server port: 9849 targetPort: 9849如果还需要公网访问 Nacos 控制台,除了 Ingress 转发 8848 之外,还要额外转发 9848。我只放通 8848 的时候,客户端注册服务偶尔成功、长连接却一直重建,控制台能看到部分节点,但服务列表总是缺斤短两。这个坑后面第 5 章还会展开。4. K8s 资源编排:SpringBoot 的 Deployment、探针与 Ingress 暴露镜像和 Nacos 都就位后,剩下的就是把业务应用编排进集群。前端 Ingress 接收入口流量,后端 Deployment 对外提供接口。4.1 前端 nginx.conf:try_files 和接口转发一起配vue2 项目的 nginx.conf 是事故高发区。很多人直接把本地 Docker 里的 nginx 配置原封不动拷过来,缺了 try_files,history 模式下刷新 /login 就直接 404。server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; expires 1d; } location /api/ { proxy_pass http://api-svc.default.svc.cluster.local: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; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; add_header Cache-Control "public, no-transform"; } }try_files $uri $uri/ /index.html;解决的是 SPA 路由回退问题:先找真实文件,找不到就统一交给 index.html,让 vue-router 自己接管。第二段location /api/把前端发起的 /api/xxx 请求转发给后端 Service。这里 proxy_pass 我故意不写 URI,请求路径会原样保留,后端 Controller 按 /api/xxx 接收。如果你想去掉 /api 前缀再转给后端,proxy_pass 就要写成http://api-svc...:8080/;,两边路径必须对齐,否则接口 404 的排查会非常绕。expires 1d 只给 index.html,静态资源带 hash 的可以放开 30 天。index.html 如果缓存时间太长,发版后用户很容易停留在旧页面。4.2 后端 Deployment:探针、环境变量一起注入springboot2.5 的 Deployment 我习惯把 Nacos 地址通过环境变量注入,配置文件留在镜像里保持不动:# api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: default spec: replicas: 3 selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: containers: - name: api-server image: registry.cn-xxxx.aliyuncs.com/yourns/api-server:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "k8s" - name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR value: "nacos-headless.infra:8848" - name: SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR value: "nacos-headless.infra:8848" readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 2 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 2readinessProbe 和 livenessProbe 是这套环境里保命的两道闸门。readiness 探针决定流量要不要放进来,liveness 探针决定容器要不要重启。springboot2.5 自带 actuator,前提是 pom 里引入 spring-boot-starter-actuator,并且在 application.yml 里放开management.endpoints.web.exposure.include=health,metrics。探针路径直接写 /actuator/health 就行,这个端点返回 JSON 里的 status 字段。注意探针的 path 不要带 spring security 的鉴权。如果项目里加了安全框架,必须把 /actuator/health 加入白名单,否则探针一直拿 401,Pod 永远起不来。4.3 后端 Service:客户端地址用 Service 名,不用 Pod IP后端 Service 用 ClusterIP 类型就够了:# api-service.yaml apiVersion: v1 kind: Service metadata: name: api-svc namespace: default spec: selector: app: api-server ports: - name: http port: 8080 targetPort: 8080这个 Service 同时被 nginx.conf 的 proxy_pass 和 Ingress 引用。springboot 向 Nacos 注册时,上报的地址是 Pod IP,由 K8s 网络自己管理,不需要也不应该在配置里硬编码。客户端配置 Nacos 地址时写的是 nacos-headless.infra:8848,那是 Nacos 服务端地址,和业务自己的注册地址是两回事。4.4 Ingress 暴露前端入口最后把前端路由打出去:# ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: default annotations: nginx.ingress.kubernetes.io/proxy-body-size: "10m" spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-svc port: number: 80这里 Ingress 只负责前端静态页面入口,接口转发已经由前端 nginx.conf 里的 proxy_pass 完成了。业务链路是:Ingress → 前端 nginx → 后端 Service,少一层转发就少一层排查点。proxy-body-size 注解设成 10m,是因为 Nginx 默认 body 大小只有 1m,上传文件稍微大一点就直接 413。5. 避坑:五个最容易翻车的地方这里把我在部署过程中真实遇到过的故障整理出来,每一条都是现场排过的。5.1 接入层问题:前端路由 404 与探针失败坑一:vue2 history 模式刷新任意子路由就 404。现象:首页能开,点进 /login 再刷新,nginx 直接返回 404。原因:nginx 没配 try_files。history 模式下 URL 路径不是真实文件路径,nginx 找不到 /login 这个文件,自然就 404 了。解决:在 location / 里补try_files $uri $uri/ /index.html;。改完 nginx.conf 要重新构建前端镜像,进容器手工改文件在 Pod 重建后会丢。坑二:SpringBoot 的健康探针一直失败,Pod 处于 NotReady。现象:Pod 状态一直是 0/1,readiness 探针日志全是 404 或 401。原因:actuator 依赖没加到 pom;或者 management.endpoints.web.exposure.include 只配了 metrics 没包含 health;另一个隐蔽原因是 spring security 把 /actuator/health 拦截了。解决:先把 actuator 依赖补齐,再确认 exposure 里包含 health,安全配置里放行健康检查路径。进 Pod 手动验证curl http://localhost:8080/actuator/health,看到{"status":"UP"}后再让探针接手。5.2 注册与配置中心问题:认证插件、gRPC 端口、命名空间坑三:Nacos 连 MySQL8 起不来,日志里有 caching_sha2_password 字样。现象:Nacos 容器反复 CrashLoopBackOff,启动日志抛 SQL 认证异常。原因:MySQL8 默认认证插件是 caching_sha2_password,Nacos 2.0.3 自带的 MySQL 驱动对它的支持不完整。解决:给 Nacos 的 MySQL 用户单独指定 mysql_native_password 认证方式:ALTER USER 'nacos'@'%' IDENTIFIED WITH mysql_native_password BY 'Nacos#2025'; FLUSH PRIVILEGES;如果不允许改认证插件,那就升级 Nacos 的 MySQL 驱动,但工程量更大。我偏好前一种方案,环境隔离干净。坑四:服务列表间歇性消失,控制台节点数不对。现象:部署完成后服务注册上了,但过一会儿再看就掉线,或者列表里实例一直处于不健康状态。原因:Nacos 2.0 客户端连接服务端走的是 gRPC 长连接,9848 端口被 Service 或安全组挡住了。客户端实际处于“连接成功又断开、断开再重连”的循环里。解决:检查 nacos Headless Service 是否暴露了 9848 和 9849,如果有 LoadBalancer 或 Ingress,也要确认这两个端口同时转发。只放 8848 是 Nacos2.0 部署最常见的失误。坑五:服务能注册,但配置中心一直拉不到配置。现象:Nacos 控制台里能看到配置,客户端启动日志却提示 config data is empty。原因:命名空间对不上。Nacos 控制台显示的是“命名空间ID”,不是名称。客户端 yml 里如果写了 namespace: public,而控制台默认 public 的 ID 其实是空字符串,两边就对不齐。解决:把客户端 namespace 去掉,或者显式使用控制台里创建的命名空间 ID:spring: cloud: nacos: config: server-addr: nacos-headless.infra:8848 namespace: dev_id_123 group: DEFAULT_GROUP排查时可以直接用 Nacos 的 OpenAPI 手动查一次配置,确认服务端文件存在,再回头看客户端配置,这样能快速定位是命名空间还是 group 的问题。6. 验证跑通:用四段 curl 串起整套系统部署完成后,推荐按顺序做四步验证。最后把它们整理成一个脚本,每次发版强制跑一遍。先确认资源状态:kubectl get pod -n infra -o wide kubectl get svc -n infra kubectl get ingress -n defaultPod 都处于 Running 且 READY 正常,Ingress 的 ADDRESS 不为空,再往下走。第二步验证前端入口和 SPA 路由:curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://app.example.com/ curl -s -o /dev/null -w "%{http_code}\n" http://app.example.com/login第一条返回 200 说明 Ingress 和前端镜像通了;第二条也返回 200,说明 try_files 生效,history 路由没有 404。第三步验证接口转发链路:curl -s http://app.example.com/api/user/1 | head -c 500能返回 JSON 数据,说明 前端nginx → 后端Service → springboot 整个链路是通的。如果这里 404,需要区分是前端 proxy_pass 的问题还是后端 Service 选择器的问题,进 Pod 里直接访问 127.0.0.1 的 health 端点做一个隔离判断。第四步验证 Nacos 注册中心:curl -s "http://nacos-headless.infra:8848/nacos/v1/ns/instance/list?serviceName=api-server&groupName=DEFAULT_GROUP"或者打开控制台看服务列表,确认 api-server 有 3 个健康实例。到这一步,注册中心、配置中心、前端入口、后端接口四个环节全部验证完成。这些小技巧我后来整理成了一个 check.sh,发版后自动执行并把输出追加到日志。从那以后我每次部署完都会强制走一遍这个脚本,不只看页面能不能打开,而是分别确认静态资源、SPA 路由、接口代理、服务注册四个环节都通过,再通知测试进场。这套流程我在模拟项目X和另一个跨平台系统上都验证过,稳住了不少应急场面,希望帮到你。本文还有配套的精品资源,点击获取
简介:这是一份面向阿里云Kubernetes环境的微服务部署资料,整合了Vue2前端、Nginx反向代理、Spring Boot 2.5后端以及Nacos 2.0.3注册中心的完整部署方案。资源针对前后端分离项目在云端集群中的落地场景,涵盖容器镜像构建、Deployment/Service编排、Ingress接入、Nacos有状态服务及MySQL初始化等关键环节,适合已有一定K8s基础、希望直接复用或深入学习部署流程的运维与开发人员。资源包共16个文件,整体约34.69MB,其中8个YAML编排文件用于K8s对象定义,2个Shell脚本实现一键部署,2个Dockerfile负责镜像构建,另有SQL初始化脚本、Nginx配置、后端JAR包和前端静态包,各文件类型用途清晰,便于按需替换和二次修改。目前已有1341人学习下载。借助该方案,读者可快速获得一条可运行的部署链路,理解Nacos集群在K8s中的搭建方式,并可将示例中的配置与脚本迁移到自有项目中,节省环境搭建与排错时间。1. 装这套栈之前:先把四个组件的版本坑位摆清楚“阿里云K8s + vue2 + nginx + springboot2.5 + nacos2.0.3”这套组合,在过去一年里我先后在两个项目上搭过。第一次我信心满满,以为按着老教程一步步来,半小时就能跑通。结果从上午耗到晚上,直到把内网镜像地址、gRPC 端口和 health 探针这三层坑踩完才算消停。这类环境的坑不在于单个组件有多难,而是四件套凑到一起后会形成“版本叠加坑”:前端 vue2 构建对 node 版本挑食,换到 node 18 可能压出诡异产物;后端 springboot2.5 对接 nacos2.0.3,服务发现不再只靠 8848,还多了 9848 这个 gRPC 长连接端口;中间再加一套 MySQL8 认证插件兼容问题,几乎把“本地能跑、K8s 上挂”的剧本演全了。适合谁?手里有 K8s 集群,想把 vue2 和 springboot 微服务从 docker-compose 迁到 K8s 的开发者。这篇笔记会按“镜像准备 → Nacos 上集群 → 业务编排 → 排错 → 验证”往下走,照着能复现,踩过的坑也一并写出来。2. 镜像准备:vue2+nginx 前端和 springboot2.5 后端镜像一次打好所有 Pod 在集群里跑的都是同一份镜像。镜像层面的问题会传染到后面每一个环节,所以第一步先把前端和后端的 Dockerfile 做扎实。2.1 先做前端镜像:node16 构建、nginx1.24 收尾vue2+nginx 的镜像,我的 Dockerfile 固定长这样:# Dockerfile.vue FROM node:16-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:1.24-alpine COPY --from=build /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80node:16-alpine 这个构建基础镜像我已经固定了很久。vue2 项目里只要带着 sass-loader、node-sass 这类依赖,在 node18 上很容易报 module build failed,同样的源码切回 node16 就一次通过。npm ci 依赖 package-lock.json,做的是全量安装,比 npm install 更可控;如果项目没提交 lock 文件,就退回 npm install。后面 nginx 层只做两件事:把 build 产物拷进镜像,再把自定义的 nginx.conf 覆盖到 conf.d 目录。这里有个细节,nginx 默认镜像的 /etc/nginx/nginx.conf 会 include /etc/nginx/conf.d/*.conf,所以写进 default.conf 就能被加载,不用去改主配置文件。构建命令:docker build -f Dockerfile.vue -t app-web:1.0.0 .2.2 后端镜像:maven 多阶段把体积降下来后端 springboot2.5 的镜像,我一般拆两段:# Dockerfile.api FROM maven:3.6.3-jdk-8 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B FROM openjdk:8-jre-alpine WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java","-XX:+UseContainerSupport","-Duser.timezone=GMT+08","-jar","/app/app.jar"]有人会问,AC K8s 里跑着 2025 年的基础组件,为什么后端还要用 JDK8?因为 springboot2.5 官方支持 JDK8,nacos2.0.3 服务端也需要 JDK8+,三个组件用同一套 Java 运行时,底座最不容易出偏差。maven 构建环境和 JRE 运行环境拆开,最终镜像里只留 JAR 包和 JRE,体积能控制在 150MB 上下。-XX:+UseContainerSupport这个参数是必加的。没有它,JVM 会把宿主机内存当成可用内存,在 K8s 设置了 resources.limits 时容易触发 OOMKilled。-Duser.timezone顺手解决日志时间差 8 小时的问题。2.3 推送到镜像仓库:ACK 节点走内网地址拉取镜像打完后要推送到仓库,ACK 集群里推荐用容器镜像服务 ACR:docker tag app-web:1.0.0 registry.cn-xxxx.aliyuncs.com/yourns/app-web:1.0.0 docker push registry.cn-xxxx.aliyuncs.com/yourns/app-web:1.0.0tag 的地址前缀要以 ACR 控制台里显示为准。ACK 节点拉镜像时,尽量选 ACR 提供的内网地址,也就是控制台标注为“内网”的那一个,下载速度快也更稳。镜像地址要和集群在同一地域,跨地域拉镜像在流量高峰期往往慢到怀疑人生。imagePullPolicy 我一般设为 Always,避免节点缓存了旧镜像导致发布后还在跑老版本。这个坑在频繁发版时最容易碰上,镜像 tag 不变只改代码,节点默认 IfNotPresent 就不重新拉取。3. Nacos 2.0.3 上集群:数据库初始化、ConfigMap 和 gRPC 端口一个都不能少Nacos 2.0.3 要跑成高可用,就不能图省事用 Deployment 单副本跑。这里我直接用 StatefulSet 部署三节点,顺带把持久化和端口一次配齐。3.1 先准备 MySQL:建库建用户,导入官方 SQLNacos 服务端把配置和服务元数据都放 MySQL。先手工建库建用户:CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'nacos'@'%' IDENTIFIED BY 'Nacos#2025'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES; USE nacos_config; SOURCE /path/to/mysql-schema.sql;mysql-schema.sql 从 nacos2.0.3 官方安装包里 conf 目录拿,必须用 2.0.3 同版本的 SQL 初始化。我见过有人拿 1.x 的 schema 去初始化 2.0 服务端,控制台能开,但服务列表和配置记录查着查着就缺字段,排查起来非常费劲。MySQL 实例我建议跑在单独的命名空间甚至单独的 StatefulSet 里,别和 Nacos 挤在同一份资源里。两者生命周期完全独立,能避免一次故障连带两个组件一起挂。3.2 用 StatefulSet 跑三节点:稳定网络标识和持久化Nacos 集群节点之间要互相感知,依赖的是固定主机名。StatefulSet 里 Pod 名是 nacos-0、nacos-1、nacos-2 这种稳定序号,配合 Headless Service 就能得到固定域名。用 Deployment 的话,Pod 名每次重建都换,Nacos 节点列表会对不上。先写 ConfigMap:# nacos-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: nacos-config namespace: infra data: NACOS_SERVERS: "nacos-0.nacos-headless.infra:8848 nacos-1.nacos-headless.infra:8848 nacos-2.nacos-headless.infra:8848" MYSQL_SERVICE_HOST: "mysql-svc.infra" MYSQL_SERVICE_DB_NAME: "nacos_config" MYSQL_SERVICE_USER: "nacos" MYSQL_SERVICE_PASSWORD: "Nacos#2025" MODE: "cluster" JVM_XMS: "1g" JVM_XMX: "1g"NACOS_SERVERS 变量必须用空格分隔三个节点地址,末尾不能带分号。MYSQL_SERVICE_* 是 Nacos 官方镜像约定要读的环境变量,直接通过 envFrom 注入,比改配置文件省事。JVM_XMS 建议给到 1g,Nacos 2.0 相对吃内存,太小的堆容易在启动阶段就异常退出。StatefulSet 完整配置:# nacos-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: infra spec: serviceName: nacos-headless replicas: 3 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: registry.cn-xxxx.aliyuncs.com/yourns/nacos-server:2.0.3 envFrom: - configMapRef: name: nacos-config ports: - containerPort: 8848 - containerPort: 9848 - containerPort: 9849 volumeMounts: - name: data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 10GivolumeClaimTemplates 是 StatefulSet 和 Deployment 的关键区别。Nacos 的 data 目录里放着集群节点元数据,如果只是 ephemeral 存储,Pod 重建后节点信息全丢,集群会反复重新选主。阿里云集群里 storageClassName 我一般选云盘 ESSD,性能和数据可靠性都比 emptyDir 稳妥。3.3 gRPC 端口映射:9848 和 9849 别漏Nacos 2.0 的端口模型和 1.x 不一样了。8848 还负责 HTTP 接口和控制台,但客户端连接走的是 gRPC 长连接,端口固定是 8848+1000 也就是 9848;9849 则是服务端之间通信用的偏移端口。规则是固定的:偏移量 1000。对应的 Headless Service 必须把这几个端口都暴露:# nacos-service.yaml apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: infra spec: clusterIP: None selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848 - name: grpc-server port: 9849 targetPort: 9849如果还需要公网访问 Nacos 控制台,除了 Ingress 转发 8848 之外,还要额外转发 9848。我只放通 8848 的时候,客户端注册服务偶尔成功、长连接却一直重建,控制台能看到部分节点,但服务列表总是缺斤短两。这个坑后面第 5 章还会展开。4. K8s 资源编排:SpringBoot 的 Deployment、探针与 Ingress 暴露镜像和 Nacos 都就位后,剩下的就是把业务应用编排进集群。前端 Ingress 接收入口流量,后端 Deployment 对外提供接口。4.1 前端 nginx.conf:try_files 和接口转发一起配vue2 项目的 nginx.conf 是事故高发区。很多人直接把本地 Docker 里的 nginx 配置原封不动拷过来,缺了 try_files,history 模式下刷新 /login 就直接 404。server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; expires 1d; } location /api/ { proxy_pass http://api-svc.default.svc.cluster.local: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; } location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; add_header Cache-Control "public, no-transform"; } }try_files $uri $uri/ /index.html;解决的是 SPA 路由回退问题:先找真实文件,找不到就统一交给 index.html,让 vue-router 自己接管。第二段location /api/把前端发起的 /api/xxx 请求转发给后端 Service。这里 proxy_pass 我故意不写 URI,请求路径会原样保留,后端 Controller 按 /api/xxx 接收。如果你想去掉 /api 前缀再转给后端,proxy_pass 就要写成http://api-svc...:8080/;,两边路径必须对齐,否则接口 404 的排查会非常绕。expires 1d 只给 index.html,静态资源带 hash 的可以放开 30 天。index.html 如果缓存时间太长,发版后用户很容易停留在旧页面。4.2 后端 Deployment:探针、环境变量一起注入springboot2.5 的 Deployment 我习惯把 Nacos 地址通过环境变量注入,配置文件留在镜像里保持不动:# api-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-server namespace: default spec: replicas: 3 selector: matchLabels: app: api-server template: metadata: labels: app: api-server spec: containers: - name: api-server image: registry.cn-xxxx.aliyuncs.com/yourns/api-server:1.0.0 ports: - containerPort: 8080 env: - name: SPRING_PROFILES_ACTIVE value: "k8s" - name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR value: "nacos-headless.infra:8848" - name: SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR value: "nacos-headless.infra:8848" readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 2 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 2readinessProbe 和 livenessProbe 是这套环境里保命的两道闸门。readiness 探针决定流量要不要放进来,liveness 探针决定容器要不要重启。springboot2.5 自带 actuator,前提是 pom 里引入 spring-boot-starter-actuator,并且在 application.yml 里放开management.endpoints.web.exposure.include=health,metrics。探针路径直接写 /actuator/health 就行,这个端点返回 JSON 里的 status 字段。注意探针的 path 不要带 spring security 的鉴权。如果项目里加了安全框架,必须把 /actuator/health 加入白名单,否则探针一直拿 401,Pod 永远起不来。4.3 后端 Service:客户端地址用 Service 名,不用 Pod IP后端 Service 用 ClusterIP 类型就够了:# api-service.yaml apiVersion: v1 kind: Service metadata: name: api-svc namespace: default spec: selector: app: api-server ports: - name: http port: 8080 targetPort: 8080这个 Service 同时被 nginx.conf 的 proxy_pass 和 Ingress 引用。springboot 向 Nacos 注册时,上报的地址是 Pod IP,由 K8s 网络自己管理,不需要也不应该在配置里硬编码。客户端配置 Nacos 地址时写的是 nacos-headless.infra:8848,那是 Nacos 服务端地址,和业务自己的注册地址是两回事。4.4 Ingress 暴露前端入口最后把前端路由打出去:# ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: default annotations: nginx.ingress.kubernetes.io/proxy-body-size: "10m" spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-svc port: number: 80这里 Ingress 只负责前端静态页面入口,接口转发已经由前端 nginx.conf 里的 proxy_pass 完成了。业务链路是:Ingress → 前端 nginx → 后端 Service,少一层转发就少一层排查点。proxy-body-size 注解设成 10m,是因为 Nginx 默认 body 大小只有 1m,上传文件稍微大一点就直接 413。5. 避坑:五个最容易翻车的地方这里把我在部署过程中真实遇到过的故障整理出来,每一条都是现场排过的。5.1 接入层问题:前端路由 404 与探针失败坑一:vue2 history 模式刷新任意子路由就 404。现象:首页能开,点进 /login 再刷新,nginx 直接返回 404。原因:nginx 没配 try_files。history 模式下 URL 路径不是真实文件路径,nginx 找不到 /login 这个文件,自然就 404 了。解决:在 location / 里补try_files $uri $uri/ /index.html;。改完 nginx.conf 要重新构建前端镜像,进容器手工改文件在 Pod 重建后会丢。坑二:SpringBoot 的健康探针一直失败,Pod 处于 NotReady。现象:Pod 状态一直是 0/1,readiness 探针日志全是 404 或 401。原因:actuator 依赖没加到 pom;或者 management.endpoints.web.exposure.include 只配了 metrics 没包含 health;另一个隐蔽原因是 spring security 把 /actuator/health 拦截了。解决:先把 actuator 依赖补齐,再确认 exposure 里包含 health,安全配置里放行健康检查路径。进 Pod 手动验证curl http://localhost:8080/actuator/health,看到{"status":"UP"}后再让探针接手。5.2 注册与配置中心问题:认证插件、gRPC 端口、命名空间坑三:Nacos 连 MySQL8 起不来,日志里有 caching_sha2_password 字样。现象:Nacos 容器反复 CrashLoopBackOff,启动日志抛 SQL 认证异常。原因:MySQL8 默认认证插件是 caching_sha2_password,Nacos 2.0.3 自带的 MySQL 驱动对它的支持不完整。解决:给 Nacos 的 MySQL 用户单独指定 mysql_native_password 认证方式:ALTER USER 'nacos'@'%' IDENTIFIED WITH mysql_native_password BY 'Nacos#2025'; FLUSH PRIVILEGES;如果不允许改认证插件,那就升级 Nacos 的 MySQL 驱动,但工程量更大。我偏好前一种方案,环境隔离干净。坑四:服务列表间歇性消失,控制台节点数不对。现象:部署完成后服务注册上了,但过一会儿再看就掉线,或者列表里实例一直处于不健康状态。原因:Nacos 2.0 客户端连接服务端走的是 gRPC 长连接,9848 端口被 Service 或安全组挡住了。客户端实际处于“连接成功又断开、断开再重连”的循环里。解决:检查 nacos Headless Service 是否暴露了 9848 和 9849,如果有 LoadBalancer 或 Ingress,也要确认这两个端口同时转发。只放 8848 是 Nacos2.0 部署最常见的失误。坑五:服务能注册,但配置中心一直拉不到配置。现象:Nacos 控制台里能看到配置,客户端启动日志却提示 config data is empty。原因:命名空间对不上。Nacos 控制台显示的是“命名空间ID”,不是名称。客户端 yml 里如果写了 namespace: public,而控制台默认 public 的 ID 其实是空字符串,两边就对不齐。解决:把客户端 namespace 去掉,或者显式使用控制台里创建的命名空间 ID:spring: cloud: nacos: config: server-addr: nacos-headless.infra:8848 namespace: dev_id_123 group: DEFAULT_GROUP排查时可以直接用 Nacos 的 OpenAPI 手动查一次配置,确认服务端文件存在,再回头看客户端配置,这样能快速定位是命名空间还是 group 的问题。6. 验证跑通:用四段 curl 串起整套系统部署完成后,推荐按顺序做四步验证。最后把它们整理成一个脚本,每次发版强制跑一遍。先确认资源状态:kubectl get pod -n infra -o wide kubectl get svc -n infra kubectl get ingress -n defaultPod 都处于 Running 且 READY 正常,Ingress 的 ADDRESS 不为空,再往下走。第二步验证前端入口和 SPA 路由:curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://app.example.com/ curl -s -o /dev/null -w "%{http_code}\n" http://app.example.com/login第一条返回 200 说明 Ingress 和前端镜像通了;第二条也返回 200,说明 try_files 生效,history 路由没有 404。第三步验证接口转发链路:curl -s http://app.example.com/api/user/1 | head -c 500能返回 JSON 数据,说明 前端nginx → 后端Service → springboot 整个链路是通的。如果这里 404,需要区分是前端 proxy_pass 的问题还是后端 Service 选择器的问题,进 Pod 里直接访问 127.0.0.1 的 health 端点做一个隔离判断。第四步验证 Nacos 注册中心:curl -s "http://nacos-headless.infra:8848/nacos/v1/ns/instance/list?serviceName=api-server&groupName=DEFAULT_GROUP"或者打开控制台看服务列表,确认 api-server 有 3 个健康实例。到这一步,注册中心、配置中心、前端入口、后端接口四个环节全部验证完成。这些小技巧我后来整理成了一个 check.sh,发版后自动执行并把输出追加到日志。从那以后我每次部署完都会强制走一遍这个脚本,不只看页面能不能打开,而是分别确认静态资源、SPA 路由、接口代理、服务注册四个环节都通过,再通知测试进场。这套流程我在模拟项目X和另一个跨平台系统上都验证过,稳住了不少应急场面,希望帮到你。本文还有配套的精品资源,点击获取