前言
最近手里有一个部署在校园网内部 K8S 集群上的项目(Satellite Micro)。环境很典型:服务器有教育网 IP(223.2.x.x),出站流量虽然有些限制但基本能通,但入站流量被学校防火墙像铁桶一样挡在外面。
为了让外网(或者说在北京的领导们)能访问到 K8S 里的前端和 API,我开始了一场内网穿透的折腾之旅。
第一阶段:尝试 Cloudflare Tunnel(以失败告终)
最开始我想用 Cloudflare Tunnel (Zero Trust),毕竟它是云原生方案,不需要自己买 VPS,而且配置起来显得很“现代化”。
1. 部署尝试
我用的是Cloudflare官网的Zero Trust,需要在网络里新建连接器,也即tunnel,另在 K8S 里部署了 cloudflared 的 Deployment,配置了 Token。理论上,它应该主动向 Cloudflare 边缘节点发起连接,建立隧道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| apiVersion: apps/v1 kind: Deployment metadata: name: cloudflare namespace: satellite-micro labels: app: cloudflare spec: replicas: 1 selector: matchLabels: app: cloudflare template: metadata: labels: app: cloudflare spec: containers: - name: cloudflare image: 223.2.44.251/satellite/cloudflare:latest imagePullPolicy: IfNotPresent args: - tunnel - --no-autoupdate - run - --protocol - http2 - --token - eyJhIjoiNTRiYWRhYTRkYjUxZmNlYTJiNDlmYjgxZDM0MWNkM2QiLCJ0IjoiYmZhNDk3OWYtOWE1OC00YzBhLTgyNjAtYTgyYWYyNjY3Y2Q5IiwicyI6Ik4yTTVNRE5oT1RZdFlURTRZUzAwWkdOa0xXSmlORGd0TjJZMFpqVXpaRGMwWlRobCJ9 env: - name: TUNNEL_PROTOCOL value: http2 resources: requests: cpu: 10m memory: 64Mi limits: cpu: 500m memory: 256Mi restartPolicy: Always
|
2. 遇到的坑
Pod 启动后,日志直接红了一片:
UDP 被拦: 起初报错 failed to dial to edge with quic。这是因为 Cloudflare 默认使用 QUIC (基于 UDP) 协议,而校园网防火墙对 UDP 并不友好。
改用 TCP 依然跪: 我通过环境变量 TUNNEL_PROTOCOL: http2 强制它走 TCP。结果报错变成了 dial tcp 198.41.200.193:7844: i/o timeout。
3. 放弃原因
经过排查发现,Cloudflare Tunnel 建立连接需要访问边缘节点的 7844 端口。然而,严苛的教育网防火墙似乎只放行了标准的 80/443 端口,7844 这种非标端口直接被掐断了。
虽然心有不甘,但“网络层物理封锁”难以逾越,遂放弃 CF 方案。
第二阶段:回归 FRP —— 端口伪装与全链路打通
既然免费的午餐吃不到,还是得靠自己。我有一台阿里云的 VPS(114.55.x.x),决定用 FRP 方案。为了骗过校园网防火墙,核心思路是:把 FRP 流量伪装成普通的 HTTPS 流量(走 443 端口)。
1. VPS 服务端配置 (frps)
为了防止被识别为非 HTTP 流量导致连接重置(EOF),我在服务端强制开启了 TLS。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
bindPort = 443
auth.token = "OpenGMS123456" transport.tls.force = true
webServer.addr = "0.0.0.0" webServer.port = 7500 webServer.user = "admin" webServer.password = "admin"
|
2. K8S 客户端配置 (frpc)
在 K8S 中,我通过 ConfigMap 挂载配置,注意 TOML 格式的坑,全局配置必须写在 [[proxies]] 上面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| apiVersion: v1 kind: ConfigMap metadata: name: frpc-config namespace: satellite-micro data: frpc.toml: | # ============================== # 连接 VPS 的配置 # ============================== serverAddr = "114.55.142.175" # 【修改这里】你的 VPS 公网 IP serverPort = 443 # 必须对应 frps.toml 里的 bindPort (443) auth.token = "OpenGMS123456" # 【修改这里】必须和 frps.toml 里的 token 一致 transport.tls.enable = true # ============================== # 穿透规则:卫星前端服务 # ============================== [[proxies]] name = "satellite-frontend" type = "tcp" # K8S 内部 DNS 地址 (服务名.命名空间.svc) localIP = "satellite-front-v2.satellite-micro.svc" # 你的 Service 端口 localPort = 5173 # 【关键】外网 VPS 监听端口 remotePort = 5173 --- apiVersion: apps/v1 kind: Deployment metadata: name: frpc-satellite namespace: satellite-micro labels: app: frpc-satellite spec: replicas: 1 selector: matchLabels: app: frpc-satellite template: metadata: labels: app: frpc-satellite spec: containers: - name: frpc image: 223.2.44.251/satellite/frpc:latest resources: limits: cpu: 200m memory: 128Mi volumeMounts: - name: config mountPath: /etc/frp/frpc.toml subPath: frpc.toml volumes: - name: config configMap: name: frpc-config
|
3. 一个小插曲:神奇的 i/o timeout
配置好后,日志一度报 i/o timeout。查了半天防火墙和安全组,最后发现竟然是因为K8S 宿主机的校园网登录认证过期了……
1
| curl -k -c /tmp/school_cookies.txt -b /tmp/school_cookies.txt "http://172.28.255.156:801/eportal/portal/login?callback=dr1003&login_method=1&user_account=%2C0%2C学号&user_password=密码" -v -L -D /tmp/login_headers.txt -o /tmp/login_body.txt
|
在宿主机上写个脚本 curl 模拟登录后,FRP 瞬间显示 start proxy success。此时,我已经可以通过 http://114.55.142.175:5173 访问到前端页面了。
第三阶段:解决 403 Forbidden —— 后端代码与 Nginx 的博弈
原本以为网络通了就万事大吉,结果登录时前端报错:
Status Code: 403 Forbidden Request URL: http://114.55.142.175:5173/api/user/login
奇怪的是,如果我用内网 IP (223.2.x.x) 访问,一切正常。
1. 原理分析:Spring Boot 的 CORS 校验
看了后端 Java 代码才恍然大悟。后端配置了 Spring Security 的 CORS 策略:
1 2 3 4 5 6 7 8
| registry.addMapping("/**") .allowedOriginPatterns( "http://localhost:*", "http://223.*", "http://192.*" );
|
案情还原:
当我通过 FRP 访问时,浏览器发出的 HTTP 请求头中,Origin 是 http://114.55.142.175:5173。
Nginx 默认透传了这个 Header 给后端。
后端保安一看:“114 开头的?不在白名单里,拒绝!”
2. 解决方案:Nginx “偷渡”法
为了解决这个问题,我有两个选择:要么改 Java 代码重新打包镜像(太慢),要么改前端 Nginx 配置(快)。
我选择了后者。利用 Nginx 修改请求头,把 Origin 抹除,让后端误以为这是一个“同源请求”或“非跨域请求”。
修改前端 Nginx 的 location /api/ 配置:
1 2 3 4 5 6 7
| location /api/ { proxy_pass http://20.1.59.49:8999/api/v1/; proxy_set_header Origin ""; proxy_set_header Referer ""; }
|
3. 结果
配置生效后,Nginx 在转发请求前去掉了 Origin 头。Spring Boot 收到请求后,发现没有 Origin 信息,默认跳过了 CORS 检查机制。
刷新浏览器,登录成功,接口返回 200 OK!
总结
这次折腾让我深刻理解了网络链路的每一环:
物理层/防火墙层: 校园网环境下,标准端口(443)+ TLS 加密是穿透防火墙的最稳方案,UDP 和非标端口基本没戏。
网络层: FRP 在 K8S 里配合 ConfigMap 使用非常灵活,但要注意宿主机的网络连通性(别忘了登录校园网账号!)。
应用层: 网络通了不代表应用能用。同源策略和后端白名单是跨域访问的两大拦路虎。通过 Nginx 清洗 Header 是一种非常实用的运维级规避手段。
希望这篇踩坑记录能帮到同样在校园网里挣扎的 K8S 玩家们。