Featured image of post 以狼的提问箱为例-免备案双 CDN 分流实战教程:境内走 EdgeOne、境外走 Cloudflare

以狼的提问箱为例-免备案双 CDN 分流实战教程:境内走 EdgeOne、境外走 Cloudflare

阅读次数:

教程性质: 本文以 狼的提问箱 项目为例,但所有原理与步骤对任何「不备案、有海外用户、想让境内/境外各走最优线路」的站点通用。

场景速览:域名权威 DNS 托管在 DNSPod(免费版支持按「境内 / 境外 / 默认」线路返回不同解析结果);境内线路 CNAME 到腾讯 EdgeOne 国际站(免备案区域,大陆用户落到香港等最近节点,走腾讯跨境骨干,比 Cloudflare 免费版大陆绕行明显更快);境外/默认线路 CNAME 到 Cloudflare 优选 IP(经 Cloudflare for SaaS 自定义主机名识别流量,由既有 Cloudflare Tunnel 回源)。


1. 为什么能这么搭 —— 三个关键认知

整套方案成立的前提,是先把三个「概念解耦」想清楚:

1.1 Cloudflare for SaaS:让「流量识别」与「DNS 解析权」解耦

Cloudflare 普通接入(Free 套餐)要求把整域 NS 迁到 Cloudflare,这与「DNS 要放在 DNSPod 做分线路」天然冲突——NS 在谁手里,谁才说了算。

Cloudflare for SaaS(免费额度 100 个自定义主机名)绕开这个限制:

  • 你有一个承载 zone(例如 cf-zone.example.net,托管在 Cloudflare);
  • 在承载 zone 里把目标域名(box.example.com)加为 Custom Hostname,并验证归属;
  • Cloudflare 边缘从此「认识」这个域名:任何带着 Host: box.example.com 到达 CF 边缘的请求,都会被匹配证书、按配置路由(本项目为 Tunnel 回源)。

关键结论:CF 侧只负责"来了流量怎么认、怎么转",不负责"流量怎么来"。流量能不能到 CF 边缘,取决于你自己的 DNS(DNSPod)把境外用户解析到哪。所以「境外走 CF」必须在 DNSPod 配一条境外线路指向 CF 的 IP/CNAME——CF 自己不会拉客。

1.2 DNSPod 分线路:免费就能「同一域名,不同答案」

腾讯云解析(DNSPod)免费版就支持按查询来源返回不同记录:线路可选「中国境内 / 境外 / 默认」,境内细分到省、运营商。默认线路是兜底,任何未命中的查询都走它。

要理解一个易错点:分线路的判定发生在 DNSPod 的权威服务器上,依据是「递归 DNS 服务器的出口 IP 归属地」——所以「境外用户走境外线路」成立的前提是境外用户的递归 DNS(8.8.8.8 等)能真的问到 DNSPod 权威,且没被中间网络劫持/污染(第 6 节有实战案例)。

1.3 EdgeOne 国际站:选对「加速区域」就不用备案

EdgeOne 分「中国大陆可用区 / 全球可用区(不含中国大陆)/ 海外可用区」等区域配置。官方口径:

  • 全球可用区(不含中国大陆) 或海外区域 → 不要求 ICP 备案
  • 选中国大陆区域 → 必须备案。

不备案的代价:大陆用户没有本地节点,请求落到最近的海外节点(大陆一般落香港,也有日韩/新加坡等),由腾讯跨境骨干回源。实测体验通常仍明显优于 Cloudflare 免费版大陆线路(后者常绕美/日且抖动大)——所以「不备案也选 EO 当境内线路」是有实测前提的,切换前建议先测速对比。

补充:Cloudflare for SaaS 启用通常需要账户先绑定支付方式,额度内不扣费。


2. 目标架构

        DNS 层   DNSPod(example.com 整域权威,免费版支持按线路返回)
                NS = byron.dnspod.net / sunshine.dnspod.net(官方 NS)
                box CNAME 记录
                   ├─ 境内  ──▶ box.example.com.edgeone.example   (EdgeOne 接入目标)
                   ├─ 境外  ──▶ cf-pref.example.net               (CF 优选 IP 域名)
                   └─ 默认  ──▶ cf-pref.example.net               (兜底,语义同境外)
   ┌──────────────┴──────────────────────────────┐
