K8s部署前后端分离微服务:Vue2+Nginx+SpringBoot+Nacos全流程实践
2026/9/9 22:16:24 网站建设 项目流程

简介:面向云原生开发者和运维人员的阿里云k8s实战部署资源,通过一套完整解决方案将Vue2+nginx+Spring Boot 2.5+Nacos 2.0.3整合部署到Kubernetes集群,帮助解决多组件网络与依赖配置繁琐的痛点,适合具备一定k8s基础、希望快速上线微服务项目的中高级开发者。目前已有1337人学习。包内共16个文件,压缩后34.69MB,按前端应用、后端服务、Nacos中间件三大部分组织,涵盖8个YAML资源清单(StatefulSet、Deployment、Service、Ingress等)、2个Shell一键部署脚本、2个Dockerfile、数据库初始化SQL、nginx配置、应用JAR包及前端静态资源压缩包。从数据库初始化、Nacos集群创建到前端发布、后端服务接入,全流程配置清晰,脚本化一键执行,大幅降低手动拼接不同组件的学习成本,读者可在真实项目中直接复用这套生产级k8s部署模板。

1. 技术选型与整体架构设计

1.1 服务拆分与握手逻辑

拿到“阿里云K8s部署Vue2+Nginx+SpringBoot2.5+Nacos2.0.3”这个标题,其实要做的本质是:把一套前后端分离的微服务应用,从单机部署搬到Kubernetes集群里跑起来。前端用Vue2打包成静态资源交给Nginx托管,后端用SpringBoot2.5提供API接口,服务注册和配置统一交给Nacos2.0.3管理,最后全部跑在阿里云的容器服务ACK上。

这套架构里,最关键的不是某个单独组件的用法,而是组件之间的通信链路。我落地的时候习惯先画清两条链路:

  1. 外部请求链路:浏览器 → SLB(阿里云负载均衡)→ Ingress → Service → Nginx Pod → 静态资源 / 反向代理到后端Service
  2. 服务注册链路:SpringBoot Pod启动时向Nacos注册自身IP和端口 → Nacos维护服务列表 → 服务间通过Nacos发现彼此

这里有个新手特别容易搞混的地方:Nginx在这一套里到底承担什么角色?它不是代替Ingress,也不是代替网关,而是两个层面的东西。Ingress负责把集群外部的流量接进来,Nginx在Pod内部负责托管前端静态资源、把 /api 开头的请求反代到后端的Service地址。两者职责不同,但可以配合使用,也能用Ingress直接把前端流量和后端流量分流到不同Service,跳过Pod内的Nginx反代。

1.2 为什么这套组合是稳妥的

选Vue2而不是Vue3,选SpringBoot2.5而不是3.x,选Nacos2.0.3而不是最新版,很多人以为是保守,实际上是企业项目迭代到一定阶段最现实的选择。Vue2生态成熟,Element UI之类的组件库配合度高;SpringBoot2.5在Java8环境下运行稳定,部署成本低;Nacos2.0.3在K8s里有专门的部署方案文档,踩坑成本可控。

从K8s的视角看,这套组合还有几个实际好处。静态资源打包到Nginx镜像里,天然符合不可变基础设施的理念——每次发版就是打一个新镜像,而不是到服务器上去覆盖文件。SpringBoot2.5自带Actuator,可以很方便地对接K8s的存活探针和就绪探针。Nacos作为注册中心和配置中心,在集群版部署时正好发挥CP模式的价值,多副本之间数据同步,避免单点故障。

提示:如果团队刚开始接触K8s,建议不要同时上服务网格(Istio之类),先把这套经典组合稳定跑起来,再逐步引入更复杂的流量治理能力。

2. 镜像构建与基础环境准备

2.1 前端Vue2多阶段构建

前端镜像构建是这套部署里最容易踩坑的环节。很多人直接拿一个Node镜像把代码放进去启动,那肯定不行——运行时应只有Nginx和静态资源,不要带上Node环境。正确做法是用Docker多阶段构建,构建阶段用Node镜像跑npm install和npm run build,运行阶段用Nginx镜像把dist目录拷贝进去。

