简介面向需要在阿里云Kubernetes环境中部署前后端分离应用的开发者这份资源完整整合了Vue2、Nginx、Spring Boot 2.5与Nacos 2.0.3的部署链路可解决从前端静态资源打包上传、后端镜像构建到注册中心高可用编排的系列问题尤其适合已掌握容器基础、想在真实云环境快速落地的读者。压缩包共16个文件包括8个YAML编排文件、2个Shell一键部署脚本、2个Dockerfile、1个SQL初始化脚本、Nginx配置、后端JAR包及前端压缩包整体大小34.69MB其中YAML覆盖前端应用、后端服务和Nacos集群Shell脚本封装发布入口SQL用于初始化Nacos依赖的数据库目录结构便于按需复用。目前已有1341人学习下载资源提供了带注释可改的YAML、可直接执行的部署脚本、关联Nacos持久化的MySQL建表SQL以及Nginx代理配置覆盖部署中常见的容器编排、服务发现、配置注入和网络暴露等环节能明显缩短环境搭建与排错时间。1. 先别急着写镜像单机 docker-compose 能跑不代表 K8s 能跑本地 docker-compose 里把 Nacos 2.0.3、SpringBoot 2.5 后端、Vue2 Nginx 前端凑到一起几分钟就能起服务但把这套全家桶迁到阿里云 K8s 集群后最常见的翻车现场是后端日志里 Nacos 客户端反复报连接失败前端刷新一下子路由就 404Pod 还被探针打得反复重启。问题基本不在代码而在 Nacos 2.x 多出来的 gRPC 端口、K8s 内部的 Service DNS 与 Pod IP 网络以及探针健康检查这三件事它们跟本机 docker 网络完全不是一回事。这篇按我当时部署某跨平台系统的顺序记录从 ACK 集群和镜像仓库准备到 Nacos 2.0.3、SpringBoot 2.5、Vue2 Nginx 逐个落 Pod最后用一条链路验证到底。适合已经在本地跑通、正准备迁到云上 K8s 的开发者。2. 环境准备ACK 集群、命名空间与镜像仓库先对齐2.1 版本矩阵先把容易起冲突的依赖定死部署这套技术栈最忌讳的是每个组件都用「最新版」。Nacos 2.0.3 是标题里指定的版本客户端 SDK 也必须走 2.x 这条线SpringBoot 2.5 对应 Spring Cloud 和 Spring Cloud Alibaba 的版本是有限定的依赖搭错服务能启动但就是注册不上排查起来非常折磨人。我先把跑通的版本组合列成一张表后面所有 YAML 和镜像都按这个表来。组件版本 / 镜像说明KubernetesACK 托管版 1.24 上下1.22 之后 Ingress API 必须走 networking.k8s.io/v1Nacosnacos/nacos-server:v2.0.3注册中心2.0 起多出 gRPC 端口SpringBoot2.5.x后端基础框架Spring Cloud Alibaba2021.0.1.0 一档需与 SpringBoot 2.5、SpringCloud 2020.0.x 对齐后端运行镜像openjdk:8u345-jre-alpine体积小K8s 场景分发快前端基础镜像nginx:1.24-alpine托管 Vue2 构建产物SpringCloud Alibaba 版本我吃过一次亏一开始图省事选了新版本结果 nacos-discovery starter 的客户端与 2.0.3 服务端的兼容性出现怪问题注册请求发出去没有响应后来锁到 2021.0.1.0 这一档才消停。我的习惯是跑通之后把所有依赖版本写进 pom 的 properties不允许随便升级。2.2 创建 ACK 集群和业务命名空间阿里云 ACK 托管版不用自己运维 Master 节点按节点池收费适合先把业务跑起来。我一般建 2 台 4C8G 的 worker 节点系统盘 40G部署这套前端加后端加 Nacos 足够。集群创建完成后kubectl 连接信息会生成一个 kubeconfig下载后放到本地 ~/.kube/config先确认能连上kubectl get nodes能列出节点后我给这套环境建一个独立命名空间免得和别的应用混在一起后面所有资源都打到这个 namespace 里kubectl create namespace app-demo kubectl label namespace app-demo appapp-demo命名空间的作用不只是隔离它还决定了 Service 的 DNS 名称。后面 Nacos 地址我会写成 nacos-server:8848而不是 127.0.0.1就是依赖同一个 namespace 内 Service DNS 解析。跨 namespace 访问时地址要带上 namespace 后缀形如 nacos-server.app-demo.svc.cluster.local:8848这个在排查网络问题时要能反应过来。2.3 镜像仓库先建好避免部署时临时赶K8s 节点要从仓库拉镜像阿里云容器镜像服务ACR个人版足够用。我习惯在 ACR 里建一个命名空间叫 app-images再建两个镜像仓库分别放后端和前端。本地先登录再打 tag 推送三步固定流程docker login registry.example.com docker tag biz-server:1.0.0 registry.example.com/app-images/biz-server:1.0.0 docker push registry.example.com/app-images/biz-server:1.0.0registry.example.com 换成你实际 ACR 仓库地址docker login 的用户名密码就是开通镜像服务时设置的专用密码不是登录阿里云控制台的密码。前端镜像同理先构建再推送tag 保持一致后面 deployment 里的 image 字段直接复用。镜像推完之后要注意 imagePullPolicy。如果镜像 tag 是 1.0.0 这种固定版本我习惯显式写成 IfNotPresent节点本地有镜像就不重复拉取。如果 tag 写成 latest那 policy 要设成 Always但生产不建议这么写因为无法通过 tag 区分版本。3. 部署注册中心 Nacos 2.0.3 与后端 SpringBoot 2.53.1 Nacos 2.0.3 用 StatefulSet 而不是 DeploymentNacos 是有状态服务即使单机模式也涉及数据文件持久化所以我用 StatefulSet 加 volumeClaimTemplates而不是普通 Deployment。单机 standalone 模式用内嵌 derby 存储适合测试和预发生产环境建议换 MySQL 数据源或做三节点集群那是另一套配置这里先把单机跑通。部署文件里最容易被忽略的是 Service 端口。Nacos 2.0 的服务端不只是监听 8848客户端从 1.4 版本开始使用 gRPC 长连接端口号是主端口加 1000 和 1001也就是 9848 和 9849。9848 供客户端连接9849 供服务端之间通信。我见过太多人只暴露 8848结果后端注册时报连接被拒。下面这份 YAML 把 8848 和 9848 都映射到 NodePort方便本地联调apiVersion: v1 kind: Service metadata: name: nacos-server namespace: app-demo spec: selector: app: nacos-server ports: - name: http-8848 port: 8848 targetPort: 8848 nodePort: 30848 - name: grpc-9848 port: 9848 targetPort: 9848 nodePort: 30849 type: NodePort --- apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos-server namespace: app-demo spec: serviceName: nacos-server replicas: 1 selector: matchLabels: app: nacos-server template: metadata: labels: app: nacos-server spec: containers: - name: nacos-server image: nacos/nacos-server:v2.0.3 ports: - name: http-8848 containerPort: 8848 - name: grpc-9848 containerPort: 9848 - name: grpc-9849 containerPort: 9849 env: - name: MODE value: standalone volumeMounts: - name: nacos-data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: nacos-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 20GiMODE 环境变量设为 standalone 表示单机模式volumeClaimTemplates 会自动给 Pod 分配一块云盘Pod 被删掉重建后数据还在。注意 9849 这里也映射了容器端口但 Service 没暴露它单机模式不需要对外提供这个端口。部署完后用 kubectl apply -f nacos.yaml 创建资源然后等待 Pod 变成 Runningkubectl get pods -n app-demo -o wide | grep nacos看到 READY 为 1/1 后浏览器访问 http://节点公网IP:30848/nacos能打开控制台就说明 8848 通了。控制台默认账号密码是 nacos/nacos生产环境务必改掉默认密码。3.2 后端 bootstrap.yml 别把 Nacos 地址写死SpringBoot 2.5 接 Nacos 依赖 bootstrap 上下文需要引入 spring-cloud-starter-bootstrap 这个依赖否则 bootstrap.yml 不生效容易踩。bootstrap.yml 里我一般这么写spring: application: name: biz-server cloud: nacos: discovery: server-addr: ${NACOS_ADDR:127.0.0.1:8848} ip: ${POD_IP:}server-addr 用环境变量 NACOS_ADDR 注入K8s 部署时指向 nacos-server:8848本地开发时默认 127.0.0.1:8848一套配置两边都能跑。ip 字段是重点在 K8s 里如果不显式指定客户端上报给 Nacos 的 IP 可能是内网 Pod IP但某些网络插件下取到的地址不一定正确导致控制台里能看到服务但实际调用不通。我通过环境变量 POD_IP 把 Pod 的真实 IP 传进去保证服务列表里显示的地址可用。启动类上记得加 EnableDiscoveryClient 注解依赖用 nacos-discovery starter。版本对齐到 Spring Cloud Alibaba 2021.0.1.0客户端内置的 nacos-client 版本就是 2.x能和 Nacos 2.0.3 服务端正常完成 gRPC 握手。3.3 后端 Dockerfile 用多阶段构建后端镜像我习惯用多阶段构建构建阶段用 maven 镜像运行阶段用 openjdk 镜像最终镜像里不残留 maven 和源码。Dockerfile 如下FROM maven:3.8.6-openjdk-8 AS build WORKDIR /build COPY pom.xml . RUN mvn dependency:resolve -B COPY src ./src RUN mvn package -DskipTests -q FROM openjdk:8u345-jre-alpine COPY --frombuild /build/target/biz-server.jar /app/app.jar ENTRYPOINT [java, -Xms512m, -Xmx512m, -jar, /app/app.jar]先单独 COPY pom.xml 并执行 dependency:resolve是为了把依赖拉取结果做成 docker 缓存层改代码后重新构建镜像不用重新下载依赖。如果项目里有私有 maven 仓库需要在 maven 镜像或 settings.xml 里配置好。JVM 参数 512m 对齐节点资源4C8G 节点上跑两个副本不紧张。如果你没有在服务器上构建镜像的习惯也可以在本地直接 mvn package然后用一个更简单的 Dockerfile 只 COPY jar 包。两种方式产物一样看团队习惯。3.4 后端 Deployment探针和启动顺序是隐蔽的坑K8s 不会保证 Nacos 先启动、后端后启动两个 Deployment 同时提交后端可能比 Nacos 先进入 Running。SpringBoot 启动速度快但 Nacos 客户端重试机制能扛住短暂不可用真正的问题是探针。如果 readinessProbe 通过 /actuator/health 检查而项目的 health 聚合了 Nacos 状态注册中心没就绪时探针就失败Pod 被判定未就绪K8s 可能重启容器形成死循环。我的 deployment 写法apiVersion: apps/v1 kind: Deployment metadata: name: biz-server namespace: app-demo spec: replicas: 2 selector: matchLabels: app: biz-server template: metadata: labels: app: biz-server spec: imagePullSecrets: - name: acr-secret containers: - name: biz-server image: registry.example.com/app-images/biz-server:1.0.0 ports: - name: http containerPort: 8080 env: - name: NACOS_ADDR value: nacos-server:8848 - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 3 --- apiVersion: v1 kind: Service metadata: name: biz-server namespace: app-demo spec: selector: app: biz-server ports: - name: http port: 8080 targetPort: 8080 type: ClusterIPNACOS_ADDR 指向 nacos-server:8848利用 K8s 内部 DNS 解析到 Nacos 的 ClusterIP比写死某个节点 IP 可靠。POD_IP 通过 fieldRef 从 Pod 元数据获取对应 bootstrap.yml 里的 ${POD_IP}。readinessProbe 的 initialDelaySeconds 我给了 30 秒SpringBoot 应用启动加 Nacos 注册时间紧张的话也可以改成 startupProbe 给足启动窗口。后端 Service 类型是 ClusterIP集群内通过 biz-server:8080 访问Ingress 层也会转发到这个 Service。两个后端副本都注册到 Nacos服务间调用走负载均衡这是部署后端的基本形态。4. 部署前端 Vue2 Nginx 与 Ingress 路由4.1 Vue2 构建产物publicPath 和接口前缀先对齐前端项目是 Vue2构建方式通常是 npm install 加 npm run build。我们项目里用的构建脚本是 build:prod产物输出到 dist 目录。构建命令我习惯固定成一套npm ci --registryhttps://registry.npmmirror.com npm run build:prodnpm ci 比 npm install 更适合在部署环境使用它严格按 package-lock.json 安装不会因为依赖版本漂移出现构建结果不一致。npm 源用镜像站加快速度这个视团队网络情况而定。构建前要确认 publicPath如果 Ingress 里前端挂在根路径 /publicPath 保持相对路径或 / 即可如果前端要挂在 /app 之类子路径publicPath、nginx root 和 Ingress path 三处必须一致这个不一致是前端白屏的高发原因。构建完成后把 dist 目录打成镜像推送到仓库和推送后端的流程一致docker build -t registry.example.com/app-images/frontend:1.0.0 . docker push registry.example.com/app-images/frontend:1.0.04.2 Nginx 配置try_files 是 Vue2 history 路由的后悔药Vue2 项目如果用了 vue-router 的 history 模式路由是前端控制的不存在真实的路径文件。用户访问 /detail/123 时nginx 会去磁盘找这个文件找不到就返回 404。解决方式是加 try_files把不存在的路径全部回退到 index.html。我的 nginx.conf 长这样server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { return 404; } }try_files 依次尝试原始路径、目录、最后 index.html这样前端路由在刷新时也能正确加载。至于 /api 的 location我故意返回 404因为接口转发统一放在 Ingress 层做如果请求落到了这个 location说明 Ingress 配置有问题返回 404 比返回 index.html 更容易暴露问题。如果你想让 nginx 自己转发接口可以在这里配置转发到后端 Service但那样 Ingress 只需要指向前端一个服务两种方案不要混用。4.3 前端 Dockerfile 和 frontend.yaml前端镜像不需要 JDK也不需要 node_modules只要 dist 产物加 nginx 配置。Dockerfile 很简单FROM nginx:1.24-alpine COPY dist/ /usr/share/nginx/html/ COPY nginx.conf /etc/nginx/conf.d/default.confnginx 基础镜像里已有默认配置我用自定义的 nginx.conf 覆盖 default.conf避免默认配置干扰。前端 Deployment 副本数可以比后端多因为纯静态资源nginx 内存占用很小apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: app-demo spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: imagePullSecrets: - name: acr-secret containers: - name: frontend image: registry.example.com/app-images/frontend:1.0.0 ports: - name: http containerPort: 80 readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: frontend namespace: app-demo spec: selector: app: frontend ports: - name: http port: 80 targetPort: 80 type: ClusterIP探针直接请求根路径nginx 返回 200 就算就绪。前端 img 请求静态资源不经过后端这种情况用 httpGet 探针没问题。4.4 Ingress 路由/ 到前端/api 到后端Ingress 是整个集群的统一入口阿里云 ACK 自带 nginx ingress controller资源创建后自动绑定一个公网 SLB 地址。域名解析到这个地址后所有流量先进 Ingress再按规则分发。我们的规则是根路径 / 给前端/api 开头的请求给后端apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: app-demo annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: /api(/|$)(.*) pathType: ImplementationSpecific backend: service: name: biz-server port: number: 8080 - path: / pathType: Prefix backend: service: name: frontend port: number: 80这里的 rewrite-target 作用是把 /api/ 前缀去掉再转发给后端。如果后端接口路径本身带了 /api 前缀比如 controller 的 RequestMapping 是 /api/login那就不需要 rewrite直接把 path 写成 /api 加 Prefix 类型即可。两种方式按后端实际接口路径选一种不要同时用。ingressClassName 要跟集群里的 IngressClass 一致ACK 上默认叫 nginx如果集群里改过名字这里要对应改。Ingress 配置完成后用 kubectl get ingress -n app-demo 查看状态ADDRESS 列会出现公网入口 IP。域名解析那里把 app.example.com 指向这个 IP等解析生效就能访问了。5. 常见问题排查注册不上、404、重启循环、镜像拉不下来5.1 现象后端日志反复报 Connection refusedNacos 控制台却能打开后端启动后日志里出现类似Connection refused或no available server的报错但浏览器访问 Nacos 控制台 8848 端口是通的。很多人第一次遇到会怀疑是用户名密码问题实际不是。原因Nacos 2.x 服务端除了 8848还用 9848 端口做 gRPC 长连接。后端 nacos-client 是 2.x 版本注册时会自动从 8848 加 1000 得到 9848 去建立连接。如果 Service 只暴露了 88489848 不通注册必然失败。解决把 Service 里补上 grpc-9848 端口映射也就是第 3.1 节里那份 YAML 的写法。如果集群内部走 ClusterIP只要 Service 里有 9848 端口即可如果要通过节点访问需要再开 NodePort 映射。验证方法是用 kubectl exec 进入任意 Pod执行 telnet nacos-server 9848能通说明端口层没问题。5.2 现象Nacos 控制台能看到服务但服务间调用时报 UnknownHost后端已经注册到 Nacos控制台服务列表里显示了一个 10.x.x.x 的地址但服务 A 调服务 B 时偶尔报 UnknownHostException或者一段时间后又连不上。原因客户端上报的 IP 不是稳定可路由的地址。Pod 重建后 IP 会变如果注册的是 Pod IP旧实例下线后服务发现里短暂保留旧地址调用就会失败。另外某些网络环境下客户端取到的是错误的网卡地址。解决给 bootstrap.yml 的 discovery 配置显式传入 POD_IP 环境变量也就是第 3.2 节的写法让 Nacos 里展示的地址始终是当前 Pod 的真实 IP。同时服务间调用尽量走 K8s Service DNS而不是直接依赖 Nacos 里看到的 IP 去拼地址这样 Pod 重建后 Service 会自动把流量转发到新 Pod。5.3 现象前端首页能打开点进子路由再刷新就 404浏览器直接访问域名首页正常但进入 /detail/123 之类的页面后按 F5 刷新nginx 返回 404。后端接口一切正常静态资源加载也正常。原因Vue2 的 history 路由模式下浏览器刷新会真实请求 /detail/123 这个路径nginx 在磁盘上找不到对应文件按默认配置返回 404。这是 nginx 托管 SPA 应用最典型的坑本地 dev server 正常是因为 dev server 内置了 history fallbacknginx 没有。解决nginx.conf 里加上try_files $uri $uri/ /index.html;将不存在的路径回退到 index.html。如果加了之后仍然偶发 404检查 Ingress 的 path 规则根路径 / 必须是 Prefix 类型并且不要和 /api 规则顺序互相覆盖。5.4 现象后端 Pod 一直重启READY 显示 0/1kubectl get pods 看到 biz-server 的 READY 是 0/1RESTARTS 次数持续增长查看 Pod 事件全是 readiness 探针失败。原因readinessProbe 配置的 initialDelaySeconds 太短SpringBoot 应用还没完成启动和 Nacos 注册探针就开始执行。/actuator/health 返回非 200K8s 判定容器未就绪不断重启容器反而让应用永远没有机会完成启动形成死循环。如果 health 又聚合了 Nacos 健康状态Nacos 本身还在启动时这个问题更明显。解决给后端配置 startupProbe把启动窗口放宽到 60 秒左右readinessProbe 的 initialDelaySeconds 调到 30 秒以上。也可以把探针路径从 /actuator/health 改成 /actuator/health/liveness只检查进程存活不检查依赖组件状态。等应用起来后再通过 readiness 控制流量接入。探针参数要按实际启动速度调整不要照抄网上的模板。5.5 现象Pod 一直 ImagePullBackOff节点拉不到镜像后端和前端镜像都推到了 ACRdeployment 也写好并 apply 了但 Pod 事件显示 ImagePullBackOffdescribe 里能看到 Failed to pull image 的报错。原因镜像仓库是私有的K8s 节点没有仓库凭证默认拉取会认证失败。这个和本地 docker login 没关系节点上的 kubelet 不会读取你本机的 docker 配置。解决在命名空间里创建一个 docker-registry 类型的 Secret然后在 deployment 的 pod template 里引用它kubectl create secret docker-registry acr-secret \ --docker-serverregistry.example.com \ --docker-username你的用户名 \ --docker-password你的密码 \ -n app-demodeployment 的 spec.template.spec 下加 imagePullSecrets 字段指向 acr-secret。如果多个 deployment 都要用逐个加上或者通过给 namespace 配置默认 imagePullSecret 的方式统一处理。这里创建完 Secret 后已经存在的 Pod 不会自动生效需要 rollout restart 一次。6. 装完验证一条链路页面、接口、注册中心一路走通部署全部完成后建议固定按下面的顺序验证一遍不要只看 Pod 状态是 Running 就收工。第一步看 Pod。kubectl get pods -n app-demo -o wide确认 nacos-server、biz-server、frontend 都是 Running 状态READY 为 1/1RESTARTS 没有异常增长。如果有重启记录回到第 5.4 节检查探针参数。第二步看注册。kubectl logs deployment/biz-server -n app-demo --tail200搜索 nacos 相关日志正常情况下能看到服务注册成功的输出。同时打开 Nacos 控制台地址是 http://节点公网IP:30848/nacos登录后在服务管理里应该能看到 biz-server 这个服务名实例数为 2对应两个后端 Pod实例 IP 与控制台里 Pod IP 一致。第三步看前端。curl -I http://app.example.com/期望返回 HTTP/1.1 200。浏览器打开域名页面正常渲染按 F12 切到 Network 面板刷新一个带子路由的页面确认页面不 404静态资源全部 200。第四步看业务链路。在 Network 面板里找一个真实接口请求比如登录接口确认返回 JSON 而不是 index.html 的 HTML 内容。我自己习惯再用 curl 模拟一次curl -X POST http://app.example.com/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}返回 JSON 说明 Ingress 的 /api 转发规则生效后端 Service 也正常。第五步做滚动更新验证。kubectl rollout restart deployment/biz-server -n app-demo等到新 Pod 全部 Ready 后回 Nacos 控制台看实例数正常情况下旧实例注销、新实例注册最终实例数恢复到 2并且所有实例健康。这一步最能暴露注册中心配置问题很多环境部署完能跑但一发布就掉注册。从那以后我每次部署这套环境都会强制走完这五步尤其是最后一步滚动更新它能把 Nacos 注册、网络解析、探针配置的所有问题一次性逼出来。只有完整跑过这条链路我才敢说环境是真的装好了。希望帮到你。本文还有配套的精品资源点击获取