大陆用户 ▶▶(命中境内线路)        ◀◀ 海外用户(命中境外/默认线路)
   ▼                                             ▼
EdgeOne 国际站                            Cloudflare 边缘
区域: 全球(不含中国大陆)                  优选 IP: 198.51.100.x(示例段)
证书: TrustAsia, CN=box.example.com      识别: for SaaS 自定义主机名
回源: EO 直连源站公网端口                 回源: Cloudflare Tunnel(纯出站)
   │                                             │
   └──────────────────┬──────────────────────────┘
  源站防火墙 iptables+ipset(仅放行 EO 回源网段;cloudflared 走 127.0.0.1 不受影响)
  HTTP 应用直接监听公网高位端口(本项目 TamaBox,无 nginx)

两条回源路径互不干扰:EO 是 入站直连(需开放端口 + 白名单锁定),CF 是 Tunnel 出站(无需任何入站规则)。


3. 前置条件清单

#条件说明
1一个域名(example.com),权威 DNS 托管在 DNSPod免费版即可;注册局委派的 NS = byron/sunshine.dnspod.net
2一个 Cloudflare 账户 + 承载 zone(cf-zone.example.net用于 for SaaS 与 Tunnel;域名本身的 DNS 不用迁给 CF
3cloudflared Tunnel 已在跑服务器 → CF 纯出站连接,绑定承载域 box.cf-zone.example.net
4服务器有公网 IP,且能开一个高位入站端口给 EO 回源用;不需要 nginx,应用直接监听即可
5EdgeOne 账户(本例国际站 edgeone.ai)选免备案区域
6CF 账户已绑支付方式启用 for SaaS 的前提(额度内不扣费)
7服务器 1C1G 级别即可iptables/ipset 是内核规则,无常驻进程

4. 分步实施

建议顺序:先 CF(步骤 A)→ 再 EO(步骤 B)→ 再 DNS(步骤 C)→ 最后服务器(步骤 D)。 前两步做完,流量仍在旧路径上,随时可暂停;步骤 C/D 才真正切换与收敛。

Step A —— Cloudflare 侧:SaaS 自定义主机名 + Tunnel(本次零改动,讲解原理)

本项目 box.example.com 的 SaaS 自定义主机名此前已配好(放在承载 zone cf-zone.example.net 下),Tunnel 已正常回源。若从零配置,动作是:

  1. 承载 zone cf-zone.example.netSSL/TLS → Custom hostnames → Add custom hostname,填 box.example.com
  2. 按提示完成归属验证(加一条 TXT 或 CNAME 验证记录);
  3. 在 Tunnel 的路由里把 box.example.com 指到本地 HTTP 服务(http://127.0.0.1:端口);
  4. 记录页里能拿到该主机名的 CNAME target(形如 <hash>.cdn.cloudflare.net)——它是「把流量引到 CF 边缘」的官方入口。

⚠️ 本项目实际没用 SaaS CNAME target,而是用优选 IP CNAME(cf-pref.example.net:一个第三方维护的、解析到 CF 低延迟边缘 IP 的域名。原理相同——解析结果落在 CF 边缘,CF 靠 Host: box.example.com 识别出这是 SaaS 自定义主机名流量再按 Tunnel 路由。优选域名的可用性依赖第三方维护,失效时换回官方 CNAME target 即可(第 7 节维护清单)。

本项目 DNSPod 上境外/默认线路指向的是 cf-pref.example.net

Step B —— EdgeOne 侧:添加站点(CNAME 接入,免备案区域)

  1. EO 控制台 → 添加站点 → 填 box.example.com
  2. 接入方式选 CNAME 接入(⚠️ 不要选 NS 接入——NS 接入会要求把域名托管给 EO,与 DNSPod 分线路冲突,和「DNS 在 CF 手里没法分线路」是同一个问题);
  3. 加速区域选「全球可用区(不含中国大陆)」(免备案的关键选择);
  4. 归属验证:按 EO 提示添加验证记录(本项目用 TXT:edgeonereclaim.example.com TXT ...),记录加在 DNSPod 的默认线路(CA/验证服务器多在境外,放默认线路全球可见最稳);
  5. 配置回源:源站地址 = 你的公网 IP + 高位端口,Host 头 = box.example.com;回源协议按你的源站监听协议选(应用直监听 HTTP 就选 HTTP,TLS 由 EO 边缘终结);
  6. 免费证书:CNAME 验证通过 + 回源配置就绪后,EO 自动签发(本项目 TrustAsia,CN=box.example.com)。签发需要几分钟到几小时——在证书签发完成前,443 会拿 *.cdn.myqcloud.com 兜底证书应答,浏览器会出现证书/HSTS 报错(第 6 节排障 6.1),属正常过程,等签发即可;
  7. 开启「客户端 IP 头」(关键,默认关闭!):站点 → 网络优化 / 规则引擎 → 客户端 IP 头部 → 开启,自定义头部名必须填 EO-Client-IP——应用侧固定读取这个名字;
  8. 删除/改写伪造的 CF 头:在 EO 加一条规则引擎规则,把客户端可能携带的 CF-Connecting-IP / X-Real-IP 等头删掉——否则大陆攻击者经 EO 带假 CF 头,会被应用当成 CF 流量优先信任(头信任模型见 Step D)。

Step C —— DNSPod 分线路:三条记录

权威 DNS 在 DNSPod(整域 zone example.com),所以直接在整域 zone 里配,不需要任何子域委派

主机记录类型线路记录值TTL
boxCNAME中国境内box.example.com.edgeone.example(EO 分配的目标)120~600
boxCNAME境外cf-pref.example.net(CF 优选 / 或 SaaS CNAME target)600
boxCNAME默认cf-pref.example.net(兜底,语义同境外)600

要点:

  • EO 归属验证 TXT 保持「默认」线路(全球可见),别只放境内;
  • 若之前误把 EO 的 CNAME 建在「默认」线路,会导致全球都走 EO——把它改成「中国境内」,境外/默认补 CF 两条;
  • TTL 别太大,方便回滚与换优选域时快速生效。

Step D —— 服务器侧:应用监听 + 信任头 + 端口锁定

D1 应用直接监听公网端口(免 nginx)

本项目 TamaBox 自带 HTTP 服务器,改配置即可:

[server]
listen = 0.0.0.0:18080   ; 从仅本机 127.0.0.1 改为全网卡,EO 才能远程连入(18080 仅为示例端口)
  • cloudflared 连的是 127.0.0.1:端口0.0.0.0 监听兼容回环连入,Tunnel 分支不受影响
  • nginx 在本方案中从头到尾不是必需——防火墙(iptables/ufw)是内核规则,零常驻进程,比 nginx 轻得多,适合 1C1G 小机器。

D2 应用信任两家 CDN 的权威头

conf/app.ini

[security]
trust_proxy = true

配合代码层支持(本项目 internal/shared/ip.goResolveClientIP),信任顺序:

  1. CF-Connecting-IP(Cloudflare 权威头,海外分支);
  2. EO-Client-IP(EdgeOne 权威头,境内分支);
  3. 兜底:TCP 对端 RemoteAddr。

⚠️ 头信任的安全前提(缺一不可)

  1. 源站防火墙只放行两家 CDN 的回源来源(EO 直连需要;CF 分支天然安全——Tunnel 是出站,外人到不了源站);
  2. EO 侧已删除伪造的 CF 头(Step B-8);
  3. 若还有 CF 侧直连,同样在 CF 规则里删除伪造的 EO 头。

任一缺失,「信任头」就会被攻击者直连源站 + 自带假头绕过——限流/封禁会拿到伪造 IP。

D3 端口白名单:iptables + ipset 自动维护(EO 免费档没有官方回源段)

EO 官方回源清单由「源站防护 / Origin ACL」提供(配套 DescribeOriginACL API),但该功能只支持付费套餐——免费档在控制台找不到入口,也没有可拉取的清单 URL。因此免费档采用**「种子清单 + 连接观察学习 + 定期同步」**的方案:

交付脚本 scripts/edgeone-origin-whitelist.sh(bash,需 root,架构要点):

  • 白名单放 ipset 集合 EO_ORIGIN,iptables 一条规则引用它:自建链 EO_IN 内「放行 127.0.0.0/8(保 cloudflared 回环)→ 命中集合放行 → 其余 REJECT tcp-reset」;
  • 更新走「临时集填充 → ipset swap 原子替换」:同步全程无规则清空窗口
  • fail-safe:拉取失败 / 清单为空 / 全非法 → 拒绝更新、保留现网,绝不误删导致端口裸奔;
  • 命令:sync(周期入口)/ learn(从连接表收集当前实际回源源 IP,做种子)/ show / flush(回滚)/ --dry-run
  • 清单来源:本地文件(/etc/edgeone-origin/cidr.txt)+ 可选 URL,取并集。

落地四步:

# 1. 改端口(默认 18080 → 你的实际端口)
# 2. 安装 ipset
apt-get install -y ipset
# 3. 生成种子:多跑几次 learn,把输出合并审阅后写入 cidr.txt(注意:种子是 EO 的"出口 IP",
#    不是应用日志里的客户端 IP!日志里是 EO-Client-IP 客户端真实 IP,不能当白名单)
bash edgeone-origin-whitelist.sh learn
bash edgeone-origin-whitelist.sh sync --dry-run
bash edgeone-origin-whitelist.sh sync
# 4. 验证:外网机器 nc -zv <IP> 18080 应 refused;https://box.example.com 应正常
# 5. 挂定时:cron「17 4 * * *」或 systemd timer,每日一次足够(EO 段 3~6 个月才变)

日后升级付费套餐:把 DescribeOriginACL API 的输出写入 cidr.txt 即可,脚本零改动。


5. 验证方法(含大陆 DNS 劫持陷阱)

5.1 分线路是否生效(🔴 重点:别在大陆本机 nslookup 境外 DNS)

大陆网络对发往境外公共 DNS(8.8.8.8 / 1.1.1.1 / 9.9.9.9 / OpenDNS,UDP 53 和 TCP 53 都会被劫持)的查询会返回按境内线路伪造的答案——在本机 nslookup box.example.com 8.8.8.8 永远显示 EO,即使境外线路早已配好。正确验证通道:

# 境内视角(本机直接查即可,通道可信)
nslookup box.example.com 223.5.5.5      # 期望 → CNAME edgeone.example → 203.0.113.x(EO 示例段)
nslookup box.example.com 119.29.29.29   # 同上(DNSPod 自家 DNS)

# 境外视角:用「境外服务器代为查询」,浏览器直接打开下面两个 API 之一
# https://api.hackertarget.com/dnslookup/?q=box.example.com
# https://networkcalc.com/api/dns/lookup/box.example.com
# 期望 → CNAME cf-pref.example.net → 198.51.100.x(CF 边缘 IP 示例段)

两条结果不一样 = 分线路生效

5.2 HTTPS 证书是否就绪

echo | openssl s_client -connect box.example.com:443 -servername box.example.com \
  | openssl x509 -noout -subject -issuer -dates
# 期望 subject = CN=box.example.com(而不是 *.cdn.myqcloud.com 兜底证书)

5.3 真实 IP 是否被正确识别

境内访问后看源站/EO 访问日志中的客户端 IP = 你的大陆公网出口 IP(说明 EO-Client-IP 生效);海外访问后同理应为你的海外出口 IP(CF-Connecting-IP)。


6. 今日排障实录(每个坑都值得记)

6.1 「网站使用 HSTS」报错 = EO 证书还没签发(不是配置错)

现象:DNS 切到 EO 后浏览器报 HSTS 错误页。 原因链:之前走 CF HTTPS 时应用下发了 Strict-Transport-Security: max-age=31536000; includeSubDomains(按 ExternalURL 以 https 开头就对每个响应下发)→ 浏览器记录「一年内必须 HTTPS」→ 切到 EO 后证书尚未签发、443 拿 *.cdn.myqcloud.com 兜底证书 → 证书与域名不匹配 → HSTS 错误页。 处理:等 EO 证书签发(几分钟~几小时)即可,域名不在 HSTS preload 列表,浏览器自动恢复,无需清缓存。紧急回滚 = DNS 改回 CF。 (若在 preload 列表里才是真麻烦——需先修好证书再等 preload 移除,故平时别主动提交 preload。)

6.2 误判「权威 DNS 在 Cloudflare」(方向性错误)

现象:一度以为 example.com 权威在 Cloudflare,推导出「需要子域 NS 委派」的复杂方案,差点让用户在 CF 做一堆不存在 zone 的操作。 根因:apex example.com 本身是一条 CNAME → your-project.pages.dev(Cloudflare Pages 项目)——nslookup 查 NS 时一路跟随 CNAME 链走到 pages.dev 的 NS(CF),被误读为「example.com zone 在 CF」。 正确做法:查域名权威必须问注册局(RDAP)或直查 SOA,不能信递归 CNAME 链:

# RDAP(注册局铁证):https://rdap.org/domain/example.com → 应返回 byron/sunshine.dnspod.net
# 或直查 SOA:nslookup -type=SOA example.com byron.dnspod.net

6.3 「境外也返回 EO」的假象 = 大陆 DNS 劫持(连 TCP 53 都劫)

现象:从大陆本机用 nslookup 测 8.8.8.8 / 9.9.9.9 / OpenDNS(UDP 和 TCP 都试),境外视角全部返回 EO,一度以为分线路没生效、甚至怀疑 DNSPod 配置错了。 真相:大陆网络劫持出境 DNS 查询,返回按境内线路伪造的 EO 答案,请求根本没到境外服务器。 破案手段:用境外服务器(hackertarget / networkcalc API)代为查询,立刻看到 CF 答案——DNSPod 三线路配置完全正确,从未出过错。教训见 5.1。

6.4 残留的阿里云验证记录

实测发现 DNSPod 上有历史残留 _dnsauth.box.example.com CNAME → xxxxxxxx...aliyunauth.certificate-validations.com(以前阿里云申请证书留下的)。_dnsauth / _acme-challenge 下的旧验证记录会干扰证书自动续期验证——证书验证记录的坑,建议确认证书正常后删除。

6.5 免费档没有「源站防护」,回源 IP 段拿不到

见 Step D3:免费档无官方清单,用 learn 观察法兜底;付费后切 DescribeOriginACL,脚本零改动。


7. 日常维护清单

周期事项
证书到期前EO 免费证书自动续期并重新做域名验证——⚠️ CA 验证服务器多在境外,所有验证记录务必放默认线路;到期前留意 EO 控制台证书状态,失败会提示重新验证
每日(定时任务)白名单 sync(EO 回源段 3~6 个月才变,每日一次足够)
每月抽查 learn 输出是否有新回源源 IP 需补种;确认优选域名 cf-pref.example.net 仍解析到 CF 边缘(失效则换回官方 SaaS CNAME target,只改 DNSPod 一条记录)
EO 回源突然失败先临时放行全段定位,再按 learn 结果收紧——EO 免费档收不到段变更通知,要有这个预案
长期若升级 EO 付费套餐:启用源站防护拿官方段 → 接入 DescribeOriginACL 输出到 cidr.txt

8. 回滚

  1. 快速回滚(境外也要回 CF 之外的兜底):DNSPod 把「境内」线路改指向 CF(或删掉境内线路,让默认兜底接管)→ 全球走 CF,分钟级生效;
  2. 彻底回滚(回到纯 CF 时代):删除 EO 站点/记录、撤防火墙放行、DNSPod 三线路合并为默认→CF;
  3. EO 证书到期前回滚,记得去 EO 控制台删除站点避免残留验证记录干扰。

附:配置快照(示例,2026-09-03 实测定稿,域名已脱敏)

域名:        box.example.com(权威 DNS: DNSPod,NS=byron/sunshine.dnspod.net)
境内:        CNAME box.example.com.edgeone.example → 203.0.113.x   (EdgeOne 国际站,区域不含大陆,示例段)
境外/默认:   CNAME cf-pref.example.net → 198.51.100.x              (CF 优选 IP,示例段)
CF 侧:       SaaS 自定义主机名(承载 zone cf-zone.example.net)+ Tunnel 出站回源
EO 证书:     TrustAsia, CN=box.example.com(签发后约 90 天到期,自动续期)
EO 归属验证: edgeonereclaim.example.com TXT(DNSPod 默认线路 ✅)
EO 客户端头: EO-Client-IP(手动开启,头名固定 EO-Client-IP)
源站:        TamaBox 直监听 0.0.0.0:高位端口(无 nginx);iptables+ipset 白名单
应用信任:    trust_proxy=true;ResolveClientIP 信任 CF-Connecting-IP → EO-Client-IP → RemoteAddr
本博客已稳定运行
使用 Hugo 构建