我用的前端Dockerfile大致是这样的:

# 构建阶段 FROM node:16-alpine AS build-stage WORKDIR /app COPY package*.json ./ RUN npm install --registry=https://registry.npmmirror.com COPY . . RUN npm run build # 运行阶段 FROM nginx:1.24-alpine COPY --from=build-stage /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

这里有个小细节值得说:npm install放在COPY全部代码之前单独执行,利用了Docker层缓存。只要 package.json 没变,后续构建就会直接复用这一层,发布速度能快一大半。还有,如果你的部署环境在阿里云,Node版本建议固定到16,Vue2项目在Node18以上构建偶尔会有OpenSSL兼容问题,报错是error:0308010C:digital envelope routines::unsupported,遇到这个别慌,要么降Node版本,要么在构建命令里加NODE_OPTIONS=--openssl-legacy-provider

Nginx配置我单独拿出来说,因为这直接决定了前端路由能不能正常工作:

server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend-service:8080/api/; 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; } }

try_files $uri $uri/ /index.html;这一行是Vue2 History路由模式(即去掉#号的美观URL)的命根子,没有它,刷新非首页路由会出现404。/api/反代指向的是K8s集群内部的Service名backend-service,这里不需要写IP,K8s的DNS解析会自动处理。

2.2 后端SpringBoot2.5镜像实操

后端镜像相对简单,但要注意Java版本。SpringBoot2.5官方要求Java8以上,实际我建议用Java 8或Java 11跑,一是运行时更稳定,二是阿里云的基础镜像体积可控。

FROM maven:3.8-openjdk-8 AS build WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:8-jre-alpine ENV TZ=Asia/Shanghai RUN apk add --no-cache tzdata WORKDIR /app COPY --from=build /app/target/app.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "-Xms512m", "-Xmx512m", "app.jar"]

dependency:go-offline这个命令平时用得不多,但配合Docker缓存很好用。它会把所有依赖先拉一遍,之后只要pom.xml不变,就不会重复联网拉依赖。生产环境里如果对镜像体积有要求,可以用JRE 11或多阶段裁减,这里用 alpine 版本已经能控制得很小了。

健康检查探针建议也在Dockerfile层面留好口子。SpringBoot2.5自带/actuator/health端点,前提是pom里引入spring-boot-starter-actuator。这个端点会用在K8s的livenss和readiness探针上,所以别关掉它。

2.3 Nacos2.0.3镜像处理

Nacos镜像比较特殊,官方提供了nacos/nacos-server:v2.0.3,但直接拿去用在K8s上会有一个坑:默认用的是内嵌Derby数据库,数据存在容器里,Pod一重启配置就丢了。生产环境必须改成MySQL存储。

我的做法是在K8s集群里额外部署一个MySQL实例(或者直接使用云数据库RDS,更省心),然后给Nacos传入环境变量MYSQL_SERVICE_HOSTMYSQL_SERVICE_DB_NAMEMYSQL_SERVICE_USERMYSQL_SERVICE_PASSWORD,同时挂载初始化SQL脚本。Nacos官方的MySQL初始化脚本在conf/nacos-mysql.sql里,可以先手工执行到数据库中。

这里提醒一句:Nacos2.0.x的grpc端口是6848(服务端)和9848(客户端),这个一定要在Service里暴露出来。很多人按1.x的经验只开了8848端口,结果SpringBoot客户端连接时报错找不到服务。9848端口实际上是通过8848端口自动偏移出来的,如果设置了NACOS_SERVER_PORT自定义端口,grpc端口也会跟着偏移。

3. K8s资源配置与部署详解

3.1 命名空间与ConfigMap规划

正式部署之前,先规划好命名空间。这一步看起来很简单,但生产环境里能帮你省掉无数麻烦。我习惯按环境划分:devtestprod,或者按业务线划分。下面以app-namespace为例。

apiVersion: v1 kind: Namespace metadata: name: app-namespace

