Web架构解剖学:从 Vue 到 Spring Boot 再到 K8s 的全链路深度解析

前言

你是否也有过这样的困惑:
“我在本地写代码时,npm run dev 就能跑起来,后端 Application.java 一点就启动了,为什么到了生产环境,就要多出来什么 Nginx、Ingress、Service?它们到底在哪?是写在代码里的吗?”

这是一个从“程序员”进阶到“架构师”必经的认知门槛。

本文将以一种“庖丁解牛”的方式,拆解一个标准的现代 Web 前后端分离架构。我们将穿越代码的表象,深入到服务器、网络和容器编排的骨架中,去理解每一个组件存在的意义。

第一章:幻象与真相 —— 开发环境 vs 生产环境

要理解 Nginx 和 K8s 的位置,首先要打破开发环境给你的“幻象”。

1.1 开发环境的“保姆服务”

当你使用 VS Code 开发 Vue 项目时,你运行了 npm run dev
当你使用 IDEA 开发 Spring Boot 时,你点击了 Run
此时,你的架构图是这样的:

你的电脑 (Localhost)
localhost:3000
HMR热更新
localhost:8080
Node.js Dev Server (Vite/Webpack)
Vue 源码文件 (.vue)
内嵌 Tomcat (Spring Boot)
开发者浏览器

幻象:

  1. 你以为浏览器直接运行了你的 .vue 文件。

  2. 你以为前后端是直接通信的(通过你在 vite.config.js 里配置的 proxy)。

  3. 你以为不需要额外的服务器软件。

真相:

  • 前端: 浏览器根本不认识 .vue 文件。是 Vite/Webpack 启动了一个 Node.js 服务,它实时地把你写的代码“编译”成了内存中的 JS 代码发给浏览器。这个 Node.js 服务纯粹是为了开发便利(热更新、报错提示)存在的,它的性能极差,绝对不能用于生产环境

  • 后端: Spring Boot 内置了一个微型的 Tomcat 容器,方便你一键启动。但在生产环境中,裸奔的 Tomcat 极其脆弱。

1.2 生产环境的“残酷世界”

当你准备上线时,第一件事就是执行 npm run build
这个命令会把你的 .vue.ts.less 全部“杀死”,经过编译、压缩、混淆,最后变成一个 dist 文件夹

在这个文件夹里,只有三种浏览器能看懂的东西:

  • .html (骨架)

  • .css (皮肤)

  • .js (肌肉/逻辑)

这就引出了第一个核心问题:谁来负责把这些静态文件“发”给用户?
Node.js 开发服务器已经退场了。这时候,我们需要一个专业的“静态资源服务器”。
Nginx,就是这个角色的不二之选。

第二章:前端的守护者 —— Nginx 在哪?

2.1 Nginx 的物理位置

这是很多初学者最容易混淆的点:Nginx 不是你工程代码的一部分(依赖包),它是服务器操作系统的一部分(基础设施)。

  • Vue 工程:这是你的“画稿”。

  • Dist 目录:这是画好的“成品画”。

  • Nginx:这是挂画的“画廊”。

你不能把画廊塞进画里。你需要在一台 Linux 服务器上安装 Nginx 软件(就像安装 Word 或 Chrome 一样)。

2.2 前端 Nginx 的核心职责

在非 K8s 的传统架构中,Nginx 通常部署在对外的一台服务器上(或者和后端在同一台)。它的配置 (nginx.conf) 决定了用户如何访问你的 Vue 应用。

职责一:静态资源托管 (Web Server)

当用户访问 www.example.com 时,Nginx 负责读取磁盘上的 dist 目录,并返回 index.html

# /etc/nginx/nginx.conf 的片段

