简介:面向需要在阿里云Kubernetes集群上落地前后端分离项目的开发者,这套资源给出Vue2前端、SpringBoot2.5后端与Nacos2.0.3注册配置中心的一体化部署参考,适合已具备容器与K8s基础、希望打通镜像构建到服务编排全流程的中高级读者。压缩包共16个文件、约34.69MB,以8个yaml清单为主,覆盖Nacos的StatefulSet、Service、Headless、Ingress及MySQL配置等编排对象;另有2个sh部署脚本、2个Dockerfile、1份Nacos建库sql、1份nginx.conf,以及前端dist打包产物与后端jar,基本构成可直接参照的部署素材。资源已有1342人学习下载,说明其在同类场景中具备一定参考价值。读者可据此理解Nacos集群在K8s中的持久化与数据库初始化方式,掌握Vue静态资源经Nginx容器化的配置要点,并借助脚本与清单梳理SpringBoot服务的发布路径,减少自行拼装编排文件时的试错成本。
1. 一次镜像拉取失败,让我重新梳理了整套部署链路
去年帮一个朋友的公司迁移一套老系统,前端 Vue2 打包后塞进 Nginx,后端 SpringBoot 2.5,注册配置中心用 Nacos 2.0.3,目标是把这套东西整体搬上阿里云 K8s。听起来是标准三件套,结果第一次kubectl apply之后,Pod 卡在ImagePullBackOff整整两个小时,排查下来是私有镜像仓库的 secret 没挂到正确的 namespace 上。这种坑不装一遍根本想不到。这套组合在国内中小团队里极其常见,Vue2 加 Nginx 做静态资源服务,SpringBoot 2.5 做业务后端,Nacos 2.0.3 管服务注册和配置,最后统一编排到阿里云 ACK 集群。装不好会怎样?前端 404、后端连不上 Nacos、健康检查一直失败、滚动更新卡死,每一个都能让你加班到半夜。这篇笔记面向正在把这套环境往 K8s 上搬的开发者,新手能照着走通,熟手能看到版本取舍和参数细节。
2. 环境准备与版本选型:先把地基打对
2.1 集群与本地工具链的版本对齐
动手之前先把版本钉死,这套组合里最容易出问题的不是代码,是版本错配。SpringBoot 2.5 对应的是 Spring Cloud 2020.0.x 系列,Nacos 客户端版本要跟服务端的 2.0.3 对齐,Vue2 的构建产物是纯静态文件,对 Node 版本反而没那么敏感,但 Nginx 的基础镜像版本会影响配置语法支持。
我一般会先把本地工具链和集群版本列成一张表,避免后面来回试:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| 阿里云 ACK 集群 | 1.24 及以上 | 1.24 之后 Dockershim 移除,注意运行时 |
| kubectl | 与集群大版本差不超过 1 | 版本偏差过大会出现字段不识别 |
| Docker | 20.10.x | 本地构建镜像用 |
| Node.js | 14.x 或 16.x | Vue2 项目建议锁 14/16,别上 18 |
| JDK | 8 或 11 | SpringBoot 2.5 两者都支持 |
| Nacos | 2.0.3 | 服务端与客户端保持一致 |
| Nginx | 1.21 或 1.23 | 基础镜像选 alpine 版体积小 |
这里有个反直觉的点:很多人以为集群版本越新越好,但 ACK 1.24 之后默认容器运行时从 Docker 换成了 containerd,如果你本地用 Docker 构建镜像再推到 ACR(阿里云容器镜像服务),推拉本身没问题,问题出在调试时习惯性docker exec进节点,结果节点上根本没有 docker 命令。我一般会在集群创建后先确认运行时:
# 查看节点容器运行时,确认是 docker 还是 containerd kubectl get nodes -o wide kubectl describe node <节点名> | grep -i "container runtime"输出里如果是containerd://1.6.x,那节点层面的排查命令就要换成crictl,这个细节不提前确认,后面排错会绕很多弯路。参数上没什么可调的,关键是心里有数。
2.2 命名空间、镜像仓库与拉取凭证
这套系统我习惯单独开一个 namespace,不跟其他业务混在一起,资源配额和网络策略都好管。镜像仓库用阿里云的 ACR,个人版够用,企业版有更细的权限控制。创建 namespace 和拉取 secret 是第一步:
# 创建独立命名空间 kubectl create namespace vue-spring-nacos # 基于 ACR 账号密码创建镜像拉取 secret kubectl create secret docker-registry acr-cred \ --docker-server=registry.cn-hangzhou.aliyuncs.com \ --docker-username=<你的ACR用户名> \ --docker-password=<你的ACR密码> \ --namespace=vue-spring-nacos--docker-server要填你 ACR 实例所在地域的完整域名,填错了 secret 虽然能创建成功,但拉镜像时依然会 401。--namespace必须跟后面 Deployment 所在的 namespace 一致,这就是我开头踩的那个坑——secret 建在了 default 里,Pod 在业务 namespace 里当然找不到。常见做法是把 secret 和 Deployment 写在同一个 YAML 文件里,用---分隔,apply 的时候一起生效,省得漏。
另外提醒一句,ACR 个人版的仓库地址和 VPC 内网地址不一样,集群在 VPC 内的话优先用内网地址拉镜像,速度快还不走公网流量。这个在 ACR 控制台的仓库基本信息里能看到,别直接复制公网地址就往 YAML 里贴。
3. 三个组件的镜像构建:Vue2、SpringBoot 2.5 与 Nacos 2.0.3
3.1 Vue2 前端打包与 Nginx 镜像
Vue2 项目构建产物是dist目录,Nginx 镜像要做的事很简单:把 dist 拷进去,替换默认配置。Dockerfile 我一般这么写:
# 前端镜像:基于 nginx alpine,体积小启动快 FROM nginx:1.23-alpine # 删掉默认配置,避免和自定义配置冲突 RUN rm -f /etc/nginx/conf.d/default.conf # 拷贝自定义 nginx 配置 COPY nginx.conf /etc/nginx/conf.d/app.conf # 拷贝 Vue2 构建产物 COPY dist/ /usr/share/nginx/html/ EXPOSE 80对应的nginx.conf里最关键的是 history 路由的 fallback,Vue2 用 history 模式时刷新页面会 404,必须配try_files:
server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # Vue2 history 模式核心配置,找不到文件就回退到 index.html location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理,指向 K8s 内部 Service location /api/ { proxy_pass http://springboot-svc:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }proxy_pass末尾那个斜杠很关键,带斜杠会把/api/前缀剥掉再转发,不带则原样转发,后端接口路径对不上就是这里的问题。springboot-svc是后面要建的 Service 名,K8s 内部 DNS 直接解析,不用写 IP。构建命令就一句docker build -t registry.cn-hangzhou.aliyuncs.com/<命名空间>/vue-web:v1 .,推之前记得docker login。
3.2 SpringBoot 2.5 镜像与 Nacos 客户端配置
SpringBoot 2.5 的镜像我用多阶段构建,编译和运行分开,运行镜像只留 JRE:
# 构建阶段 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 # 运行阶段,只保留 JRE FROM openjdk:8-jre-slim WORKDIR /app COPY --from=build /app/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]dependency:go-offline这步是为了利用 Docker 层缓存,pom 没变就不会重新拉依赖,构建快很多。运行阶段用openjdk:8-jre-slim而不是完整 JDK,镜像能小一半。
Nacos 2.0.3 的客户端配置在application.yml里,这里有个 2.0 之后的坑:2.0 版本 Nacos 增加了 gRPC 通信,默认端口是主端口加 1000,也就是 8848 对应 9848,K8s 里如果只暴露了 8848,客户端会连不上。配置长这样:
spring: cloud: nacos: discovery: server-addr: nacos-svc:8848 namespace: ${NACOS_NAMESPACE:public} config: server-addr: nacos-svc:8848 file-extension: yaml namespace: ${NACOS_NAMESPACE:public}server-addr用 K8s Service 名,不要写具体 Pod IP。namespace用环境变量注入,不同环境用不同命名空间隔离配置。注意 Nacos 2.0.3 客户端连服务端时,9848 端口必须通,这个在 Service 定义里要一起暴露,后面第 4 章会讲。
3.3 Nacos 2.0.3 服务端镜像与持久化
Nacos 服务端我建议直接用官方镜像nacos/nacos-server:2.0.3,不要自己从头构建。但官方镜像默认用内嵌 Derby 数据库,生产环境必须换成 MySQL,否则重启配置就丢。环境变量这么配:
env: - name: MODE value: "standalone" # 单机模式,集群模式改 cluster - name: SPRING_DATASOURCE_PLATFORM value: "mysql" - name: MYSQL_SERVICE_HOST value: "mysql-svc" - name: MYSQL_SERVICE_DB_NAME value: "nacos_config" - name: MYSQL_SERVICE_USER value: "nacos" - name: MYSQL_SERVICE_PASSWORD value: "<数据库密码>" - name: NACOS_AUTH_ENABLE value: "true" # 开启鉴权,2.0.3 支持MODE选 standalone 适合中小规模,真要集群就改 cluster 并配NACOS_SERVERS列表。NACOS_AUTH_ENABLE开了之后客户端也要配用户名密码,别忘了。MySQL 那边要提前建库并导入 Nacos 2.0.3 自带的mysql-schema.sql,这个脚本在官方包的 conf 目录里,版本不对会导致表结构缺失,启动报错。
4. K8s 编排落地:Deployment、Service 与配置注入
4.1 前端与后端 Deployment 的关键参数
Deployment 的 YAML 里,除了镜像和端口,最容易被忽略的是探针和资源限制。前端是静态服务,探针简单;后端启动慢,探针的initialDelaySeconds要给够,不然 Pod 还没起来就被判定失败重启,陷入循环。
apiVersion: apps/v1 kind: Deployment metadata: name: springboot-app namespace: vue-spring-nacos spec: replicas: 2 selector: matchLabels: app: springboot-app template: metadata: labels: app: springboot-app spec: imagePullSecrets: - name: acr-cred containers: - name: app image: registry.cn-hangzhou.aliyuncs.com/<命名空间>/springboot-app:v1 ports: - containerPort: 8080 resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1" memory: "1Gi" readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 40 # SpringBoot 启动慢,给足时间 periodSeconds: 10 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 20imagePullSecrets必须写在spec.template.spec下,写在 Deployment 顶层是无效的,这是另一个高频错误。readinessProbe的initialDelaySeconds给 40 秒是因为 SpringBoot 2.5 加上 Nacos 注册,冷启动通常要 20 到 30 秒,给少了就反复重启。/actuator/health需要 pom 里引入spring-boot-starter-actuator并暴露端点,没配的话探针一直 404,Pod 永远不 Ready。
4.2 Service 暴露与 Nacos 双端口处理
Service 这块,前端用 ClusterIP 加 Ingress 对外,后端和 Nacos 用 ClusterIP 内部通信。Nacos 2.0.3 的 Service 要同时暴露 8848 和 9848:
apiVersion: v1 kind: Service metadata: name: nacos-svc namespace: vue-spring-nacos spec: selector: app: nacos ports: - name: http port: 8848 targetPort: 8848 - name: grpc port: 9848 targetPort: 9848 # Nacos 2.0 gRPC 端口,少这个客户端连不上 type: ClusterIP9848 这个端口是 Nacos 2.0 之后新增的,客户端启动时会尝试建立 gRPC 长连接,Service 里没暴露就会一直重连失败,日志里刷Client not connected。我见过有人排查半天以为是网络策略问题,其实就是 Service 少写了一个端口。前端 Ingress 的配置按阿里云 ACK 的 Ingress 控制器来写,注解里指定证书和后端 Service 即可,这里不展开。
4.3 ConfigMap 与 Secret 的注入方式
配置注入我一般分两类:非敏感的放 ConfigMap,密码类放 Secret。SpringBoot 的application.yml里数据库密码、Nacos 密码这些不要硬编码,用环境变量引用:
apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: vue-spring-nacos data: NACOS_NAMESPACE: "prod" SPRING_PROFILES_ACTIVE: "k8s" --- apiVersion: v1 kind: Secret metadata: name: app-secret namespace: vue-spring-nacos type: Opaque stringData: DB_PASSWORD: "<数据库密码>" NACOS_PASSWORD: "<Nacos密码>"然后在 Deployment 里用envFrom一次性注入:
envFrom: - configMapRef: name: app-config - secretRef: name: app-secretstringData比data好用,直接写明文,K8s 自动帮你 base64 编码,省得手动转。但注意 Secret 只是编码不是加密,生产环境要配合 KMS 或者外部密钥管理,这个看团队规范。ConfigMap 改了之后 Pod 不会自动重启,需要kubectl rollout restart deployment/springboot-app -n vue-spring-nacos手动触发,这个习惯要养成。
5. 避坑与排查:五个真实翻车记录
5.1 镜像拉取失败:secret 挂错位置
现象是 Pod 状态ImagePullBackOff,kubectl describe pod里事件显示unauthorized或pull access denied。原因九成是imagePullSecrets没写或者写错层级,要么就是 secret 建在了别的 namespace。解决方法是先确认 secret 存在:kubectl get secret acr-cred -n vue-spring-nacos,再检查 Deployment 的spec.template.spec.imagePullSecrets字段,两处都对还不行就用kubectl get secret acr-cred -n vue-spring-nacos -o yaml看里面的.dockerconfigjson解码后 server 地址对不对。
5.2 Nacos 客户端连不上:9848 端口没通
现象是 SpringBoot 启动日志刷com.alibaba.nacos.api.exception.NacosException: Client not connected,或者注册一直失败。原因是 Nacos 2.0 的 gRPC 端口 9848 没在 Service 里暴露,或者 NetworkPolicy 拦了。解决方法是检查 Service 的 ports 列表有没有 9848,再kubectl exec进后端 Pod 用nc -zv nacos-svc 9848测连通性。如果集群开了网络策略,还要放行对应端口。
5.3 前端刷新 404:history 模式没配 fallback
现象是首页能打开,点进子路由再刷新就 404。原因是 Nginx 没配try_files,请求的路径在磁盘上找不到对应文件。解决方法是在nginx.conf的location /里加try_files $uri $uri/ /index.html;,改完重新构建镜像推上去。这个坑几乎每个 Vue2 上 Nginx 的人都会踩一次,记住就好。
5.4 后端反复重启:探针时间给太短
现象是 Pod 的 RESTARTS 计数一直涨,kubectl describe里显示 liveness probe failed。原因是 SpringBoot 冷启动超过探针的initialDelaySeconds,还没起来就被判定死亡。解决方法把initialDelaySeconds调到 60 以上,failureThreshold适当加大,或者干脆先只留 readinessProbe,等稳定了再加 liveness。我一般会先kubectl logs看启动耗时,按实际时间加 20 秒余量。
5.5 Nacos 重启配置丢失:还在用内嵌 Derby
现象是 Nacos Pod 重启后,之前注册的服务和配置全没了。原因是SPRING_DATASOURCE_PLATFORM没设成 mysql,默认走了内嵌 Derby,数据存在容器里,重启即丢。解决方法是确认环境变量配了 mysql 相关参数,并且 MySQL 里已经导入对应版本的 schema。改完重建 Pod,再注册一个测试服务验证持久化生效。
6. 装完怎么验证:从 Pod 状态到端到端链路
部署完别急着交付,按顺序验证一遍才算跑通。第一步看 Pod 和 Service 状态:
# 所有资源状态,Running 且 READY 为 1/1 才算正常 kubectl get pods,svc -n vue-spring-nacos # 看事件,排查 Pending 或重启原因 kubectl describe pod <pod名> -n vue-spring-nacos | tail -20第二步验证 Nacos 注册是否生效,进 Nacos 控制台或者直接调 API:
# 通过 Nacos OpenAPI 查服务列表,确认后端已注册 kubectl exec -it <任意pod> -n vue-spring-nacos -- \ curl -s "http://nacos-svc:8848/nacos/v1/ns/service/list?pageNo=1&pageSize=10"返回的doms里能看到 SpringBoot 服务名,说明注册链路通了。第三步验证前端到后端的完整调用,用kubectl port-forward把前端 Service 映射到本地,浏览器访问并触发一个调后端的接口:
# 本地映射前端服务,浏览器访问 localhost:8080 验证页面和接口 kubectl port-forward svc/vue-web-svc 8080:80 -n vue-spring-nacos页面能打开、子路由刷新不 404、接口返回正常数据,这三条都过才算端到端跑通。最后再模拟一次滚动更新,kubectl set image换个镜像 tag,观察新 Pod 起来、旧 Pod 优雅退出,确认探针和优雅停机配置没问题。
我自己的习惯是每次部署完必走一遍这个清单,尤其是 Nacos 的 9848 端口和前端 fallback 这两项,翻车概率最高。从那以后我每次上这套组合,都强制先把 Service 端口和探针时间在纸上列一遍再 apply,省下的排查时间远比多花的那十分钟值。希望帮到你。
本文还有配套的精品资源,点击获取