后端应用的配置建议放到ConfigMap里,而不是写死在镜像中。这样环境变量、数据库连接地址、Nacos地址等都可以在部署时动态注入,镜像本身不用变。

apiVersion: v1 kind: ConfigMap metadata: name: backend-config namespace: app-namespace data: SPRING_PROFILES_ACTIVE: "prod" SPRING_DATASOURCE_URL: "jdbc:mysql://mysql-service:3306/app_db?useUnicode=true&characterEncoding=utf8&useSSL=false" SPRING_DATASOURCE_USERNAME: "app_user" SPRING_DATASOURCE_PASSWORD: "yourpassword" SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR: "nacos-service:8848" SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR: "nacos-service:8848"

注意ConfigMap里的密码是明文,真正生产环境建议用K8s的Secret。这里为了演示方便就不绕弯子了。

3.2 Deployment、Service与探针配置

下面是一个相对完整的后端Deployment配置,重点在于探针和资源限制:

apiVersion: apps/v1 kind: Deployment metadata: name: backend-deployment namespace: app-namespace spec: replicas: 2 selector: matchLabels: app: backend template: metadata: labels: app: backend spec: containers: - name: backend image: registry.cn-hangzhou.aliyuncs.com/yournamespace/backend:1.0.0 ports: - containerPort: 8080 envFrom: - configMapRef: name: backend-config readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 20 timeoutSeconds: 5 failureThreshold: 3 resources: requests: cpu: 250m memory: 512Mi limits: cpu: "1" memory: 1Gi imagePullPolicy: IfNotPresent

readinessProbe和livenessProbe的差别要搞清楚。readiness决定流量要不要打进来——服务还没准备就绪之前提前就绪,会有一段时间50x报错;liveness决定Pod要不要重启——服务挂死时探活失败,K8s会杀掉重启。SpringBoot启动本身比较慢,尤其还要注册到Nacos,所以initialDelaySeconds别设太小,我见过太多人卡在这里。

Service的配置相对简单:

apiVersion: v1 kind: Service metadata: name: backend-service namespace: app-namespace spec: selector: app: backend ports: - port: 8080 targetPort: 8080

前端Nginx的Deployment和Service逻辑完全一样,区别在于镜像不同、端口是80、探针路径改成//index.html。前端Pod一般不需要外部直接访问,通过Ingress暴露即可。

3.3 Nacos2.0.3的K8s部署要点

Nacos在K8s里部署,我推荐用StatefulSet而不是Deployment,因为Nacos节点之间需要稳定的网络标识和存储。下面是精简版配置:

apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: app-namespace spec: serviceName: nacos-service replicas: 2 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: nacos/nacos-server:v2.0.3 ports: - containerPort: 8848 - containerPort: 9848 env: - name: MODE value: cluster - name: NACOS_SERVERS value: "nacos-0.nacos-service.app-namespace.svc.cluster.local:8848,nacos-1.nacos-service.app-namespace.svc.cluster.local:8848" - name: MYSQL_SERVICE_HOST value: "mysql-service" - name: MYSQL_SERVICE_DB_NAME value: "nacos_config" - name: MYSQL_SERVICE_USER value: "nacos" - name: MYSQL_SERVICE_PASSWORD value: "nacos123" volumeMounts: - name: data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi

对应的Headless Service:

apiVersion: v1 kind: Service metadata: name: nacos-service namespace: app-namespace spec: clusterIP: None selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848

StatefulSet配Headless Service是一套固定组合,利用nacos-0.nacos-service这种稳定的DNS名称互相通信,Nacos集群模式下节点之间就是这么发现彼此的。

3.4 Ingress暴露服务

前端和后端都部署好之后,需要一个统一的入口把流量接进来。我用的Ingress配置:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: app-namespace annotations: nginx.ingress.kubernetes.io/proxy-body-size: 50m spec: rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: frontend-service port: number: 80 - path: /api pathType: Prefix backend: service: name: backend-service port: number: 8080

