Web架构解剖学:从 Vue 到 Spring Boot 再到 K8s 的全链路深度解析
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。
此时,你的架构图是这样的:
幻象:
-
你以为浏览器直接运行了你的
.vue文件。 -
你以为前后端是直接通信的(通过你在
vite.config.js里配置的proxy)。 -
你以为不需要额外的服务器软件。
真相:
-
前端: 浏览器根本不认识
.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),会面临两个大问题:
-
CORS 跨域:浏览器的同源策略会拦截请求。
-
暴露内网:你不希望把后端服务器真实的 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?
假设我们没有使用 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 的作用。
一定要区分两个概念:
-
Ingress (资源对象):这是一张“规则表”(YAML 文件)。它定义了:“如果用户访问
a.com,转给 Service A;如果访问b.com/api,转给 Service B”。 -
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 集群部署,前后端分离。
-
外部网络:用户访问
https://www.example.com。 -
L4 负载均衡 (Cloud LB):阿里云/AWS 的 SLB,将流量分发给 K8s 集群的入口。
-
Ingress Controller (Nginx):
-
部署在 K8s 边缘。
-
接收流量,解密 HTTPS。
-
根据域名/路径规则进行路由。
-
-
前端路由分支:
-
如果是访问静态页面(JS/CSS)。
-
Ingress 转发给 Frontend Service。
-
Frontend Service 指向运行 Nginx 的 Pod(专门用来托管
dist文件)。
-
-
后端路由分支:
-
如果是访问
/api/xxxx。 -
Ingress 转发给 Backend Service。
-
Backend Service 负载均衡到具体的 Spring Boot Pod。
-
第七章:关键总结与思考
通过这次梳理,我们应该纠正以下几个常见的认知误区:
-
代码与设施分离:
Nginx、Ingress 不是代码里的逻辑,而是承载代码的容器和管道。你写的代码(Vue/Java)是货物,Nginx/K8s 是货船和航线。 -
Nginx 无处不在:
-
它可以作为Web服务器(托管前端静态文件)。
-
它可以作为反向代理(解决跨域、隐藏内网)。
-
它可以作为Ingress Controller(K8s 的流量网关)。
-
它以不同的形态,出现在架构的不同层级。
-
-
Vue 的编译本质:
理解 Vue 只是源码,浏览器只认 HTML/JS,通过 Nginx 托管 Dist 目录,是理解前端部署的基石。 -
K8s 的抽象层:
理解 Ingress -> Service -> Pod 这三层抽象,就理解了云原生网络的核心。Service 屏蔽了 Pod 的易变性,Ingress 屏蔽了 Service 的内网属性。
希望这篇文章能帮你打通从“写代码”到“看架构”的任督二脉!
更多推荐
所有评论(0)