ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

K8s 部署 Vue2+SpringBoot2.5+Nacos2.0.3 全链路实战与避坑

K8s 部署 Vue2+SpringBoot2.5+Nacos2.0.3 全链路实战与避坑

简介:这份资源面向需要在阿里云Kubernetes集群上落地前后端分离项目的开发与运维人员,围绕Vue2前端、Nginx、SpringBoot2.5后端与Nacos2.0.3注册配置中心,提供一套可直接参考的部署编排方案,适合已具备容器与K8s基础、希望打通完整上线链路的中级读者。压缩包共16个文件,约34.69MB,以8个yaml清单为主,覆盖前端、后端与Nacos的Deployment、Service、Ingress及StatefulSet等编排;另含2个sh部署脚本、2个Dockerfile、1个SQL初始化脚本、1个nginx.conf、1个jar与1个gz前端产物,从镜像构建到服务暴露形成闭环。已有1342人学习下载。读者可据此理解Nacos基于StatefulSet与MySQL的持久化部署方式、前后端镜像构建思路及Ingress路由配置,并借助脚本快速复现环境,减少自行拼装清单的试错成本。

1. 一套能跑通的前后端分离部署链路,到底卡在哪

很多团队第一次把 Vue2 前端、SpringBoot2.5 后端和 Nacos2.0.3 注册中心往 K8s 上搬,卡住的地方往往不是镜像构建,而是三件事:前端 Nginx 怎么把/api转发到后端 Service、后端 Pod 启动时 Nacos 地址还没就绪导致注册失败、以及 Nacos 自身在 K8s 里用 StatefulSet 还是 Deployment 的取舍。这套部署方案就是围绕这三个真实痛点组织的:一份可复现的 K8s 资源清单,把 Vue2 静态资源、Nginx 反向代理、SpringBoot2.5 服务、Nacos2.0.3 注册配置中心串成一条完整链路。适合已经会写 Dockerfile、但对 K8s 多组件协同还没底的中级后端和运维同学,也适合想把单体项目改造成云原生部署的开发者照着抄。下面按「资源是什么 → 怎么落地 → 坑在哪」的顺序拆开讲,参数和清单都能直接改。

2. 拆解部署拓扑:四个组件在 K8s 里怎么摆

2.1 组件职责与调用关系