这里把/api单独分流到后端Service,这样前端的Nginx配置里可以不用配置反代,由Ingress层直接完成路由。两种方式都行,看团队习惯。如果后端接口请求还涉及文件上传,务必设置proxy-body-size,默认是1m,传个图片就会报413。

阿里云ACK集群默认集成了Ingress Controller,会自动创建公网SLB。如果你的集群是自己建的,需要先部署Nginx Ingress Controller,否则上面这个Ingress资源创建了也不生效。

4. 常见问题与排查技巧实录

4.1 服务注册不上Nacos

这是SpringBoot服务启动后最常遇到的问题。服务起来半天了,Nacos控制台里看不到服务列表,或者服务列表里有但实例信息为空。先查三个地方:

  • Nacos地址通不通:进Pod里执行curl nacos-service:8848/nacos/v1/console/health/readiness,能看到{"code":200}说明网络通。
  • 9848端口是不是被挡了:Nacos2.0客户端发现服务用的是grpc,走的是9848端口。如果用Service暴露时只映射了8848,客户端连不上grpc,就会报Client not connected, current status:STARTING
  • 命名空间对不对:SpringBoot配置里如果指定了namespace,但Nacos上没有建对应ID的命名空间,服务会注册到一个不存在的命名空间里,控制台自然看不到。建议先不指定namespace跑通最简单的链路,然后再加命名空间隔离。

4.2 前端刷新404

我见过几十次的问题,几乎每次都有人问。Vue2项目在Nginx里部署后,从首页跳转别的页面完全正常,但直接刷新某个子路由就出现404。原因很简单:Nginx尝试去找一个不存在的物理路径,比如/about,但前端项目里根本没有about.html

解决办法就是那行try_files $uri $uri/ /index.html;。如果改完Nginx配置还是不生效,注意浏览器缓存,强制刷新一下再试。另外如果你把前端资源放CDN上,也要保证CDN回源时支持这条规则。

4.3 Nacos重启后配置丢失

如果没接MySQL,Nacos0.2.0.3用的是内嵌Derby,配置数据存在容器里。Pod被重新调度到其他节点,或者副本缩容后再扩容,数据就没了一大半。这个问题在生产环境尤其严重,配置中心里的数据是团队资产,不能用临时存储保存。

接到MySQL后还可能出现连不上数据库的情况。我用云数据库RDS时遇到过白名单问题——RDS默认只允许指定的IP访问,K8s集群的出口IP需要提前加到白名单里。自己搭MySQL的话,注意建好数据库和账号,权限要授权到位:

CREATE DATABASE nacos_config DEFAULT CHARACTER SET utf8mb4; CREATE USER 'nacos'@'%' IDENTIFIED BY 'nacos123'; GRANT ALL PRIVILEGES ON nacos_config.* TO 'nacos'@'%'; FLUSH PRIVILEGES;

4.4 镜像拉不下来

阿里云ACK集群的节点在拉取个人或第三方镜像仓库时,有时候会因为网络限制拉不下来。碰到ErrImagePullImagePullBackOff,先kubectl describe pod看具体报错。如果是manifest unknown,多半是镜像tag写错了,到镜像仓库确认一下。如果是connection refusedtimeout,就得检查是否需要配置私有仓库的Secret。

阿里云ACR(容器镜像服务)有公网地址和VPC地址之分。建议ACK集群内拉镜像用VPC地址,速度快还稳定。跨账号拉取需要配置imagePullSecrets,可以预先创建一个docker-registry类型的Secret关联到ServiceAccount。

4.5 资源限制导致OOMKilled

SpringBoot应用如果设置了limits内存太小,会频繁触发OOMKilled,Pod反复重启,看起来就像死循环。JVM默认会根据宿主机可用内存自动调整堆大小,但如果容器限制为512M,JVM可能把它认成宿主机的内存,堆扩容直接把容器冲爆。

我一般在Dockerfile或启动命令里显式设置-Xms512m -Xmx512m,同时给K8s的limits留出堆外内存的余量,比如堆512M就用1Gi。这样JVM只会在512M的堆范围内运行,不会越界去抢K8s给它限定的内存。