server {
    listen       80;
    server_name  [www.example.com](https://www.example.com);

    # 核心配置:静态文件在哪里?
    location / {
        root   /usr/share/nginx/html/dist; # 你的 npm run build 产物放这里
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;  # 这一行是 SPA 的命门,后面细说
    }
}
职责二:解决 SPA 路由的“404 诅咒”

现代前端框架(Vue/React)大多是 SPA (单页应用)
这意味着,整个网站只有一个 index.html。即使你的 URL 变成了 www.example.com/user/profile,浏览器其实并没有去向服务器请求 user/profile 这个文件(因为根本不存在)。是 Vue Router 在浏览器端通过 JavaScript 修改了地址栏,并骗过了眼睛。

但是,如果你在 /user/profile 页面按下了 F5 刷新,会发生什么?
浏览器会老老实实地向 Nginx 发送一个 GET /user/profile 的请求。
Nginx 去 dist 文件夹一看:“我只有 index.html,没有叫 user/profile 的文件啊?”
于是 Nginx 返回 404 Not Found

为了解决这个问题,Nginx 必须配置兜底策略:
try_files $uri $uri/ /index.html;
这句话的意思是:“如果在磁盘上找不到对应的文件,别报错,统统把 index.html 返回给浏览器,剩下的事交给 Vue 自己去处理。”

第三章:跨越鸿沟 —— 反向代理与后端连接

现在页面加载出来了,用户点击了“登录”。Vue 发出了一个请求:/api/login

3.1 为什么需要反向代理?

如果 Vue 代码直接请求后端 IP(例如 http://192.168.1.50:8080/login),会面临两个大问题:

  1. CORS 跨域:浏览器的同源策略会拦截请求。

  2. 暴露内网:你不希望把后端服务器真实的 IP 和端口暴露给公网。

3.2 Nginx 的“变身”

此时,前端 Nginx 摇身一变,从“文件管理员”变成了“中转站(反向代理)”。

我们需要在 Nginx 配置里加一段:

server {
    listen 80;
    server_name [www.example.com](https://www.example.com);

    # 前端静态资源
    location / {
        root /app/dist;
        try_files $uri $uri/ /index.html;
    }

    # 后端接口转发
    location /api/ {
        # 重点来了:Nginx 帮你去请求后端
        proxy_pass [http://127.0.0.1:8080/](http://127.0.0.1:8080/); 
        
        # 把用户的真实 IP 告诉后端,否则后端只能看到 Nginx 的 IP
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

架构图变化:

用户浏览器 前端 Nginx (80端口) Spring Boot (8080端口) 阶段1:加载页面 GET /index.html 返回 index.html (Vue App) 阶段2:数据交互 GET /api/user/1 (看不见后端IP) 匹配到 location /api/ GET /user/1 (内网转发) JSON 数据 JSON 数据 用户浏览器 前端 Nginx (80端口) Spring Boot (8080端口)

第四章:后端的守门员 —— 为什么后端也需要 Nginx?

假设我们没有使用 K8s,而是传统的虚拟机部署。你的 Spring Boot 应用直接跑在 8080 端口。
你可能会问:“前端 Nginx 已经做了一次转发了,为什么有时候后端服务器上还要再装一个 Nginx?”

或者说,如果前端和后端不在同一台机器上,后端的架构通常是:
公网 -> 负载均衡器(LB) -> 后端 Nginx -> Spring Boot 集群

后端 Nginx 的存在有三大理由:

4.1 动静分离与性能卸载

Spring Boot(Tomcat)是 Java 写的,它的强项是逻辑计算(CPU 密集型),处理并发连接的能力(IO 密集型)远不如 Nginx(C 语言编写,基于事件驱动)。
让 Tomcat 去处理 HTTP 握手、SSL 加密解密、Gzip 压缩,简直是暴殄天物。
最佳实践: Nginx 在前面处理 HTTPS 握手,解密成 HTTP 后再传给 Tomcat,让 Java 专注于业务。

4.2 负载均衡 (Load Balancing)

生产环境通常不会只起一个 Spring Boot 实例。如果起了 3 个实例(Port 8080, 8081, 8082),谁来分配流量?
Nginx 配置 upstream

upstream my_java_app {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://my_java_app; # 自动轮询分配
    }
}

第五章:容器化的新世界 —— Kubernetes (K8s)

这一章是很多人的知识盲区。当架构迁移到 K8s 后,Nginx 似乎“消失”了,取而代之的是 Pod, Service, Ingress。

让我们重新映射这些概念。

5.1 Pod:不稳定的原子

你的 Spring Boot 应用被打包成了 Docker 镜像,在 K8s 里运行的实例叫 Pod
核心特征: Pod 是“用完即弃”的。每次发布新版本、或者机器宕机,旧 Pod 会死掉,新 Pod 会产生,IP 地址也会随之改变

如果 Nginx 把 proxy_pass 写死成 Pod 的 IP,那系统每分钟都会挂一次。

5.2 Service:永不失联的电话号码

为了解决 Pod IP 变动的问题,K8s 发明了 Service
Service 分配了一个 ClusterIP(比如 10.96.0.1),这个 IP 在 Service 的生命周期内是永远不会变的。

  • 逻辑: 你只要往 Service 发请求,K8s 内部的 kube-proxy 机制会自动把流量转发给当前活着的、健康的 Pod。

  • 比喻: Service 是公司的“总机号码”,Pod 是具体的“客服员工”。员工可以离职换人,但总机号码不用变。

5.3 Ingress:K8s 的“大门”

现在我们有了 Service,但 Service 的 IP 只是集群内部的 IP(内网),外网怎么访问进来呢?
这就是 Ingress 的作用。

一定要区分两个概念:

  1. Ingress (资源对象):这是一张“规则表”(YAML 文件)。它定义了:“如果用户访问 a.com,转给 Service A;如果访问 b.com/api,转给 Service B”。

  2. Ingress Controller (控制器):这是真正干活的“程序”。它本质上通常就是 Nginx!(最常用的是 ingress-nginx)。

当你部署 Ingress Controller 时,K8s 会启动几个特殊的 Pod,里面运行着 Nginx。
当你编写 Ingress YAML 规则时:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
spec:
  rules:
  - host: [www.example.com](https://www.example.com)
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: springboot-service # 转发给 Service
            port: 
              number: 80

神奇的事情发生了:
Ingress Controller 会实时监听这些 YAML 的变化,并自动生成 nginx.conf,然后重载 Nginx 进程。

所以,Ingress 下面不仅有 Nginx,它本质上就是 Nginx 的“自动化配置生成器”。

第六章:全链路大图 —— 一个请求的完整一生

让我们把所有碎片拼在一起,画出终极架构图。

场景: K8s 集群部署,前后端分离。

  1. 外部网络:用户访问 https://www.example.com

  2. L4 负载均衡 (Cloud LB):阿里云/AWS 的 SLB,将流量分发给 K8s 集群的入口。

  3. Ingress Controller (Nginx)

    • 部署在 K8s 边缘。

    • 接收流量,解密 HTTPS。

    • 根据域名/路径规则进行路由。

  4. 前端路由分支

    • 如果是访问静态页面(JS/CSS)。

    • Ingress 转发给 Frontend Service

    • Frontend Service 指向运行 Nginx 的 Pod(专门用来托管 dist 文件)。

  5. 后端路由分支

    • 如果是访问 /api/xxxx

    • Ingress 转发给 Backend Service

    • Backend Service 负载均衡到具体的 Spring Boot Pod

Kubernetes Cluster
Frontend Pod
Backend Pod
HTTPS Request
TCP
路由规则: /
路由规则: /api
负载均衡
负载均衡
Frontend Service
K8s Ingress Controller (本质是 Nginx)
Backend Service
Frontend Pod (Nginx 容器)
Backend Pod (Spring Boot 容器)
Spring Boot 进程
Nginx 进程
Dist 静态文件
用户客户端
云厂商负载均衡 (SLB/ELB)

第七章:关键总结与思考

通过这次梳理,我们应该纠正以下几个常见的认知误区:

  1. 代码与设施分离
    Nginx、Ingress 不是代码里的逻辑,而是承载代码的容器和管道。你写的代码(Vue/Java)是货物,Nginx/K8s 是货船和航线。

  2. Nginx 无处不在

    • 它可以作为Web服务器(托管前端静态文件)。

    • 它可以作为反向代理(解决跨域、隐藏内网)。

    • 它可以作为Ingress Controller(K8s 的流量网关)。

    • 它以不同的形态,出现在架构的不同层级。

  3. Vue 的编译本质
    理解 Vue 只是源码,浏览器只认 HTML/JS,通过 Nginx 托管 Dist 目录,是理解前端部署的基石。

  4. K8s 的抽象层
    理解 Ingress -> Service -> Pod 这三层抽象,就理解了云原生网络的核心。Service 屏蔽了 Pod 的易变性,Ingress 屏蔽了 Service 的内网属性。

希望这篇文章能帮你打通从“写代码”到“看架构”的任督二脉!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