阿里云K8s部署Vue+Spring Boot+Nacos:容器化微服务实战与避坑指南
简介面向阿里云K8s部署Vue2前端、Nginx、SpringBoot2.5后端及Nacos 2.0.3注册中心的开发者这套部署资料包提供完整的容器化落地方案。包体共16个文件包括8个YAML编排文件、2个Shell脚本、2个Dockerfile以及SQL初始化、Nginx配置、业务JAR和前端静态压缩包整体34.69MB目录按模块划分便于单独复用。内容涵盖Nacos有状态集群的StatefulSet部署、MySQL库表初始化、前端与后端服务的Ingress路由以及一键执行脚本可以直接在阿里云K8s上复现微服务基础环境。目前已有1340人学习下载适合需要参考完整编排模板和自动化部署脚本的中高级开发者。1. 阿里云 k8s 上部署 vue2nginxspringboot2.5nacos2.0.3这套组合到底怎么串起来一句话说清楚这套部署方案就是把一个典型的前后端分离项目完整搬到阿里云容器服务 K8s 集群上前端用 Vue2 构建后用 Nginx 镜像托管静态文件后端是 Spring Boot 2.5 的 Java 应用打成镜像运行注册中心和配置中心用 Nacos 2.0.3整个集群内部通过 Service 域名互相通信外部流量从 Ingress 进来。很多团队第一步就卡在“各个组件单独跑都正常放进 K8s 里谁也不认识谁”这个阶段——Nacos 注册不上、前端代理不到后端、Pod 重启就丢配置这些不是某一家云厂商的特殊问题而是这套技术栈在容器环境下的通病。这篇笔记面向的是已经能用 Docker 跑通项目、但对 K8s 编排还处于“照着模板改”阶段的开发者。我会把从镜像构建、Nacos 持久化部署、Spring Boot 注册发现到 Nginx 反向代理和 Ingress 暴露的完整路径展开重点写那些不跑一遍根本发现不了的坑Nacos 2.x 的 gRPC 端口、K8s 里 Service 名和 localhost 的区别、StatefulSet 和 Deployment 的选择依据。这些点排查起来都靠日志说话属于那种“知道了一次就会了”的经验能帮你把部署时间从两三天压到一个下午。2. 部署前的架构拆解与镜像构建先把三个组件的关系理顺2.1 为什么是 Nacos 2.0.3 Spring Boot 2.5 这个版本组合在写任何 YAML 之前先回答一个最容易忽略的问题为什么标题里锁死了 Spring Boot 2.5 和 Nacos 2.0.3 这两个版本因为这两个版本之间的兼容性关系直接决定了你后面要踩多少坑。Spring Boot 2.5.x 对应的 Spring Cloud Alibaba 版本是 2021.0.1.0 这一支Nacos Client 内置的是 2.0.3 左右的版本。如果用了 Spring Boot 2.6.x 搭配旧版 Spring Cloud Alibaba会遇到spring.factories加载机制变化导致的自动配置失效反过来 Spring Boot 2.4 以下配 Nacos 2.x 服务端又会出现配置拉取协议不兼容。所以这套组合不是随便选的它是“当时稳定且社区验证最多”的一条路。然后是 Nacos 2.0.3 本身。2.x 相比 1.x 最大的变化是引入了 gRPC 长连接通信服务端除了 8848 的 HTTP 端口还额外监听 9848客户端 gRPC和 9849服务端 gRPC端口。这点在 K8s 部署里极其关键因为 Service 如果只暴露 8848客户端注册看着成功但心跳和订阅会全部超时表现为“服务列表里能看到过一会儿就掉线”。我见过很多团队在这个问题上卡了一整天最后发现是 Service 端口没带全。最后说镜像构建的总体策略。我的习惯是三个组件分两条流水线前端和后端各一条Nacos 直接用官方镜像但把配置外置。前端镜像用多阶段构建避免把 node_modules 和构建产物一起带进最终镜像后端镜像用maven镜像做构建阶段openjdk镜像做运行阶段。镜像仓库统一推到阿里云 ACRK8s 集群从 ACR 拉取这样在集群节点上不需要额外配 docker login用免密组件或 secrets 都能解决。2.2 前端镜像Vue2 构建 Nginx 托管反向代理指向哪个地址前端这部分的镜像构建关键不在 Vue 本身而在 Nginx 配置。Vue2 构建出来是纯静态文件不需要 Node 运行时所以 Nginx 镜像就是最终归宿。但浏览器里跑的是用户侧的 JavaScript它发请求的目标地址必须是浏览器能访问到的地址不能是 K8s 集群内部的 Service 名——这是前后端联调最容易绕晕的地方。常见做法是前端构建时把所有接口请求路径写成相对路径/api/...在 Nginx 配置里通过location /api块把请求反向代理到后端 Service。这样镜像内部通过 Service 名访问后端浏览器侧只感知到同域请求天然规避跨域问题。下面这份就是我在类似项目里常用的 nginx.conf 模板。server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # 前端路由 history 模式下的回退必须放在静态文件匹配之后 location / { try_files $uri $uri/ /index.html; } # 接口反向代理/api 开头的请求转发到后端 Service 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; # 后端接口如果涉及文件上传改大 body 限制 client_max_body_size 20m; } # 静态资源缓存策略 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 7d; add_header Cache-Control public, no-transform; } gzip on; gzip_types text/plain application/javascript text/css application/json; gzip_min_length 1k; }对应这份配置的 Dockerfile 采用多阶段构建第一阶段用 node 镜像跑npm install和npm run build第二阶段把构建产物拷贝进 nginx 镜像。注意backend-service这个名字不是随便写的它对应后面创建的 K8s Service 名称你如果用了别的命名空间或 Service 名这里必须同步改。# 第一阶段构建 Vue2 项目 FROM node:14-alpine AS build-stage WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . RUN npm run build # 第二阶段Nginx 托管静态文件 FROM nginx:1.21-alpine COPY --frombuild-stage /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80这里有个细节值得说try_files $uri $uri/ /index.html;这一行是给 Vue Router 的 history 模式用的。如果你用的是 hash 模式不加问题不大但一旦上了 history 模式而不配这一行刷新页面就 404而且这个 404 是 Nginx 直接返回的不走任何后端逻辑排查时容易误判成后端出问题。2.3 后端镜像Spring Boot 2.5 的 DockerfileJVM 参数和时区一起搞定后端镜像相对简单但有两个细节我会一并处理掉时区问题和 JVM 内存参数。基础镜像用openjdk:8-jre-alpine还是openjdk:11取决于你本地的 JDK 版本。Spring Boot 2.5 对 Java 8 和 11 都支持但如果你的项目里用了javax命名空间的旧依赖Java 8 更省心。# 第一阶段Maven 构建 FROM maven:3.6.3-openjdk-8 AS build-stage WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests # 第二阶段运行 FROM openjdk:8-jre-alpine WORKDIR /app # 设置时区不做这一步的话容器日志时间是 UTC排查问题非常痛苦 RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone COPY --frombuild-stage /app/target/*.jar app.jar # JVM 参数在 K8s 里用环境变量传入镜像里只留默认值 ENV JAVA_OPTS EXPOSE 8080 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]这里我是故意把JAVA_OPTS留成空的环境变量的。在 K8s 里部署时容器内存限制由resources.limits.memory控制而 JVM 默认堆大小是根据宿主机内存算的如果不显式指定-Xmx很容易出现容器内存限制 512Mi 但 JVM 申请了 1G 堆的情况直接被 OOMKilled。我的习惯是在 Deployment 的 env 里注入JAVA_OPTS-Xms256m -Xmx256m限制和容器配额对齐。镜像部分到此就齐了三个镜像分别对应xxx-vue-nginx、xxx-springboot、nacos-server。前两个自己构建推 ACRNacos 用官方镜像版本标签锁2.0.3不要用latest。3. 在 K8s 里部署 Nacos 2.0.3StatefulSet 的选择与 MySQL 持久化3.1 为什么用 StatefulSet 而不是 Deployment 来跑 NacosNacos 在 K8s 里的部署方式社区主要有两派官方提供的nacos-operator和自己手写 YAML。我倾向于手写 YAML因为 operator 版本在 2.x 早期的兼容性不稳定而且引入 operator 本身就是个学习成本。手写的关键是选对工作负载类型——Nacos 是有状态服务数据要持久化Pod 身份要稳定这三条都指向 StatefulSet。Deployment 和 StatefulSet 的核心区别在于 Pod 的身份和存储。Deployment 的 Pod 名字是随机后缀重建后完全变了StatefulSet 的 Pod 有稳定的网络标识比如nacos-0.nacos-service而且每个 Pod 可以挂独立的 PVC。对于 Nacos 来说如果做集群模式节点间通信依赖于稳定的节点身份StatefulSet 是唯一正确的选择。如果你只是单机测试用 Deployment 加一个 PVC 也能跑但生产环境我建议直接上 StatefulSet省得以后迁移。Nacos 2.0.3 的存储有两块业务数据在 MySQL 里本地文件在HOME目录下。所以持久化也要分两部分考虑——MySQL 数据用 PVCNacos 的logs和conf目录最好也挂出来不然 Pod 重建后日志全丢排错没有依据。下面这份 YAML 是我在类似项目中实际用过的骨架已经去掉了具体的镜像地址apiVersion: v1 kind: Service metadata: name: nacos-service namespace: app-prod spec: clusterIP: None ports: - name: http port: 8848 targetPort: 8848 - name: rpc port: 9848 targetPort: 9848 selector: app: nacos --- apiVersion: apps/v1 kind: StatefulSet metadata: name: nacos namespace: app-prod spec: serviceName: nacos-service replicas: 1 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: standalone - name: NACOS_SERVER_PORT value: 8848 - name: PREFER_HOST_MODE value: hostname - name: SPRING_DATASOURCE_PLATFORM value: mysql - name: MYSQL_SERVICE_HOST value: mysql-service - name: MYSQL_SERVICE_DB_NAME value: nacos_config - name: MYSQL_SERVICE_PORT value: 3306 - name: MYSQL_SERVICE_USER value: nacos - name: MYSQL_SERVICE_PASSWORD valueFrom: secretKeyRef: name: mysql-secret key: nacos-password - name: JVM_XMS value: 256m - name: JVM_XMX value: 256m - name: JVM_XMN value: 128m volumeMounts: - name: nacos-logs mountPath: /home/nacos/logs - name: nacos-conf mountPath: /home/nacos/conf resources: requests: memory: 512Mi cpu: 250m limits: memory: 1Gi cpu: 1 volumeClaimTemplates: - metadata: name: nacos-logs spec: accessModes: [ReadWriteOnce] storageClassName: alicloud-disk-essd resources: requests: storage: 10Gi - metadata: name: nacos-conf spec: accessModes: [ReadWriteOnce] storageClassName: alicloud-disk-essd resources: requests: storage: 10Gi注意这份清单里 Service 我用了clusterIP: None这是 headless service。StatefulSet 需要它来给每个 Pod 分配稳定的 DNS 名称。但有个细节headless service 没有负载均衡功能如果你的客户端需要访问 Nacos 的 8848 端口还需要额外建一个普通 Service。Nacos 2.x 创建集群时会自动寻找同类节点但客户端如果走 headless service会出现选到某个具体 Pod 的情况虽然大多数时候能通但不如用普通 Service 稳定。另外要特别说PREFER_HOST_MODEhostname这个环境变量。默认情况下 Nacos 会把 Pod 的 IP 注册为服务地址但 Pod 重建后 IP 就变了客户端缓存的老地址就失效。设成 hostname 后Nacos 会注册为 Pod 的 DNS 名称比如nacos-0.nacos-service.app-prod.svc.cluster.local这个地址在 Pod 重建后依然不变。这在 K8s 环境里是必须的不设的话服务注册中心会频繁出现“服务健康但调用失败”的诡异问题。3.2 Nacos 的 MySQL 初始化建库脚本和账户权限这两步不能省Nacos 2.0.3 做配置存储需要 MySQL但很多人会漏掉一个前提MySQL 里得先有nacos_config这个库和对应的表结构。Nacos 不会自动建库它只会在已有库上执行表结构的检查。Nacos 官方镜像的conf/目录下有mysql-schema.sql先把脚本在 MySQL 里执行一遍Nacos 启动后才会正常读写。具体操作是先把 Nacos 镜像跑起来或者直接解压 Nacos 安装包拿到mysql-schema.sql然后导入到你的 MySQL 实例。如果你是拿云数据库 RDS 当存储那更简单直接在 DMS 里把脚本粘贴执行。这个步骤执行完建议顺手确认两张关键表的存在config_info和service_info一个管配置一个管服务实例。缺了其中任何一张Nacos 启动时都报错但报错信息不尽相同容易让人误判。MySQL 账户的权限也要给对。Nacos 连接的账户至少需要SELECT、INSERT、UPDATE、DELETE权限如果给的是个只读账户启动时不会报错但写配置、注册服务实例时就会静默失败表现就是“Nacos 控制台能打开但新增配置一直不成功”。我的做法是单独建一个nacos用户只授权nacos_config库的 DML 权限从权限上隔离风险。最后提一下 K8s 里的Secret管理密码。上面 YAML 里我写了valueFrom.secretKeyRef这个 Secret 要先创建。用kubectl create secret会明文暴露在命令历史里我一般先写一个 Secret 的 YAML 文件再 apply密码用 base64 编码虽然安全性有限但至少不在命令行里直接出现。生产环境可以接阿里云的 KMS 插件做加密但那是后话。4. 部署 Spring Boot 2.5 应用从 bootstrap.yml 到 K8s 探针4.1 Spring Boot 接入 Nacos 的最小配置依赖、bootstrap.yml、注解三件套后端要接入 Nacos 2.0.3需要处理“注册发现”和“配置管理”两个能力。先说依赖Spring Boot 2.5 对应的是 Spring Cloud Alibaba 2021.0.1.0你在pom.xml里引入三个坐标——spring-cloud-alibaba-dependencies做 BOM 管理nacos-discovery管注册nacos-config管配置。版本号不要自己手写用 BOM 统一锁不然各种莫名其妙的 NoSuchMethodError 会把你逼疯。配置文件的重点在于Nacos 的配置不是写在application.yml里的而是要写进bootstrap.yml因为 bootstrap 阶段的执行顺序在application.yml之前需要先连接 Nacos 拉取配置后续的属性才能被覆盖。这是一个面试八股里经常考、但实际部署时最容易出问题的点——很多人把server-addr写进了application.yml结果 Nacos 配置中心的配置永远加载不进来。# bootstrap.yml spring: application: name: backend-service cloud: nacos: discovery: server-addr: nacos-service:8848 namespace: app-prod group: DEFAULT_GROUP config: server-addr: nacos-service:8848 namespace: app-prod group: DEFAULT_GROUP file-extension: yaml # 共享配置如果有多个微服务共用的配置段放这里 shared-configs: ->apiVersion: apps/v1 kind: Deployment metadata: name: backend-service namespace: app-prod spec: replicas: 2 selector: matchLabels: app: backend-service template: metadata: labels: app: backend-service spec: containers: - name: backend-service image: registry.example.com/app-prod/backend-service:1.2.0 ports: - containerPort: 8080 env: - name: JAVA_OPTS value: -Xms256m -Xmx256m - name: SPRING_PROFILES_ACTIVE value: prod resources: requests: memory: 512Mi cpu: 250m limits: memory: 1Gi cpu: 1 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 60 periodSeconds: 15 timeoutSeconds: 3 failureThreshold: 3 lifecycle: preStop: exec: command: [sh, -c, sleep 5] terminationGracePeriodSeconds: 30 --- apiVersion: v1 kind: Service metadata: name: backend-service namespace: app-prod spec: selector: app: backend-service ports: - port: 8080 targetPort: 8080readinessProbe和livenessProbe的区别必须讲清楚readiness 探针失败Pod 会被从 Service 的 Endpoints 里摘掉流量不再转发但 Pod 不会被重启liveness 探针失败Kubelet 会直接杀掉容器重启。所以 liveness 的initialDelaySeconds一定要给足Spring Boot 应用冷启动加注册到 Nacos 的过程可能要 30-60 秒如果延迟时间太短探针一失败就重启陷入 CrashLoopBackOff。preStop钩子里我放了sleep 5别小看这 5 秒。K8s 在终止 Pod 时先执行 preStop然后等terminationGracePeriodSeconds如果应用在收到 SIGTERM 后没有优雅退出这个 sleep 给了流量从 Service 摘除的缓冲时间防止“Pod 已经挂了但流量还在转发”的窗口期。Spring Boot 2.5 在默认情况下会把server.shutdown设为immediate如果要优雅停机得设成graceful并配合spring.lifecycle.timeout-per-shutdown-phase控制最长等待时间。后端注册到 Nacos 后验证方式很简单打开 Nacos 控制台的“服务管理-服务列表”能看到backend-service这个服务IP 地址显示的是 Pod 的 IP状态是健康。如果看不到先看 Pod 日志里有没有注册成功的记录再看 Nacos 的9848端口通不通。这个排查路径是最快的。5. 前端与 IngressNginx 镜像上线浏览器流量怎么进集群5.1 前端部署到 K8sDeployment 还是直接挂静态文件前端镜像和 Nginx 配置在第 2 章已经构建好了到 K8s 这一侧反而简单。前端的 Deployment 不需要环境变量不需要探针甚至不需要多副本——静态文件服务是无状态的Nginx 天然支持多副本但单个副本在资源充足时也够用。更大的意义在于练熟 K8s 的基本流程为以后加副本滚动更新打好底子。apiVersion: apps/v1 kind: Deployment metadata: name: vue2-nginx namespace: app-prod spec: replicas: 2 selector: matchLabels: app: vue2-nginx template: metadata: labels: app: vue2-nginx spec: containers: - name: vue2-nginx image: registry.example.com/app-prod/vue2-nginx:1.2.0 ports: - containerPort: 80 resources: requests: memory: 64Mi cpu: 50m limits: memory: 128Mi cpu: 250m readinessProbe: httpGet: path: / port: 80 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: vue2-nginx-service namespace: app-prod spec: selector: app: vue2-nginx ports: - port: 80 targetPort: 80前端资源里有个被忽视的点readinessProbe的路径写/即可Nginx 默认返回 200。如果你在 Nginx 配置里加了 basic auth 或者其他拦截规则探针会失败。另外如果你的 Vue2 项目用了publicPath: /的默认配置那 Nginx 的root指向html目录就能直接工作但如果你用了 CDN 路径或二级目录部署部署方式要跟着调不过这个一般不在 K8s 部署的讨论范围内。Nginx 镜像的副本数我的建议是生产环境至少 2 个副本起步。原因不是性能而是kubectl rollout restart滚动更新时如果只有一个副本更新期间会有一小段服务不可用。两个副本配合maxSurge和maxUnavailable的默认策略可以做到滚动更新不中断。5.2 Ingress 配置域名、路径转发和强制 HTTPS前端和后端在集群内部都通了最后一步是把服务从集群里暴露出去。传统方式是NodePort但生产我建议直接上 Ingress。阿里云的 K8s 集群默认装好了一个 Ingress Controller你只需要创建 Ingress 资源就能用。下面的配置演示了将app.example.com域名的流量路由到前端 Nginx 服务后端 API 在 Nginx 内部代理不需要单独暴露。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: app-prod annotations: nginx.ingress.kubernetes.io/proxy-body-size: 20m nginx.ingress.kubernetes.io/ssl-redirect: true spec: rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: vue2-nginx-service port: number: 80这里有一处容易踩的设计坑为什么前端 Nginx 里代理的backend-service已经能访问了还要单独开 Ingress因为 Ingress 是集群的入口前端 Nginx 是集群内的反向代理两者职责不同。浏览器访问https://app.example.com时流量进入集群的 Ingress Controller然后转发到前端 Nginx 这个 PodNginx 再把/api/前缀的请求转给后端 Service。这套链路中任何一环的名字写错都会出现 502 或 404。Ingress 的注解里我把proxy-body-size设成了20m和 Nginx 配置里的client_max_body_size对齐。如果不设置 Ingress 这一层的 body 限制默认值是 1m你上传一个 5MB 的文件会在 Ingress 这一层直接被拒。这类问题在前后端联调时非常隐蔽因为接口返回的错误码是 413前端却以为是后端报错。6. 常见问题避坑Nacos 注册、端口、升级我帮你们把坑踩平了6.1 现象服务注册上了但调用超时Nacos 控制台显示“临时实例已删除”这个问题的典型特征是Spring Boot 应用启动后日志里能看到 Nacos 注册成功的输出Nacos 控制台也能短暂看到服务实例但几秒到几十秒后实例状态变成“不健康”或者直接从列表消失前端调用接口就报连接超时。原因有三个层次从最常见到最隐蔽排列第一层是9848端口没暴露Nacos 2.x 的客户端心跳走的是 gRPC 长连接如果 Service 的 targetPort 没把 9848 映射出来心跳就发不出去第二层是PREFER_HOST_MODE没设成hostname客户端注册的是 Pod IP而 Pod 重建后 IP 变了旧地址的心跳永远收不到第三层是集群的网络安全组把 9848 端口拦了。你自己检查的顺序也按这三层从上往下查。解决方式是第一确保第 3 章那份 Service YAML 里rpc端口的映射存在然后看客户端连接的是不是nacos-service:8848如果客户端走 NodePort 或 LB 方式访问 Nacos还要检查外部端口是否把 9848 一并映射出来了。第二确认 Nacos 的 Pod 环境变量里PREFER_HOST_MODEhostname已经生效这个配置在集群环境里属于“必须配置”而不是“推荐配置”。第三在 Nacos 所在节点上执行ss -lntup | grep 9848验证端口监听正常再用客户端所在 Pod 执行telnet nacos-service 9848验证连通性哪一步断了就查哪一层。6.2 现象Nacos 控制台能打开但配置中心新增配置一直转圈保存失败这个问题的典型特征是Nacos 2.0.3 部署完成后控制台页面能正常登录服务列表也能看到但往配置中心里新增或修改配置时一直显示“发布中”然后失败。排错时看一眼 Nacos Pod 的日志会看到类似[ nacos plugin ] load mysql-plugin error或者 SQL 执行失败的记录。原因基本锁定在 MySQL 连接这一层常见的有三种情况一是nacos_config库的表结构没初始化二是 MySQL 账户权限不足三是SPRING_DATASOURCE_PLATFORM环境变量没设成mysqlNacos 还在试图用内置的 Derby 数据库存储。解决方式是先执行第 3.2 节的建库脚本确认config_info表存在然后在 MySQL 里执行SHOW GRANTS FOR nacos%检查权限。这里还有个容易忽略的细节如果你用的 MySQL 是 8.0 版本注意caching_sha2_password认证插件的兼容性问题在 Nacos 的连接串后加allowPublicKeyRetrievaltrue参数可以规避部分认证失败但这个参数只在特定的客户端驱动版本下有效最稳妥的方案是换用 MySQL 5.7 或者在 MySQL 里把nacos用户的认证插件改成mysql_native_password。6.3 现象Pod 状态一直是 Running但 Service 访问不通curl到前端页面返回 502这类问题在 Ingress 接入后变得尤其常见。表现是前端页面从浏览器访问是 502但登录到集群内部用kubectl exec进入其他 Pod执行curl http://vue2-nginx-service却返回 200。原因是 Ingress Controller 无法访问后端的 Service但 Pod 网络内部是通的。最典型的原因是 Ingress 的backend指向的 Service 端口名不对或者 Ingress 所在的命名空间和后端 Pod 不在同一个命名空间。Ingress 资源默认只在自身命名空间内解析 Service 名如果跨命名空间引用需要额外配置或者使用ExternalName类型的 Service。解决方式是第一步在集群里用kubectl get ingress -n app-prod确认 Ingress 状态看是否有Address字段第二步检查 Ingress 的backend.service.name是否和实际创建的 Service 完全一致大小写、中划线都不能差第三步看 Ingress Controller 的日志如果发现service not found或endpoint not ready基本就是命名空间或端口映射的问题。这个排查流程看着慢但比瞎改 YAML 快得多每次排错按这个顺序走一遍大部分 Ingress 问题五分钟内能定位。6.4 现象Nacos 从 1.x 升到 2.0.3 后客户端配置拉取失败但服务注册正常这题的坑在于 Nacos 2.x 引入了 gRPC 协议但客户端在启动时会先通过 HTTP 端口 8848 拉取配置然后自动升级到 gRPC 长连接。如果你的部署只暴露了 8848 端口配置中心能拉到一次数据但后续的配置变更推送就收不到了表现就是“应用启动时配置是对的改配置后应用不感知”。当初从 1.x 升到 2.0.3 时我很长时间里一直以为是自己代码写错了后来抓包才发现客户端一直在向 9848 端口发起 gRPC 连接而 Service 没有转发这个端口——这就解释了为什么注册正常但配置推送失效。解决方式是用kubectl exec进入应用 Pod对 Nacos 的 Service 名字执行nc -vz nacos-service 9848如果卡住或者连接失败回到 Service YAML 确认rpc端口是否暴露。还有一个隐藏点如果你把 Nacos 部署在集群外比如用云数据库或虚机那还要检查客户端所在的安全组是否放行 9848 的出方向流量。这个端口在 Nacos 文档里写得很清楚但实际部署中却总被漏掉属于看着简单、踩起来很痛的典型问题。最后一个建议也是我现在的习惯每次部署完这套架构后我把所有端口、Service 名、命名空间写到项目的 README 里这看起来是小事但三个月后再回来看你会感谢当时的自己。好了这套 vue2nginxspringboot2.5nacos2.0.3 在阿里云 K8s 上部署的完整链路和最容易出的问题都在这了希望帮到你。本文还有配套的精品资源点击获取