4.6 常用排查命令速查

部署过程中会遇到各种奇怪问题,这套排查命令我用了很多年,基本够用:

场景命令
查看Pod状态kubectl get pods -n app-namespace
查看Pod日志kubectl logs -f <pod-name> -n app-namespace
查看Pod详细事件kubectl describe pod <pod-name> -n app-namespace
进入Pod调试kubectl exec -it <pod-name> -n app-namespace -- /bin/sh
测试集群内DNS解析kubectl run curl-test --image=curlimages/curl -it --rm -- sh
查看Service端点kubectl get endpoints -n app-namespace
修改Deployment镜像触发滚动更新kubectl set image deployment/backend-deployment backend=xxx:v2 -n app-namespace
查看滚动更新状态kubectl rollout status deployment/backend-deployment -n app-namespace

注意:kubectl exec进Pod后,基础镜像里不一定有curl命令。建议在调试用的临时Pod里装一个完整工具集,或者直接跑一个busybox镜像来测试网络连通性。

5. 结合实际部署过程的一些补充

5.1 阿里云ACK集群的创建与镜像仓库

在阿里云上实操这套方案时,集群创建本身没什么难度。在容器服务控制台里创建ACK集群,选择专有版还是托管版,我建议一般业务选托管版,Master节点不用自己维护。创建时选好VPC、交换机,节点规格按业务量规划,至少2个节点起步。

镜像仓库选择上,建议开通阿里云容器镜像服务ACR个人版(免费额度基本够用),把前端、后端、Nacos镜像都推到这里。推镜像之前,先创建命名空间和镜像仓库,然后用控制台给的docker命令操作就行。需要注意的是,ACK集群在工作节点上拉取ACR镜像时,如果仓库是私有的,需要配置Secret。不过一个技巧是:把ACR的仓库属性设为私有后,可以用RAM用户授权的方式让ACK自动获取拉取权限,流程上会简单不少。

5.2 持续集成与更新的思路

部署这套组合,如果每次发版都手动打镜像、手动更新Deployment,效率太低,也容易出错。我建议至少做到“流水线化”:代码推到Git仓库触发构建,构建产物打到Docker镜像并推送到仓库,然后自动更新K8s的Deployment镜像版本。

在K8s层面,更新镜像的方式有很多种,最简单的是文末命令里的kubectl set image,但更推荐把镜像版本记录在Deployment的label或annotation中,用kubectl apply整体更新,同时配合rollout status持续查看滚动节奏。这个过程中,因为后端是双副本Deployment,滚动更新时天然能做到“先起一个新的、停一个旧的”,用户无感。

5.3 备份、监控与弹性补强

K8s部署这套组合后,最让人放心的是有一个无形的“运维底线”:探针保证进程不会死,Service保证流量不中断,滚动更新保证发版不停机。但监控还是要主动做起来,至少要有三样东西:集群监控(云监控或Prometheus+Grafana)、日志采集(日志服务或ELK)、JVM监控(对SpringBoot来说很有价值)。

我自己实际部署过这套组合后,最大的体会是:不要把K8s当成一个好玩的玩具,它是一个要深度适配到团队工作流里的基础设施。从本地能跑,到K8s里能跑,这中间隔着很多细致的检查,镜像、网络、存储、内存限制,每一项都要亲自踩一遍才知道问题在哪。

如果之前只做过单机Docker部署,建议在K8s上跑这套组合时,先把Deployment、Service、ConfigMap、StatefulSet这几个资源吃透,尤其是StatefulSet与Deployment的差异,这在Nacos这类有状态组件上至关重要。Nacos2.0.3配合MySQL落库,配合Headless Service的稳定网络标识,配合滚动更新策略,一套有状态的基础服务就能跑得很稳。前端Nginx镜像做好多阶段构建,后端JVM内存边界控制好,再配合Ingress把两条流量路由分开,整条链路在生产环境跑上几个月都不会有太多动静。

真到有问题的时候,按照前面的排查清单一步步来,大概率能很快找到症结所在。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询