这套链路里四个角色分工很清晰。Vue2 打包产物是纯静态文件,交给 Nginx 托管;Nginx 同时承担反向代理,把浏览器发来的/api/**请求转发到 SpringBoot 的 Service;SpringBoot2.5 作为业务服务,启动时向 Nacos2.0.3 注册自己,并从 Nacos 拉配置;Nacos 既是注册中心也是配置中心,内部还依赖一个数据库存配置和实例信息。调用方向是单向的:浏览器 → Nginx → SpringBoot → Nacos,Nacos 不反向调用业务服务,这一点决定了网络策略和启动顺序的设计。

在 K8s 里,这四个组件对应四类工作负载。Vue2 + Nginx 合成一个镜像,用 Deployment 跑多副本,前面挂 Service 和 Ingress;SpringBoot 用 Deployment,副本数按业务量定;Nacos 用 StatefulSet 更稳,因为它需要稳定的网络标识和持久化存储。数据库可以放集群外,也可以用集群内的 MySQL,本文按集群内 MySQL 讲,方便整套自包含。

2.2 命名空间与资源配置清单

先把命名空间和基础配置立起来,后面所有资源都放进去,避免和集群里其他业务混在一起。下面这份namespace.yaml和configmap.yaml是整条链路的地基。

# namespace.yaml apiVersion: v1 kind: Namespace metadata: name: demo-stack --- # configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: stack-config namespace: demo-stack data: # Nacos 服务地址,SpringBoot 启动时读取 NACOS_ADDR: "nacos-svc.demo-stack.svc.cluster.local:8848" # Nacos 命名空间 ID,隔离不同环境配置 NACOS_NAMESPACE: "public" # 后端对外暴露端口 SERVER_PORT: "8080"

这里有两个参数值得说清楚。NACOS_ADDR用的是 K8s 集群内 DNS 全名服务名.命名空间.svc.cluster.local,比直接写 IP 稳,Pod 重建后地址不变。NACOS_NAMESPACE填public是 Nacos 默认命名空间,如果团队按环境隔离,这里要换成对应命名空间的 ID,不是名称,填错会导致配置拉不到但启动不报错,属于典型玄学问题。

资源配额建议在 ConfigMap 之外单独用 ResourceQuota 限制命名空间总量,避免某个 Pod 内存泄漏把节点拖垮。常见做法是给 SpringBoot 容器设requests.memory: 512Mi、limits.memory: 1Gi,Nacos 给requests.memory: 1Gi、limits.memory: 2Gi,因为 Nacos 2.0 默认开启 gRPC 长连接,内存占用比 1.x 高不少。

2.3 存储与网络的前置准备

Nacos 需要持久化,MySQL 也需要。用 StorageClass 动态供给最省事,如果没有默认 StorageClass,就得手动建 PV。下面这段是 Nacos 的 PVC 声明,MySQL 同理。

# nacos-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nacos-data namespace: demo-stack spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi # 有默认 StorageClass 可省略 storageClassName storageClassName: standard

网络方面,Nacos 2.0.3 除了 8848 的 HTTP 端口,还开了 9848 的 gRPC 端口用于客户端长连接。很多人在 Service 里只暴露 8848,结果 SpringBoot 能注册但配置监听不生效,日志里一堆连接超时。Service 必须同时映射 8848 和 9848,下面在 Nacos 那节会给出完整清单。前置准备做到这里,拓扑和存储就清楚了,接下来进入各组件落地。

3. 逐个落地:Nginx、SpringBoot、Nacos 的清单与参数

3.1 Vue2 + Nginx 镜像与反向代理配置

Vue2 项目打包后是dist目录,Nginx 镜像的 Dockerfile 很薄,关键是nginx.conf里的转发规则。下面这份配置把静态资源和 API 代理分开处理。

# nginx.conf 片段 server { listen 80; server_name _; # Vue2 history 路由回退,避免刷新 404 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } # 反向代理到 SpringBoot Service location /api/ { proxy_pass http://backend-svc.demo-stack.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_connect_timeout 5s; proxy_read_timeout 30s; } }

proxy_pass末尾带斜杠和不带斜杠行为不同:带斜杠会把/api/前缀去掉再转发,后端收到的路径是/user/list;不带斜杠则保留/api/user/list。这取决于 SpringBoot 的context-path设置,两边必须对齐,否则 404。try_files那行是 Vue2 history 模式的后悔药,少了它用户刷新非根路由直接白屏。proxy_read_timeout设 30 秒,长请求业务要单独调大,别全局放开。

对应的 Deployment 和 Service 清单如下,副本数先给 2,滚动更新策略用默认的 RollingUpdate 即可。

# frontend-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: demo-stack spec: replicas: 2 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: nginx image: registry.example.com/demo/frontend:v1 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: memory: 256Mi --- apiVersion: v1 kind: Service metadata: name: frontend-svc namespace: demo-stack spec: selector: app: frontend ports: - port: 80 targetPort: 80

镜像地址用内部仓库占位,实际替换成自己的。前端容器不需要环境变量注入,因为 API 地址写死在 Nginx 配置里,这也是这套方案的一个取舍:改后端地址要重新构建前端镜像。想避免的话,可以用 envsubst 在启动时渲染配置,但会多一层复杂度,团队小的时候不值得。

3.2 SpringBoot2.5 接入 Nacos2.0.3 的配置要点

SpringBoot2.5 对接 Nacos2.0.3,依赖版本要对齐。spring-cloud-alibaba用 2021.1 对应 SpringBoot2.5 比较稳,Nacos 客户端版本由它传递。下面是bootstrap.yml的关键配置,注意 SpringBoot2.5 默认不加载 bootstrap,需要引入spring-cloud-starter-bootstrap依赖。

# bootstrap.yml spring: application: name: backend-service cloud: nacos: discovery: server-addr: ${NACOS_ADDR} namespace: ${NACOS_NAMESPACE} config: server-addr: ${NACOS_ADDR} namespace: ${NACOS_NAMESPACE} file-extension: yaml # 配置不存在时不阻塞启动,按需开启 refresh-enabled: true

spring.application.name必须和 Nacos 里配置的 Data ID 前缀一致,Data ID 规则是${prefix}-${profile}.${file-extension},比如backend-service-prod.yaml。namespace填的是 ID 不是名称,前面提过。refresh-enabled打开后,Nacos 配置变更会推送到客户端,配合@RefreshScope生效,但要注意不是所有 Bean 都适合动态刷新,数据源这类建议重启。

Deployment 里通过环境变量把 ConfigMap 的值注入,同时加就绪探针,避免 Nacos 还没起来时流量打进来。

# backend-deploy.yaml 片段 spec: containers: - name: backend image: registry.example.com/demo/backend:v1 envFrom: - configMapRef: name: stack-config ports: - containerPort: 8080 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 20 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 40 periodSeconds: 20

initialDelaySeconds给 20 秒是血泪经验:SpringBoot 启动加上向 Nacos 注册,冷启动经常超过 15 秒,探针给太短会反复重启,Pod 一直 CrashLoopBackOff,看日志却没有任何异常,纯属探针误杀。/actuator/health需要引入 actuator 依赖并暴露端点,否则探针 404 同样重启。

3.3 Nacos2.0.3 的 StatefulSet 与双端口暴露

Nacos 用 StatefulSet 跑单节点起步,生产环境建议三节点集群加外部 MySQL。单节点清单如下,重点看端口和启动参数。

# nacos-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: demo-stack spec: serviceName: nacos-svc replicas: 1 selector: matchLabels: app: nacos template: metadata: labels: app: nacos spec: containers: - name: nacos image: nacos/nacos-server:v2.0.3 env: - name: MODE value: standalone - name: SPRING_DATASOURCE_PLATFORM value: mysql - name: MYSQL_SERVICE_HOST value: mysql-svc.demo-stack.svc.cluster.local - name: MYSQL_SERVICE_DB_NAME value: nacos_config - name: MYSQL_SERVICE_USER value: nacos - name: MYSQL_SERVICE_PASSWORD value: nacos_pwd ports: - containerPort: 8848 name: http - containerPort: 9848 name: grpc volumeMounts: - name: data mountPath: /home/nacos/data volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 5Gi --- apiVersion: v1 kind: Service metadata: name: nacos-svc namespace: demo-stack spec: selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848

MODE=standalone只适合开发和测试,生产要改成cluster并配NACOS_SERVERS集群地址列表。SPRING_DATASOURCE_PLATFORM=mysql表示用外部 MySQL 存配置,不配的话 Nacos 用内嵌 Derby,重启配置就丢,这是新手最容易踩的坑。9848 端口必须暴露,Nacos2.0 客户端默认用8848+1000作为 gRPC 端口,Service 里漏了它,注册能成功但配置监听和实例变更推送全部失效,表现为「服务列表能看到,但调用方拿不到最新实例」。

MySQL 初始化脚本在 Nacos 发行包的conf/mysql-schema.sql,建库后先导入再启动 Nacos,否则启动日志报表不存在。数据库连接池参数通过MYSQL_SERVICE_DB_PARAM调整,默认值在副本多的时候偏小,可以加characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true。

4. 避坑与排查:五个真实翻车现场

4.1 后端注册成功但配置拉不到

现象是 SpringBoot 启动日志显示 Nacos 注册成功,但@Value注入的配置全是默认值,Nacos 控制台也看不到配置监听。原因通常是命名空间填了名称而不是 ID,或者 Data ID 的 profile 对不上。解决方式是到 Nacos 控制台复制命名空间 ID 填进namespace,并确认 Data ID 格式为应用名-环境.yaml,本地用spring.profiles.active指定环境。排查时打开logging.level.com.alibaba.cloud.nacos=debug看客户端实际请求的 Data ID。

4.2 Nginx 转发后接口 404 或 502

404 多半是proxy_pass斜杠和context-path没对齐,按 3.1 的说明检查前缀是否被吃掉。502 则是后端 Service 没就绪或端口不对,先kubectl get endpoints backend-svc -n demo-stack看有没有后端地址,为空说明 Pod 没通过就绪探针。还有一种隐蔽情况:Nginx 配置里写的是 Service 短名backend-svc,但 Nginx 和 SpringBoot 不在同一命名空间,DNS 解析失败,必须写全名或服务名.命名空间。

4.3 Nacos Pod 反复重启且日志无异常

现象是 Nacos 容器启动几十秒后被 kill,日志停在启动完成附近。原因一般是内存 limit 给太小,Nacos2.0 启动后 JVM 堆加 gRPC 连接占用容易超过 1Gi,被 OOMKilled。用kubectl describe pod看Last State里的Reason: OOMKilled就能确认。解决是把 limit 提到 2Gi,并通过JVM_XMS、JVM_XMX环境变量限制堆大小,别让 JVM 按容器内存自动算。

4.4 前端刷新页面白屏

用户访问/user/list后按 F5 直接 404,原因是 Nginx 没配try_files回退,请求被当成静态文件找不到。按 3.1 加上try_files $uri $uri/ /index.html;即可。注意这行只对 history 模式有效,hash 模式不需要,配了也无害。如果加了还白屏,检查root路径是否和镜像里 dist 拷贝位置一致。

4.5 滚动更新时短暂 502

发布新版本时前端报一批 502,原因是旧 Pod 被删时 Nginx 还在转发,或者 SpringBoot 优雅停机没配。解决是给 SpringBoot 加server.shutdown=graceful和spring.lifecycle.timeout-per-shutdown-phase=30s,Deployment 里配terminationGracePeriodSeconds: 40,让 Pod 有时间处理完存量请求再退出。前端 Nginx 侧可以加proxy_next_upstream error timeout;做一次重试兜底。

5. 验证与进阶:把链路跑通再谈优化

部署完别急着交付,按顺序验证一遍。先看 Pod 状态:kubectl get pods -n demo-stack四个组件都 Running 且 READY 为 1/1。再看 Nacos 控制台服务列表里有没有backend-service实例,实例数应等于后端副本数。然后从前端入口访问一个调后端的页面,浏览器 Network 里/api/**返回 200。最后改一条 Nacos 配置,确认后端日志出现配置刷新记录,这一步能验证 9848 端口和长连接是否真的通了。

# 快速验证脚本 kubectl get pods -n demo-stack kubectl get endpoints backend-svc -n demo-stack # 从集群内测试 Nacos gRPC 端口连通性 kubectl run nettest --rm -it --image=busybox -n demo-stack -- \ sh -c "nc -zv nacos-svc 9848 && nc -zv nacos-svc 8848"

这段脚本里get endpoints确认 Service 有后端,nc确认两个端口都通。nc -zv的-z是只扫描不发送数据,-v输出详情,busybox 自带 nc,不用额外装工具。如果 9848 不通而 8848 通,回到 3.3 检查 Service 端口映射。

进阶方向有两个。一是把 Nacos 换成三节点集群,StatefulSet 的replicas改 3,每个 Pod 配NACOS_SERVERS指向三个 Pod 的稳定域名,MySQL 用主从或云数据库,这一步能消除单点。二是把前端 API 地址从写死改成启动时注入,用 Nginx 的envsubst模板加 ConfigMap 挂载,改后端地址不用重新构建镜像。我一般会在交付前把「改配置 → 刷新 → 验证」这条链路手动走一遍,因为 K8s 里最贵的不是部署,是部署完以为通了、上线才发现配置没生效。从那以后我每次改完 Nacos 相关配置,都强制走一遍端口连通性和配置刷新验证,希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进