饮墨

子安饮墨馀三斗,留与卿儿作赋来

3 步用 Cloudflare Tunnel 安全暴露内网服务:零公网 IP、零开端口,替代传统反向代理

痛点:暴露内网服务的老路越走越窄

运维团队经常面对一个需求:把内网的 Grafana、Jenkins、内部 API 安全暴露给远程团队访问。

传统方案:

方案 问题
公网 IP + Nginx 反代 需要开端口、配 TLS、防 DDoS,攻击面大
VPN(WireGuard/OpenVPN) 客户端配置复杂,移动端体验差,排错成本高
SSH 隧道 临时可以,生产不稳定,断线无自动恢复
frp/ngrok 依赖中转服务器,带宽受限,安全性参差

核心矛盾:想让特定人访问内网服务,又不想在防火墙上开任何入站端口。

Cloudflare Tunnel(原 Argo Tunnel)正好解决这个问题——服务器只需出站连接到 Cloudflare 边缘网络,无需公网 IP、无需开端口,流量通过 Cloudflare 全球网络加速并做安全防护。

方案:Cloudflare Tunnel + Zero Trust Access

架构原理:

用户浏览器 → Cloudflare Edge (TLS终止 + WAF + Access策略)
                        ↕ (出站长连接,无需入站)
              内网服务器 cloudflared → localhost:3000 (Grafana)

关键特性: - 零入站端口:cloudflared 进程从内网主动建立出站连接 - 自动 TLS:Cloudflare 边缘处理证书,内网服务可以跑 HTTP - Zero Trust Access:支持 Google/GitHub/OIDC 认证,细粒度控制谁能访问 - 内置负载均衡:多个 cloudflared 实例自动 HA

实操步骤

第 1 步:安装 cloudflared 并认证

# Debian/Ubuntu
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install -y cloudflared

# 登录认证(会打开浏览器授权)
cloudflared tunnel login

认证完成后,凭证存储在 ~/.cloudflared/cert.pem

第 2 步:创建 Tunnel 并配置路由

# 创建隧道
cloudflared tunnel create ops-internal
# 输出:Created tunnel ops-internal with id a]b1c2d3-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# 查看隧道 ID
cloudflared tunnel list

创建配置文件 ~/.cloudflared/config.yml

tunnel: ab1c2d3-xxxx-xxxx-xxxx-xxxxxxxxxxxx
credentials-file: /root/.cloudflared/ab1c2d3-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json

ingress:
  - hostname: grafana.yourdomain.com
    service: http://localhost:3000
  - hostname: jenkins.yourdomain.com
    service: http://localhost:8080
  - hostname: api-internal.yourdomain.com
    service: http://localhost:8000
  # 兜底规则(必须有)
  - service: http_status:404

配置 DNS 路由:

cloudflared tunnel route dns ops-internal grafana.yourdomain.com
cloudflared tunnel route dns ops-internal jenkins.yourdomain.com

第 3 步:以 systemd 服务运行并验证

# 安装为系统服务
sudo cloudflared service install

# 启动并设置开机自启
sudo systemctl enable --now cloudflared

# 验证状态
sudo systemctl status cloudflared
cloudflared tunnel info ops-internal

验证访问:

# 从外部机器测试
curl -I https://grafana.yourdomain.com
# 应返回 200,且 Server 头显示 cloudflare

加分项:配置 Zero Trust Access 策略

在 Cloudflare Zero Trust 控制台 → Access → Applications 中:

  1. 添加 Application,类型 Self-hosted
  2. 设置 Application domain 为 grafana.yourdomain.com
  3. 添加 Policy:仅允许公司邮箱域名(如 @yourcompany.com)通过 Google OAuth 认证访问
  4. 启用后,未认证用户访问该域名会跳转到登录页

避坑指南

坑 1:config.yml 的 ingress 缺少兜底规则

# 错误 — cloudflared 启动报错
ingress:
  - hostname: grafana.yourdomain.com
    service: http://localhost:3000
# 缺少兜底!

# 正确 — 最后一条不带 hostname
ingress:
  - hostname: grafana.yourdomain.com
    service: http://localhost:3000
  - service: http_status:404

cloudflared 强制要求最后一条 ingress 规则不带 hostname 作为兜底,否则启动直接报 The last ingress rule must match all hostnames

坑 2:内网服务绑定了 127.0.0.1 但 cloudflared 跑在 Docker 里

如果 cloudflared 运行在容器中,localhost 指向容器自身网络。解决方案:

# 方案 A:cloudflared 容器用 host 网络
docker run --network host cloudflare/cloudflared:latest tunnel run

# 方案 B:service 地址改为宿主机 IP
ingress:
  - hostname: grafana.yourdomain.com
    service: http://172.17.0.1:3000  # Docker 默认网桥的宿主机 IP

坑 3:Tunnel 断线后 DNS 记录残留

删除 Tunnel 前必须先清理 DNS:

# 先删 DNS 记录
cloudflared tunnel route dns --delete ops-internal grafana.yourdomain.com

# 再删 Tunnel(强制删除会留下 CNAME 残留)
cloudflared tunnel delete ops-internal

不清理的话,域名会一直 CNAME 到已失效的 Tunnel ID,返回 1033 错误。

总结

对比维度 传统 Nginx 反代 Cloudflare Tunnel
公网 IP 必须 不需要
防火墙入站规则 需开 80/443 零入站端口
TLS 证书 自己管(Let's Encrypt) Cloudflare 自动管理
DDoS 防护 需另外部署 内置
认证鉴权 自建(OAuth/BasicAuth) Zero Trust Access 内置
高可用 需自建多节点 + LB 多 cloudflared 实例自动 HA

适用场景:内网监控面板、CI/CD 系统、内部 API、开发/测试环境远程访问。

不适合:对延迟极敏感的实时交易系统(多一跳 Cloudflare 边缘),以及需要非 HTTP/TCP 协议的场景(Tunnel 支持 TCP/UDP,但配置更复杂)。

一句话总结:如果你还在为暴露内网服务而头疼防火墙规则和 TLS 证书,Cloudflare Tunnel 是目前最省心的零信任方案——装一个 cloudflared、写一个 YAML,内网服务即可安全上线。

您还没有登录,请登录后发表评论。