Chips

用一条 Cloudflare Tunnel 收敛全部对外服务

· 约 4 分钟 · 1271 字

把"逐个暴露端口 + 逐条挂 DNS"改成一条隧道统一出口后,宿主不再监听任何对外端口。记录 ingress 顺序、多副本 HA 的真实作用边界,以及 metrics 默认绑定带来的意外暴露。

起点:散乱的暴露方式

最初每加一个服务就是三件事:容器里写 ports:、防火墙放行、DNS 挂一条 A 记录指向源站 IP。服务一多就出现几个必然结果:

改成隧道之后,这三件事变成一件:加一条 ingress 规则。

架构

互联网 ──► Cloudflare 边缘(TLS 在此终止)
              ▲
              │  隧道:cloudflared 主动外连建立
              │
      cloudflared ×N ──共享 docker 网络──► 各服务容器

关键性质:cloudflared 是出站连接。宿主不监听任何对外端口,不需要公网入站,也不需要为它开防火墙。源站 IP 不再出现在任何 DNS 记录里。

ingress 规则的两件事

规则托管在 Cloudflare 侧(token 模式隧道),本地没有配置文件。只有两个高频错误:

1. 顺序。 具体 hostname 必须排在兜底项之前:

ingress:
  - hostname: notes.example.com
    service: http://notes:3000
  - hostname: files.example.com
    service: http://files:5244
  - service: http_status:404        # 兜底必须在最后

写反了不会报错,只会让所有域名都命中 404。

2. DNS 指向隧道而不是源站。 每个 hostname 需要一条 proxied 的 CNAME:

<子域>  CNAME  <隧道ID>.cfargotunnel.com   (Proxied)

橙色云必须打开,否则流量不会经过边缘,TLS 也就不会被终止。

多副本 HA:作用边界比想象中窄

同一隧道可以注册多个 connector 副本,边缘会自动分流。这解决的是「进程还活着但连接已经断了」这一类故障——此时:

所以多副本真正的价值是:单个 connector 挂死时,边缘绕行到另一个副本,服务不中断。健康检查只负责报告,不负责自愈。二者不能互相替代——只做健康检查会有"显示不健康但一直不恢复"的窗口,只做多副本则缺少可观测性。

一个意外暴露:metrics 默认绑全网卡

cloudflared 的 --metrics 参数不写地址时默认绑 [::]。这意味着同一容器网络里的任何容器都能读到:

后者的价值不必多说:堆转储里可能有 token、请求头、内存中的凭据。如果那个共享网络里还跑着第三方镜像(很多镜像不是你自己构建的),这就不是理论风险。

修复只是加一个地址:

command: tunnel --no-autoupdate --metrics 127.0.0.1:20241 run

值得留意的是:这类"默认值恰好是宽的那个"的问题,往往在部署时被当作可用配置抄进文档,然后一路带着走。

新增一个服务:三步

# 1. 让服务加入共享网络。注意不写 ports:
services:
  app:
    image: some/image
    networks: [edge]
    # 有意不写 ports: —— 这是重点
networks:
  edge:
    external: true
# 2. 加 ingress 规则(云端),把 hostname 映射到服务名
# 3. 加 DNS:CNAME <子域> → <隧道ID>.cfargotunnel.com(Proxied)

不需要碰端口,不需要碰防火墙。把"新增服务"从三处改动收敛成一处,是这次改造最大的收益。

资源上限顺带要设

隧道把服务藏到了边缘后面,但容器本身的内存并没有上限。同机多个服务共用内存时,一个 OOM 会牵连全部。mem_limit / memswap_limit / pids_limit 三个一起设,其中 memswap_limit 等于 mem_limit 是有意的——它禁止换页,避免内存压力变成磁盘 IO 抖动。宿主 swap 本来就紧张时,让容器快速失败比让整机一起卡要好。

排查表

现象 原因 处理
HTTP 521 边缘连不上源站,或 DNS 刚改还没收敛 等 30 秒重试;看 connector 日志
HTTP 404 该 hostname 没有匹配到 ingress 规则 检查规则顺序与 hostname 拼写
HTTP 502 connector 通了但后端拒绝连接 在共享网络里直接探后端端口
域名不解析 本地 resolver 负缓存 换公共 DNS 复核,如 dig @1.1.1.1
改了密码/配置不生效 只改了运行时状态,没改 compose 的环境变量 改 compose 并重建容器

最后一条值得单独强调:只要 environment 里存在某个配置项,它每次启动都会覆盖运行时设置。这是"改了没生效"最常见的成因。

小结

← 全部文章