1. 项目概述:为什么要在K8s集群前加一层Nginx做负载均衡反向代理?
最近在帮某高校实验室部署一个面向教学演示的Vue前端系统,后端是多个微服务模块,全部跑在自建的K8s集群上——版本是v1.28,容器运行时用的是Docker,集群管理界面选了Kuboard v3(轻量、国产、中文友好,对新手特别友好)。但上线测试时发现一个问题:用户直接访问K8s Service暴露的NodePort端口,体验很差——URL里带着:30080这种端口号,HTTPS根本没法配;用Ingress又得额外学CRD、搞cert-manager、还要维护ingress-nginx controller本身,对刚接触K8s的运维同学来说,学习曲线太陡。最后我们决定绕开K8s原生Ingress,用一台独立的Nginx服务器做“集群前置网关”:它不进K8s集群,就部署在集群外的物理机或云主机上,负责统一接收HTTPS请求、做SSL卸载、按路径/域名把流量分发到后端不同K8s节点的NodePort服务上。这个方案听起来“土”,但实测下来非常稳:配置清晰、故障隔离好、证书管理完全自主、扩容缩容不影响前端入口,连Kuboard控制台的访问都一并接管了。
核心关键词全在这里了:Nginx负载均衡(不是简单转发,而是带健康检查、权重、会话保持的真实LB)、反向代理K8s节点地址(注意,是代理到K8s Worker节点IP+NodePort,不是Pod IP,也不是ClusterIP)、HTTPS证书方式(用OpenSSL自签CA+签发域名证书,兼顾安全性与可控性)、Kuboard集成(把Kuboard控制台也纳入统一HTTPS入口)、Vue项目镜像打包(前端静态资源如何构建成Docker镜像、如何适配反向代理后的路径)。这不是一个玩具Demo,而是一套可落地、可审计、可交接的生产级边缘网关方案。适合中小团队、教育机构、内部平台建设者——你不需要精通K8s网络模型,只要懂Nginx配置和基础证书原理,就能搭起来。下面我就把从零开始的每一步,包括踩过的坑、调参依据、OpenSSL命令细节、Vue路由history模式适配要点,全都摊开讲清楚。
2. 整体架构设计与技术选型逻辑
2.1 为什么放弃Ingress,坚持用外部Nginx?
这是整个方案最常被问的问题。很多人第一反应是:“K8s不是有Ingress吗?为啥还要自己搞Nginx?”答案很实在:可控性、调试便利性、权限隔离、历史兼容性。我来拆解一下:
可控性:Ingress规则写在YAML里,由ingress-nginx controller动态加载。一旦配置出错,比如正则写崩了、rewrite规则没生效,你得查controller日志、看event、甚至进pod里抓包。而外部Nginx配置是纯文本文件,改完
nginx -t && nginx -s reload立刻生效,tail -f /var/log/nginx/error.log一眼看到报错在哪行。某次线上环境因Ingress注解格式错误导致所有前端404,排查花了47分钟;换成外部Nginx后,同类问题3分钟定位。调试便利性:Nginx自带
stub_status模块,配合nginx-module-vts还能看实时连接数、上游响应时间分布。你可以直接在Nginx机器上用curl -H "Host: demo.example.com" http://127.0.0.1/healthz模拟请求,验证路由是否正确,完全不用碰K8s API。而Ingress调试必须通过kubectl get ingress、kubectl describe ingress、再配合kubectl port-forward打隧道,步骤多、易出错。权限隔离:Kuboard这类管理工具,我们希望只给运维组访问权限,但业务开发组只需要访问自己的Vue应用。如果全走Ingress,就得给开发组分配RBAC权限去操作Ingress资源,安全风险大。外部Nginx配置由运维集中管理,开发只需提供自己的service名称和端口,权限边界清晰。
历史兼容性:该实验室原有几套非容器化老系统(Java Web、PHP),也想统一走HTTPS入口。外部Nginx可以无缝把它们和K8s服务混在一个server块里,用location区分,而Ingress只能代理K8s内的Service。
提示:这不是反对Ingress,而是场景匹配。如果你的集群规模超50节点、需要灰度发布、金丝雀、WAF集成,那Ingress+Traefik或Nginx Ingress Controller仍是首选。但对几十个服务、十几人的小团队,外部Nginx就是“够用、好用、省心”的答案。
2.2 为什么代理到K8s节点IP,而不是用kube-proxy的ClusterIP?
关键点在于:外部Nginx无法直接访问K8s集群内部网络。ClusterIP只在集群内有效,外部机器ping不通。NodePort是唯一能让外部网络直接访问Service的方式——它把Service映射到每个Worker节点的固定端口(默认30000-32767)。所以我们的代理目标是:http://<worker-node-ip>:<nodeport>。
但这里有个陷阱:K8s默认的NodePort是随机分配的,每次删重建Service,端口可能变。我们不能让Nginx配置跟着变。解决方案是固定NodePort值。在Service YAML里明确指定:
apiVersion: v1 kind: Service metadata: name: vue-app-svc spec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30081 # 强制固定为30081,不再随机 selector: app: vue-app这样,无论Service重建多少次,Nginx upstream里写的192.168.10.11:30081永远有效。我们给每个关键服务都分配了固定NodePort:30081给主Vue应用,30082给Kuboard,30083给API网关,30084给监控面板……形成一张清晰的端口地图。
2.3 为什么用OpenSSL自签证书,而不是Let's Encrypt?
Let's Encrypt当然好,免费、自动续期、浏览器信任。但它要求域名能被公网解析且HTTP端口开放(用于ACME挑战),而这个实验室环境是纯内网,DNS走的是本地Bind服务器,没有公网IP。强行搞Let's Encrypt要配DNS插件、开防火墙、暴露内网服务,安全风险反而上升。
OpenSSL自签方案的优势在于:完全离线、全程可控、无第三方依赖、证书生命周期自主。我们用OpenSSL搭建了一个私有CA(Certificate Authority),先签一个根证书(ca.crt),再用这个根证书去签发所有服务的域名证书(demo.example.com.crt)。然后把根证书安装到所有客户端(Windows/macOS/Linux/Chrome/Firefox)的受信任根证书存储区。之后访问https://demo.example.com,浏览器就显示“安全连接”,没有警告。整个过程不联网,证书有效期设为5年,到期前一个月邮件提醒运维手动续签——比自动续期更可靠,因为没人会忘记检查邮件。
注意:自签证书只适用于内网或受控环境。如果你的服务要对外提供,Let's Encrypt或商业证书是唯一选择。本方案明确限定于内网场景,所有安全假设都基于此。
2.4 Kuboard为何能被Nginx代理?它的特殊性在哪?
Kuboard v3的Web界面不是传统单页应用,它依赖WebSocket长连接做实时状态推送(比如Pod日志流、实时监控图表)。这意味着Nginx配置里必须显式开启WebSocket支持,否则Kuboard控制台会卡在“正在连接…”界面。很多初学者只配了proxy_pass,忘了加这两行:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";这三行的作用是:告诉Nginx用HTTP/1.1协议(WebSocket必须),并把客户端的Upgrade: websocket头和Connection: upgrade头透传给后端。缺一不可。我们曾因漏掉proxy_http_version 1.1,导致Kuboard登录后所有按钮点击无响应,查日志发现全是101 Switching Protocols失败。
另外,Kuboard的API路径是/api/...,而前端资源在/。所以Nginx里要分开配置:location /代理静态资源,location /api/代理API,location /ws/(如果有)专门代理WebSocket。这种细粒度控制,是Ingress难以做到的。
3. 核心组件部署与配置详解
3.1 OpenSSL私有CA搭建:从零生成可信根证书
这一步是HTTPS的基础,必须一次做对。我们不推荐用openssl req -x509一条命令生成自签证书,因为那样没有真正的CA层级,后续签发子证书会受限。我们要建一个标准的两级CA结构:根CA(Root CA)→ 服务证书(Server Cert)。
第一步:创建CA工作目录与配置文件
mkdir -p ~/ca/{private,db,certs,newcerts,crl} chmod 700 ~/ca/private touch ~/ca/db/index.txt echo 1000 > ~/ca/db/serial echo 1000 > ~/ca/db/crlnumber # 创建CA配置文件 ca.cnf cat > ~/ca/ca.cnf << 'EOF' [ ca ] default_ca = CA_default [ CA_default ] dir = /home/ubuntu/ca certs = $dir/certs crl_dir = $dir/crl database = $dir/db/index.txt new_certs_dir = $dir/newcerts serial = $dir/db/serial crlnumber = $dir/db/crlnumber certificate = $dir/certs/ca.crt private_key = $dir/private/ca.key RANDFILE = $dir/private/.rand default_days = 1825 default_md = sha256 policy = policy_any x509_extensions = root_ca name_opt = ca_default cert_opt = ca_default crl_extensions = crl_ext default_crl_days = 30 [ policy_any ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional [ root_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = critical, CA:true keyUsage = critical, digitalSignature, cRLSign, keyCertSign [ server_cert ] basicConstraints = critical, CA:false nsCertType = server keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer [ crl_ext ] authorityKeyIdentifier=keyid:always EOF这个配置文件定义了CA的行为:根证书有效期5年(1825天)、使用SHA256哈希、强制要求basicConstraints标识CA身份。server_cert段专为服务证书准备,禁用CA:true,只允许serverAuth用途。
第二步:生成根密钥与根证书
# 生成2048位RSA根密钥(绝对保密!) openssl genrsa -aes256 -out ~/ca/private/ca.key 2048 # 生成根证书(有效期5年,-days 1825) openssl req -x509 -new -nodes -key ~/ca/private/ca.key \ -sha256 -days 1825 -out ~/ca/certs/ca.crt \ -subj "/C=CN/ST=Beijing/L=Beijing/O=Lab CA/CN=Lab Root CA" \ -config ~/ca/ca.cnf执行时会提示输入密码,这是保护根密钥的密码,务必记牢。生成的ca.crt就是根证书,后续要分发给所有客户端安装。
第三步:为域名生成服务证书
假设我们要为demo.example.com签发证书:
# 1. 生成服务私钥 openssl genrsa -out ~/ca/private/demo.example.com.key 2048 # 2. 生成证书签名请求(CSR) openssl req -new -key ~/ca/private/demo.example.com.key \ -out ~/ca/private/demo.example.com.csr \ -subj "/C=CN/ST=Beijing/L=Beijing/O=Lab/CN=demo.example.com" \ -config ~/ca/ca.cnf # 3. 用根CA签发证书(关键!指定server_cert扩展) openssl ca -in ~/ca/private/demo.example.com.csr \ -out ~/ca/certs/demo.example.com.crt \ -cert ~/ca/certs/ca.crt -keyfile ~/ca/private/ca.key \ -extensions server_cert -config ~/ca/ca.cnf \ -days 1825签发完成后,demo.example.com.crt就是服务证书,demo.example.com.key是私钥。把这两个文件拷贝到Nginx服务器的/etc/nginx/ssl/目录下即可。
实操心得:
openssl ca命令必须用-extensions server_cert参数,否则签出来的证书缺少serverAuth扩展,现代浏览器(Chrome 90+)会拒绝建立HTTPS连接,报错ERR_CERT_INVALID。这个坑我们踩了两次,第一次没加参数,证书看着正常,但浏览器死活不认。
3.2 Nginx配置:负载均衡+反向代理+HTTPS卸载
Nginx配置是本方案的核心。我们采用模块化设计:主配置nginx.conf只做全局设置,具体站点配置放在/etc/nginx/conf.d/下的独立文件中,便于维护。
主配置nginx.conf关键片段:
user www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # 日志格式:增加upstream响应时间、状态码 log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time $upstream_status'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log warn; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # 启用gzip压缩,减小Vue静态资源体积 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; include /etc/nginx/conf.d/*.conf; }站点配置/etc/nginx/conf.d/demo.example.com.conf:
upstream vue_backend { # 负载均衡策略:ip_hash保证同一IP始终访问同一节点(解决Vue history路由404) ip_hash; # 定义两个K8s Worker节点,端口为固定NodePort server 192.168.10.11:30081 max_fails=3 fail_timeout=30s; server 192.168.10.12:30081 max_fails=3 fail_timeout=30s; # 健康检查:每5秒发一次HEAD请求,超时1秒,连续2次失败标记为down check interval=5 rise=2 fall=2 timeout=1; } upstream kuboard_backend { # Kuboard需要WebSocket,用least_conn更公平 least_conn; server 192.168.10.11:30082 max_fails=3 fail_timeout=30s; server 192.168.10.12:30082 max_fails=3 fail_timeout=30s; check interval=5 rise=2 fall=2 timeout=1; } server { listen 443 ssl http2; server_name demo.example.com; # SSL证书路径 ssl_certificate /etc/nginx/ssl/demo.example.com.crt; ssl_certificate_key /etc/nginx/ssl/demo.example.com.key; ssl_trusted_certificate /etc/nginx/ssl/ca.crt; # 根证书,用于OCSP stapling # SSL安全加固(参考Mozilla SSL Configuration Generator) ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; # HSTS:强制浏览器后续只用HTTPS访问(内网环境可选) # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Vue应用:root指向Nginx本地,但所有API请求代理到后端 location / { root /var/www/vue-app; index index.html; try_files $uri $uri/ /index.html; # 解决history模式404 } # API接口:代理到K8s Service的NodePort location /api/ { proxy_pass http://vue_backend/; 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; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300; proxy_send_timeout 300; } # Kuboard控制台:单独配置,支持WebSocket location /kuboard/ { proxy_pass http://kuboard_backend/; 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; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300; proxy_send_timeout 300; } # Kuboard WebSocket路径(v3版本用/ws/) location /ws/ { proxy_pass http://kuboard_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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; } } # HTTP重定向到HTTPS server { listen 80; server_name demo.example.com; return 301 https://$server_name$request_uri; }这个配置有几个关键点:
upstream块定义了两个后端组:vue_backend用ip_hash,确保用户刷新页面不会因负载均衡跳到另一个节点导致Vue Router history模式404;kuboard_backend用least_conn,更公平地分发WebSocket连接。check指令启用了Nginx的健康检查模块(需编译时加入--with-http_upstream_check_module,Ubuntu官方包默认不带,我们用的是nginx-extras包)。location /api/和location /kuboard/的proxy_pass末尾都加了/,这是为了正确重写路径。比如/api/users会被代理到http://backend/api/users,而不是http://backend//api/users。try_files $uri $uri/ /index.html是Vue history模式的灵魂,它让Nginx在找不到静态文件时,返回index.html,由Vue Router接管路由,避免404。
3.3 Vue项目镜像打包:Dockerfile编写与Nginx适配
Vue项目不能直接扔进K8s,必须打包成Docker镜像。但很多教程教的是nginx:alpine基础镜像+COPY静态文件,这不够——我们需要让Vue应用知道它运行在反向代理后面,正确生成链接和API前缀。
第一步:修改Vue项目配置(vue.config.js)
module.exports = { // 部署基础路径,必须和Nginx location匹配 publicPath: '/', // 开发服务器代理(仅开发用,不影响生产镜像) devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, } } }, // 生产环境API基础URL,由环境变量注入 configureWebpack: { plugins: [ new webpack.DefinePlugin({ 'process.env.VUE_APP_API_BASE_URL': JSON.stringify(process.env.VUE_APP_API_BASE_URL || '/api/') }) ] } }关键点是publicPath: '/',表示所有静态资源(js/css)都从根路径加载。如果Nginx里location是/app/,这里就要写/app/。
第二步:编写Dockerfile(多阶段构建)
# 构建阶段:用Node.js编译Vue FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build # 运行阶段:用Nginx提供静态服务 FROM nginx:1.25-alpine # 复制Vue构建产物到Nginx默认目录 COPY --from=builder /app/dist/ /usr/share/nginx/html/ # 复制自定义Nginx配置(覆盖默认default.conf) COPY nginx.conf /etc/nginx/conf.d/default.conf # 暴露80端口 EXPOSE 80 # 启动Nginx CMD ["nginx", "-g", "daemon off;"]第三步:构建并推送到镜像仓库
# 构建镜像(tag用git commit id,便于追溯) docker build -t registry.example.com/vue-app:$(git rev-parse --short HEAD) . # 登录私有仓库(假设用Harbor) docker login registry.example.com # 推送 docker push registry.example.com/vue-app:$(git rev-parse --short HEAD)第四步:K8s Deployment YAML
apiVersion: apps/v1 kind: Deployment metadata: name: vue-app spec: replicas: 2 selector: matchLabels: app: vue-app template: metadata: labels: app: vue-app spec: containers: - name: vue-app image: registry.example.com/vue-app:abc1234 ports: - containerPort: 80 resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" --- apiVersion: v1 kind: Service metadata: name: vue-app-svc spec: type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30081 # 固定端口,与Nginx upstream一致 selector: app: vue-app注意事项:Vue镜像里的Nginx只是“容器内服务”,它不处理HTTPS,只负责高效提供静态文件。真正的SSL卸载、负载均衡、路径路由,全部由外部Nginx完成。这样分工明确,容器镜像职责单一,易于测试和替换。
4. 实操全流程与关键参数验证
4.1 环境准备清单与版本确认
在动手前,务必确认所有组件版本兼容。我们实测的组合是:
| 组件 | 版本 | 说明 |
|---|---|---|
| K8s Master | v1.28.2 | 使用kubeadm部署,CNI用Calico v3.26 |
| K8s Worker | v1.28.2 | 两台,IP分别为192.168.10.11和192.168.10.12 |
| Docker | 24.0.6 | 官方CE版,已配置国内镜像源 |
| Kuboard | v3.28.0 | 通过Helm安装,Service类型为NodePort |
| Nginx | 1.25.2 (nginx-extras) | Ubuntu 22.04 apt安装,含upstream_check模块 |
| OpenSSL | 3.0.2 | Ubuntu 22.04默认版本,支持TLS 1.3 |
验证命令:
# 检查K8s节点状态 kubectl get nodes -o wide # 确认Ready状态,IP正确 # 检查NodePort服务是否监听 kubectl get svc -A | grep NodePort # 找到vue-app-svc和kuboard-svc的nodePort # 在Worker节点上验证NodePort是否通 curl -I http://127.0.0.1:30081 # 应返回200 OK curl -I http://127.0.0.1:30082 # Kuboard应返回302重定向到/login # 检查Nginx服务器能否访问Worker节点 curl -I http://192.168.10.11:30081 # 必须通,否则Nginx代理失败 curl -I http://192.168.10.12:30081如果curl不通,90%是防火墙问题。Ubuntu默认用ufw,需放行NodePort端口:
sudo ufw allow 30081 sudo ufw allow 30082 sudo ufw reload4.2 Nginx配置热加载与健康检查验证
配置写完,别急着nginx -s reload,先做语法检查:
sudo nginx -t # 输出应为:nginx: the configuration file /etc/nginx/nginx.conf syntax is ok # nginx: configuration file /etc/nginx/nginx.conf test is successful语法正确后,热加载:
sudo nginx -s reload验证Nginx是否监听443端口:
sudo ss -tlnp | grep :443 # 应看到 nginx: master process /usr/sbin/nginx ...最关键的验证是健康检查是否生效。Nginx的upstream_check模块会把状态写入共享内存,我们可以用curl访问内置状态页(需在nginx.conf的http块里加include /etc/nginx/modules-enabled/10-check.conf;,并确保nginx-extras包已安装):
# 访问Nginx状态页(需配置location,此处略) curl http://127.0.0.1/status # 返回类似: # Upstream Name: vue_backend # Status: UP # Active: 2 # Down: 0 # Check: 100% (100/100)如果看到Status: DOWN,说明健康检查失败。常见原因:
- Worker节点防火墙未开放NodePort
check指令的interval太短,后端来不及响应proxy_pass地址写错,Nginx连不到后端
4.3 HTTPS证书安装与浏览器信任
将之前生成的ca.crt(根证书)安装到客户端:
- Windows:双击
ca.crt→ “安装证书” → “本地计算机” → “受信任的根证书颁发机构” - macOS:双击
ca.crt→ “钥匙串访问” → 拖入“系统”钥匙串 → 右键证书 → “显示简介” → “信任” → “始终信任” - Chrome/Firefox:Chrome用系统证书,Firefox需单独导入(设置 → 隐私与安全 → 安全设备 → 加载)
安装后,访问https://demo.example.com,浏览器地址栏应显示锁图标,点击锁图标 → “连接是安全的” → “证书有效”。
用openssl s_client验证证书链:
openssl s_client -connect demo.example.com:443 -servername demo.example.com -showcerts # 输出中应包含: # -----BEGIN CERTIFICATE----- # (服务证书) # -----END CERTIFICATE----- # -----BEGIN CERTIFICATE----- # (根证书) # -----END CERTIFICATE----- # Verify return code: 0 (ok)Verify return code: 0表示证书链验证通过。如果返回20(unable to get local issuer certificate),说明客户端没装根证书。
4.4 Vue应用路径与API调用实测
部署Vue Pod后,访问https://demo.example.com,打开浏览器开发者工具(F12)→ Network标签页,刷新页面:
- 所有
.js、.css请求的URL应为https://demo.example.com/app.js,状态码200 - 点击一个触发API的按钮,Network里应看到
https://demo.example.com/api/users请求,状态码200,响应数据正确 - 查看Response Headers,
X-Proxy-By头应显示nginx,证明经过了代理
如果API请求404,检查:
- Vue代码里
axios.get('/api/users')是否正确(不要写http://...绝对URL) - Nginx配置中
location /api/的proxy_pass末尾是否有/ - K8s Service的
nodePort是否与Nginx upstream中的一致
如果Vue页面空白,Network里全是404,检查:
vue.config.js的publicPath是否为'/'- Docker镜像里
/usr/share/nginx/html/目录下是否有index.html、js/、css/等文件(docker run -it --rm <image> ls -la /usr/share/nginx/html/)
5. 常见问题与独家排障技巧
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令/方法 | 解决方案 |
|---|---|---|---|
访问https://demo.example.com显示“您的连接不是私密连接” | 根证书未安装到客户端,或证书链不完整 | openssl s_client -connect ... -showcerts看是否返回根证书 | 重新安装ca.crt到客户端,或检查Nginxssl_trusted_certificate路径 |
Nginx日志出现upstream timed out (110: Connection timed out) | Worker节点NodePort未监听,或防火墙拦截 | telnet 192.168.10.11 30081,sudo ufw status | 开放端口,检查K8s Service是否Running |
Vue页面加载后,点击路由链接变成https://demo.example.com/#/about,但地址栏没变 | Vue Router未启用history模式,或Nginxtry_files未生效 | 查看Vue代码router/index.js是否mode: 'history';检查Nginx配置location /块 | 确保vue.config.jspublicPath: '/',Nginxtry_files $uri $uri/ /index.html; |
| Kuboard登录后,左上角一直转圈,“正在连接…” | WebSocket未启用,或proxy_set_header缺失 | 浏览器Network标签页,Filterws,看WebSocket连接状态 | 确认Nginx配置中有proxy_http_version 1.1;、proxy_set_header Upgrade $http_upgrade;、proxy_set_header Connection "upgrade"; |
nginx -t报错unknown directive "check" | Nginx未编译upstream_check_module | `nginx -V 2>&1 | grep -o with-http_upstream_check_module` |
5.2 我踩过的三个深坑与解决方案
坑一:Kuboard v3.28的API路径变更导致404
某次Kuboard升级到v3.28,我们发现/kuboard/路径下所有API请求都404。抓包发现,前端JS发起的请求是/kuboard/api/v3/clusters,但Nginxlocation /kuboard/代理到后端后,变成了/kuboard/kuboard/api/v3/clusters,多了一层/kuboard。原因是Kuboard v3.28的前端硬编码了/kuboard前缀,而我们的Nginx配置是proxy_pass http://kuboard_backend/;(末尾有/),导致路径重写错误。
解决方案:去掉proxy_pass末尾的/,并用rewrite指令修正:
location /kuboard/ { rewrite ^/kuboard/(.*)$ /$1 break; proxy_pass http://kuboard_backend; # ... 其他proxy_set_header }这样,/kuboard/api/v3/...被重写为/api/v3/...,再代理到后端,完美匹配。
**坑二:Vue history模式