简介:这份资源面向需要在阿里云Kubernetes集群上落地前后端分离项目的后端与运维开发者,提供一套可直接参考的部署方案,解决Vue2前端、SpringBoot2.5后端与Nacos2.0.3注册配置中心在K8s中协同编排的问题。包内共16个文件,以8个yaml编排清单为主,覆盖Nacos的StatefulSet、Service、Headless、Ingress及MySQL配置,另含2个sh部署脚本、2个Dockerfile、1个nginx.conf、1个SQL初始化脚本、1个jar与1个gz前端产物,压缩包约34.69MB。目前已有1341人学习下载。读者可据此了解Nacos集群在K8s中的持久化与数据库初始化方式,掌握Vue静态资源经Nginx容器化后与SpringBoot服务的镜像构建、脚本一键部署及Ingress暴露路径,并参考目录中前后端两套模块的划分思路,快速搭建可运行的云原生部署环境。
1. 从零把 Vue2 + Nginx + SpringBoot2.5 + Nacos2.0.3 搬上 K8s:这套组合到底值不值得跟
如果你手上有一套前后端分离的老项目——Vue2 打包出来的静态资源、Nginx 做网关和静态托管、SpringBoot2.5 提供接口、Nacos2.0.3 当注册中心和配置中心——现在要把它们整体搬进 K8s,那这套部署方案就是为你准备的。它不是那种"跑个 Demo 就完事"的玩具,而是一套能直接对着改、对着用的生产级编排思路。我见过太多团队卡在"本地跑得好好的,一上 K8s 就各种连不上、注册不上、配置拉不到"的循环里,核心问题往往不是 K8s 本身难,而是没搞清楚每个组件在集群里到底该怎么暴露、怎么发现、怎么配。这套方案把 Vue2 的静态资源用 Nginx 镜像承载,SpringBoot2.5 打成独立 Deployment,Nacos2.0.3 用 StatefulSet 保证有状态服务的稳定性,再通过 Service 和 Ingress 把流量串起来。适合谁?适合已经会写 Dockerfile、但对 K8s 编排还停留在"能跑 Pod"阶段的后端或运维同学,也适合想把单体拆成微服务但不想动业务代码的团队。下面我按实际落地顺序,把每一步拆开讲。
2. 镜像构建与 Nacos2.0.3 有状态服务部署:先把地基打对
2.1 Vue2 静态资源怎么打进 Nginx 镜像
Vue2 项目打包后就是一堆dist目录下的静态文件,没必要在 K8s 里跑 Node 环境。常见做法是用多阶段构建,第一阶段用 Node 镜像编译,第二阶段把产物塞进 Nginx 镜像。这样最终镜像只有几十 MB,启动快,攻击面也小。
# 第一阶段:编译 Vue2 项目 FROM node:14-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install --registry=https://registry.npmmirror.com COPY . . RUN npm run build # 第二阶段:用 Nginx 托管静态资源 FROM nginx:1.21-alpine # 删掉默认配置,换成我们自己的 RUN rm -rf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf COPY --from=builder /app/dist /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]逻辑说明:第一阶段用node:14-alpine是因为 Vue2 对 Node 版本比较敏感,14 或 16 都行,但别用 18 以上,容易遇到 OpenSSL 报错。npm install指定了国内镜像源,构建速度会快很多。第二阶段把编译产物复制到 Nginx 默认的静态目录,同时替换掉默认配置。参数方面,nginx.conf里要配好try_files $uri $uri/ /index.html;,否则 Vue 的 history 路由刷新会 404。另外daemon off;是必须的,让 Nginx 在前台运行,否则容器启动后立刻退出。
2.2 Nacos2.0.3 为什么用 StatefulSet 而不是 Deployment
Nacos2.0.3 默认用内嵌 Derby 数据库,单机模式跑没问题,但一旦你开了集群模式,每个节点都需要稳定的网络标识和持久化存储。Deployment 的 Pod 名字是随机的,重启后 IP 和名字都变,Nacos 节点之间互相发现就会出问题。StatefulSet 给每个 Pod 固定的名字(比如nacos-0、nacos-1、nacos-2)和稳定的 DNS 记录,配合 Headless Service,节点之间就能通过nacos-0.nacos-headless这种域名互相通信。
apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: default spec: serviceName: nacos-headless replicas: 1 # 单机模式先跑1个,集群模式改3 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: nacos/nacos-server:2.0.3 ports: - containerPort: 8848 name: client - containerPort: 9848 name: grpc env: - name: MODE value: "standalone" # 集群模式改成 cluster - name: SPRING_DATASOURCE_PLATFORM value: "mysql" # 如果用外置MySQL - name: MYSQL_SERVICE_HOST value: "mysql-service" - name: MYSQL_SERVICE_DB_NAME value: "nacos_config" - name: MYSQL_SERVICE_USER value: "root" - name: MYSQL_SERVICE_PASSWORD value: "yourpassword" - name: JVM_XMS value: "512m" - name: JVM_XMX value: "512m" volumeMounts: - name: nacos-data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 5Gi逻辑说明:serviceName: nacos-headless指向一个 Headless Service,这是 StatefulSet 的标准搭配。端口 8848 是 Nacos 的 HTTP 端口,9848 是 2.0 版本新增的 gRPC 端口,客户端和服务端通信靠它,很多人在 Ingress 里只暴露 8848 导致注册失败,就是漏了 9848。环境变量MODE控制单机还是集群,SPRING_DATASOURCE_PLATFORM设为 mysql 表示用外置数据库,生产环境强烈建议这么干,否则数据丢了就真丢了。JVM_XMS和JVM_XMX根据节点内存调整,512m 是起步值,太小会频繁 GC。volumeClaimTemplates给每个 Pod 自动创建 PVC,保证数据持久化。
2.3 SpringBoot2.5 镜像构建的注意点
SpringBoot2.5 打镜像最省事的方式是用spring-boot-maven-plugin的build-image目标,但那个默认拉取的基础镜像比较大。我一般还是写 Dockerfile,用分层构建把依赖和业务代码分开,这样改代码后重新构建只传最上面一层,速度快很多。
FROM openjdk:8-jre-alpine WORKDIR /app # 先复制依赖文件,利用Docker缓存 COPY target/lib/ ./lib/ COPY target/springboot-app.jar ./app.jar # 时区设置,避免日志时间差8小时 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime EXPOSE 8080 ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=k8s", "app.jar"]逻辑说明:openjdk:8-jre-alpine体积小,但注意 Alpine 用的是 musl libc,某些 native 库可能不兼容,如果项目里有 JNI 调用,换成openjdk:8-jre-slim更稳。target/lib/目录需要在 Maven 里配maven-dependency-plugin把依赖复制出来,这样依赖不变时这一层缓存一直有效。-Dspring.profiles.active=k8s指定了 K8s 环境的配置文件,里面要写 Nacos 的地址和命名空间。时区设置别漏,否则查日志时时间对不上,排查问题会多绕很多弯路。
3. K8s 编排文件怎么写:Service、Ingress 与 Nacos 注册的联动
3.1 SpringBoot 的 Deployment 与 Service
SpringBoot 应用在 K8s 里跑,核心是要让 Nacos 能注册上,同时让 Ingress 能把外部流量导进来。Deployment 里要配好健康检查,否则 Pod 还没启动完就被杀,或者挂了不重启。
apiVersion: apps/v1 kind: Deployment metadata: name: springboot-app namespace: default spec: replicas: 2 selector: matchLabels: app: springboot-app template: metadata: labels: app: springboot-app spec: containers: - name: app image: your-registry/springboot-app:v1.0.0 ports: - containerPort: 8080 env: - name: NACOS_SERVER_ADDR value: "nacos-headless.default.svc.cluster.local:8848" - name: NACOS_NAMESPACE value: "your-namespace-id" - name: SPRING_PROFILES_ACTIVE value: "k8s" readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 20 resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" --- apiVersion: v1 kind: Service metadata: name: springboot-service namespace: default spec: selector: app: springboot-app ports: - port: 8080 targetPort: 8080 type: ClusterIP逻辑说明:NACOS_SERVER_ADDR用的是 Headless Service 的完整域名,格式是<service-name>.<namespace>.svc.cluster.local:<port>。如果 Nacos 和 SpringBoot 在同一个 namespace,可以简写成nacos-headless:8848,但写全了更保险。readinessProbe的initialDelaySeconds设 30 秒是因为 SpringBoot 启动加上 Nacos 注册需要时间,设太短 Pod 会一直重启。livenessProbe的延迟设更长,60 秒,避免启动过程中被误杀。资源限制别不写,否则一个 Pod 可能把节点内存吃光,影响其他服务。Service 用 ClusterIP 就够了,外部流量走 Ingress。
3.2 Ingress 怎么同时暴露前端和 Nacos 控制台
Ingress 是流量的入口,前端 Vue 页面和 Nacos 控制台都需要从外部访问。常见做法是用两个 Ingress 规则,或者一个 Ingress 里配多个 path。Nacos 2.0.3 的控制台默认路径是/nacos,但它的 API 也在同一个端口,所以转发时要小心。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: main-ingress namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/proxy-body-size: "50m" spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx-frontend port: number: 80 - path: /api pathType: Prefix backend: service: name: springboot-service port: number: 8080 - host: nacos.example.com http: paths: - path: / pathType: Prefix backend: service: name: nacos-headless port: number: 8848逻辑说明:rewrite-target: /这个注解要慎用,它会把所有路径重写成/,导致/api前缀丢失。如果 SpringBoot 的接口路径本身不带/api,那前端请求/api/user转发到后端就变成/user,需要后端配合。更稳妥的做法是不加 rewrite,让前端直接请求后端真实路径,或者用nginx.ingress.kubernetes.io/configuration-snippet做更精细的路径处理。proxy-body-size设 50m 是为了文件上传,默认 1m 会直接返回 413。Nacos 的 Ingress 单独用一个域名,避免和业务流量混在一起,也方便加白名单限制。
3.3 Nacos 注册 IP 问题的排查与解决
这是最容易翻车的地方。SpringBoot 注册到 Nacos 时,默认拿的是 Pod 的 IP,但 Nacos 控制台看到的也是 Pod IP。如果 Nacos 和 SpringBoot 不在同一个网络平面,或者 Nacos 部署在集群外,就会导致服务发现失败。常见现象是:Nacos 控制台能看到服务列表,但调用方拿到的实例 IP 是 Pod IP,跨节点访问不通。
# 在SpringBoot的Deployment里加环境变量 env: - name: NACOS_DISCOVERY_IP valueFrom: fieldRef: fieldPath: status.podIP - name: NACOS_DISCOVERY_PORT value: "8080"逻辑说明:NACOS_DISCOVERY_IP显式指定注册 IP,用fieldRef拿 Pod IP 是最准的。如果 Pod 网络和外部网络不通,就需要在 Nacos 客户端配置里指定spring.cloud.nacos.discovery.ip为节点的物理 IP,或者用 HostNetwork 模式。但 HostNetwork 会占用节点端口,多个副本会冲突,一般不推荐。更常见的做法是确保 K8s 集群的 Pod 网络和 Nacos 所在网络互通,比如用 Calico 的 BGP 模式或者 Flannel 的 host-gw 模式。如果实在不通,就在 Nacos 前面加一层 Nginx 做 TCP 转发,把 Pod IP 转成节点 IP。
4. 避坑与常见问题排查:那些让你半夜爬起来改配置的坑
4.1 Nacos 9848 端口没暴露导致注册失败
现象:SpringBoot 启动日志里能看到nacos registry相关字样,但 Nacos 控制台的服务列表里就是没有实例,或者过一会儿就掉线。原因:Nacos2.0.3 的客户端和服务端通信除了 8848 还依赖 9848 的 gRPC 端口,如果 Service 或防火墙只开了 8848,gRPC 连不上,注册就会超时失败。解决:在 Nacos 的 Service 里显式暴露 9848 和 9849(集群间通信),Ingress 不需要暴露这两个端口,但集群内部必须通。
apiVersion: v1 kind: Service metadata: name: nacos-headless namespace: default spec: clusterIP: None selector: app: nacos ports: - name: client port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848 - name: grpc-cluster port: 9849 targetPort: 98494.2 Vue 路由刷新 404
现象:首页能打开,点导航也能跳转,但按 F5 刷新或者直接访问/user/list就报 404。原因:Nginx 默认按文件路径找资源,/user/list在磁盘上不存在,自然 404。解决:在nginx.conf里加try_files $uri $uri/ /index.html;,让所有找不到的路径都回退到index.html,由 Vue Router 接管。
server { listen 80; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://springboot-service:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }4.3 SpringBoot 连 Nacos 超时
现象:启动日志报com.alibaba.nacos.api.exception.NacosException: client not connected或者timeout。原因:Nacos 的地址写错了,或者网络不通,或者 Nacos 还没完全启动 SpringBoot 就急着连。解决:先确认NACOS_SERVER_ADDR的域名能解析,在 Pod 里nslookup nacos-headless看看。如果解析没问题,检查 Nacos 的 Pod 是否 Ready,用kubectl logs看 Nacos 启动日志。最后在 SpringBoot 配置里加spring.cloud.nacos.discovery.fail-fast=false,让启动不因为 Nacos 连不上而失败,等 Nacos 起来后自动重连。
4.4 镜像拉取失败 ImagePullBackOff
现象:Pod 状态一直是ImagePullBackOff,kubectl describe pod显示Failed to pull image。原因:镜像仓库需要认证,但没配imagePullSecrets;或者镜像 tag 写错了;或者节点无法访问镜像仓库。解决:先docker pull在节点上手动拉一下,确认镜像存在且能拉。如果仓库是私有的,创建 Secret 并在 Deployment 里引用。
kubectl create secret docker-registry regcred \ --docker-server=your-registry.com \ --docker-username=yourname \ --docker-password=yourpassword \ --docker-email=youremail@example.com然后在 Deployment 的spec.template.spec里加imagePullSecrets: - name: regcred。
4.5 资源限制太紧导致 OOMKilled
现象:Pod 频繁重启,kubectl describe pod显示OOMKilled。原因:resources.limits.memory设得太小,SpringBoot 加上 Nacos 客户端占用的内存超过限制,被内核杀掉。解决:把limits.memory调到 1Gi 以上,同时设置JVM_XMX为限制的 70% 左右,留出堆外内存给 Nacos 客户端和 Netty。
env: - name: JAVA_OPTS value: "-Xms512m -Xmx768m -XX:MaxMetaspaceSize=256m" resources: limits: memory: "1Gi"5. 进阶技巧:用 InitContainer 等 Nacos 就绪再启动业务
5.1 为什么需要 InitContainer
SpringBoot 启动时如果 Nacos 还没准备好,即使配了fail-fast=false,也会在日志里刷一堆连接异常,影响排查。更优雅的做法是用 InitContainer 先探测 Nacos 的 8848 端口,通了再启动主容器。这样业务容器启动时 Nacos 一定可用,日志干净,启动成功率也高。
initContainers: - name: wait-for-nacos image: busybox:1.28 command: ['sh', '-c', 'until nc -z nacos-headless 8848; do echo waiting for nacos; sleep 2; done;']逻辑说明:busybox镜像小,自带nc命令。until nc -z nacos-headless 8848会一直循环,直到端口通为止,每 2 秒试一次。这个 InitContainer 跑完后主容器才会启动,相当于给 Nacos 加了一个启动屏障。注意nacos-headless要能被解析,如果 Nacos 在别的 namespace,写成nacos-headless.other-ns.svc.cluster.local。
5.2 用 PodDisruptionBudget 保证滚动更新时服务不中断
K8s 滚动更新时默认会先杀一个旧 Pod 再起新 Pod,如果副本数只有 1,那更新期间服务就挂了。用 PodDisruptionBudget 可以限制同时不可用的 Pod 数量,配合多副本,做到零中断。
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: springboot-pdb namespace: default spec: minAvailable: 1 selector: matchLabels: app: springboot-app逻辑说明:minAvailable: 1表示至少要有 1 个 Pod 可用,K8s 在驱逐 Pod 时会遵守这个约束。如果副本数是 2,那更新时会先起一个新 Pod,等它 Ready 后再杀旧 Pod,始终保证有 1 个在跑。注意 PDB 只对自愿驱逐生效,节点宕机这种非自愿中断它管不了,所以副本数至少 2 才是真的高可用。
5.3 验证部署是否成功的几个命令
部署完了别急着收工,按顺序跑一遍这些命令,确认每个环节都正常。
# 1. 看所有Pod状态,Running且READY 1/1才算正常 kubectl get pods -o wide # 2. 看Nacos服务列表,确认SpringBoot实例已注册 curl http://nacos.example.com/nacos/v1/ns/catalog/services?hasIpCount=true&withInstances=false&pageNo=1&pageSize=10 # 3. 看Ingress是否分配了地址 kubectl get ingress # 4. 在Pod里测试Nacos连通性 kubectl exec -it springboot-app-xxx -- nc -zv nacos-headless 8848 # 5. 看Pod事件,排查启动失败原因 kubectl describe pod springboot-app-xxx逻辑说明:第一步看 Pod 是否 Running,如果一直ContainerCreating就看事件。第二步调 Nacos 的 OpenAPI 查服务列表,确认实例数对得上。第三步看 Ingress 的 ADDRESS 有没有值,没有的话检查 Ingress Controller 是否正常运行。第四步在业务 Pod 里直接测 Nacos 端口,排除网络策略干扰。第五步看事件里的Warning信息,大部分启动问题都能从这里找到线索。
从那以后我每次部署完这套组合,都会强制走一遍 InitContainer 探测加 PDB 检查的流程,宁可多等两分钟,也不想到半夜被报警叫起来改配置。希望帮到你。
本文还有配套的精品资源,点击获取