news 2026/10/7 14:30:35

多设备接入Cloudflare Tunnel实战:从权限排错到容器化部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多设备接入Cloudflare Tunnel实战:从权限排错到容器化部署

先说个可能打破你预期的事实:我这套内部代号叫“cloudflare-os”的方案,并不是Cloudflare官方发布的某个操作系统——至少到今天为止,官方还没有这样一个东西。会起这个名字,完全是因为我折腾了一圈之后发现,用Cloudflare这套云服务把家里的NAS、虚拟机、开发板统一管起来,真正的难点几乎全部落在“操作系统”这四个字上。权限模型、文件系统、守护进程、虚拟化、软件源……每一项都比Cloudflare本身的配置门槛高。这篇就围绕“cloudflare-os”这个概念,把我在Linux、macOS、飞牛OS(fnOS)上接入Cloudflare Tunnel和周边工具时踩过的坑、排查链路和最终固化下来的方案完整梳理一遍。如果你正打算让本地多台设备都稳定接入Cloudflare生态,这篇文章应该能帮你少走不少弯路。

1. 先搞清楚“cloudflare-os”到底是个什么东西

1.1 并不是操作系统,而是一套本地Agent接入体系

Cloudflare真正跑在你本机上的组件,远不止“改个DNS”这么轻。我日常用到的其实就三类东西:

  • cloudflared:Cloudflare Tunnel的客户端,一个常驻操作系统的守护进程,负责把本地服务通过出站连接暴露到Cloudflare边缘网络;
  • Wrangler CLI:开发Cloudflare Workers用的本地命令行工具,会牵扯到Node/Rust工具链、缓存目录这些OS级的东西;
  • 域名解析接入:把域名NS切到Cloudflare,让DNS、代理、安全策略全部交给Cloudflare处理。

这三类里,最重的是第一个。cloudflared不是一次性命令,它需要以一个系统服务的形式长期活着,才能让Cloudflare边缘网络随时和你本地的服务建立起安全通道。所以“cloudflare-os”这个代号在我这边的含义,就是“以Cloudflare云能力为中心、把各种本地操作系统统一纳管起来的一组Agent和配套配置”。它横跨Linux服务器、macOS开发机、Windows老机器、以及飞牛OS这类NAS系统,每一端的适配都跟底层OS深度绑定。

1.2 为什么隧道模式反而比端口映射更吃操作系统稳定性

很多人一开始的想法是:我路由器上做一个端口映射,把NAS的Web界面映射出去不就完了?我在早期也是这么干的,直到被两件事教育了。

第一件事是运营商大内网。现在很多宽带的公网IP是假的,光猫拨号拿到的IP并不直接暴露在公网上,端口映射根本无从谈起。第二件事是安全性。即使你有公网IP,直接把端口裸奔在公网等于把NAS的管理界面、FTP、甚至摄像头流媒体接口直接交给全网扫描器,日志能给你刷出几千条攻击记录。

Cloudflare Tunnel的工作方式和端口映射完全相反:cloudflared主动从你本机发出加密连接(默认是四条长连接),连到Cloudflare边缘网络,然后在边缘网络上注册一条路由。公网用户访问你的域名时,请求在Cloudflare边缘节点被处理,再通过这条已经建立好的隧道转发到本地。整个过程不需要你在路由器上开任何入站端口,也不需要公网IP。

但代价就藏在这里:隧道必须是一个持续存活的服务。一旦cloudflared进程挂掉、被系统杀掉、或者因为底层日志文件把磁盘写满而崩溃,整条链路就断了。这时候你拼的不是Cloudflare的控制台配置,而是操作系统的服务管理能力——systemd写得好不好、Docker重启策略对不对、日志有没有做轮转限制。我后来遇到的那次“overlay2占满导致cloudflared反复重启”,根子就在OS侧,而不是Cloudflare侧。

1.3 这套环境的整体画像是“云边协同”

把“cloudflare-os”整体画出来,其实就两层:公网侧是Cloudflare的DNS、CDN、Zero Trust访问策略、边缘网络;本地侧是你的NAS、服务器、虚拟机、开发板,每台设备上跑一个cloudflared客户端,通过出站连接和Cloudflare边缘网络保持心跳。剩下的工作,全部是在本地操作系统上让这个Agent活得更稳、日志更可控、权限不越界。

搞清楚这个框架之后,我遇到的第一个硬骨头就来了:在macOS上部署cloudflared时,直接被一个权限错误拍在了沙滩上。

2. 第一道坎:权限错误“拒绝访问 (os error 5)”的完整排查链路

2.1 报错场景还原

我在一台macOS测试机上做cloudflared初始化,执行到cloudflared service install或者直接运行cloudflared tunnel run的时候,终端里弹出了这么一段:

error: 拒绝访问。 (os error 5)

后面还跟着一句提示,大意是“要想在不使用后台服务器的情况下工作,请重新运行”。我当时盯着这几行字看了半天,完全没搞懂“background server”是什么东西。后来才意识到,问题压根就不在Cloudflare的配置上,而是操作系统不允许这个进程去创建它需要的后台服务,或者去写它需要的配置目录。

需要说明的是,这类报错并不只出现在cloudflared上。很多Rust写的CLI工具在macOS或者受限的Linux环境里跑起来时,都会把底层操作系统的错误码直接打印出来。cloudflared本身就是Rust写的,所以它的错误信息格式非常“系统”——直接把errno翻译成系统文本,不带任何包装。

2.2 os error 5到底是什么

在Unix/Linux/macOS里,errno 5对应的宏是EACCES,含义是“Operation not permitted”,也就是权限不足。在Windows里,os error 5对应的是ERROR_ACCESS_DENIED,同样也是拒绝访问。

但“权限不足”这四个字太笼统了。我拆到实际场景里,它通常来自三个不同的层面:

层面典型来源表现
文件系统权限当前用户对配置目录没有写权限写/var/lib/cloudflared或/etc/cloudflared失败
内核安全模块SELinux或AppArmor拦截RHEL系默认开启SELinux enforcing时常见
macOS沙盒/TCC机制首次运行的CLI没有后台运行权限创建系统服务时被系统阻止

这三个层面排查起来的路径完全不同,我按顺序给出我实际用的排查链路。

2.3 完整排查链路(可以直接照着抄)

第一步,确认身份和目录权限。先看当前用户是谁,再看cloudflared要用的几个标准目录是否存在、是否可写:

id ls -ld /var/lib/cloudflared /etc/cloudflared ls -ld ~/.cloudflared

如果目录不存在,先尝试用sudo创建并授权:

sudo mkdir -p /etc/cloudflared sudo chmod 755 /etc/cloudflared

第二步,如果是RHEL系(CentOS、Rocky、AlmaLinux等),查SELinux:

getenforce ausearch -m avc -ts recent

如果ausearch输出里有一堆avc denied记录,基本就是SELinux在拦截cloudflared访问某个文件或绑定某个端口。可以先临时放进permissive模式验证:

sudo setenforce 0

再执行一次cloudflared命令。如果通了,说明就是SELinux策略的问题。注意,permissive模式只用来验证,千万别在生产环境一直开着。更稳妥的做法是给cloudflared写一条自定义SELinux模块,或者直接把可执行文件放在系统预设的允许路径下。

第三步,macOS上查沙盒和TCC记录。打开“系统设置 → 隐私与安全性”,看有没有被阻止的项;或者打开“应用程序 → 实用工具 → 控制台”,在搜索框里输入cloudflared,看有没有类似“deny”的日志。macOS的TCC机制很要命,它会在第一次运行某个未签名或签名不完整的CLI工具时,默认拦截它创建后台服务的能力。有时候你甚至需要在“隐私与安全性”里手动给终端App授权“完全磁盘访问权限”,CLI才能读取它需要的一些系统路径。

第四步,最常用也最容易被忽略的一招:直接加sudo重跑。

sudo cloudflared tunnel login sudo cloudflared tunnel run <tunnel-name>

2.4 我的处理结果和一条重要经验

我那次的问题根因其实特别基础:没加sudo执行,配置文件的路径又放在了用户家目录下的自定义位置,而cloudflared的service install流程默认去找/etc/cloudflared/,写入失败,于是整个初始化流程中断,抛出了os error 5。把配置挪到标准位置、用sudo重新执行之后,一步就过了。

一条很重要的经验送给所有刚接触Cloudflare Tunnel的人:不要一次性把login、create、route、run、install五步全部连着跑完。每执行一步,先单独验证一步的状态。比如login之后马上跑cloudflared tunnel list确认登录态已经在;create之后确认json凭据文件有内容;route之后确认DNS解析记录出现在Cloudflare Dashboard里。这样出问题时,定位范围会小非常多。我那次就是太着急一把梭,结果错误一来根本不知道卡在哪一环。

3. NAS实战:在飞牛OS上跑通Cloudflare Tunnel的完整路线

3.1 为什么拿飞牛OS做载体

最近飞牛OS(fnOS)在自建NAS圈子里热度很高,热搜里一堆“刷飞牛OS”“虚拟机装飞牛OS”的。我也跟风刷了一台旧主机来试。飞牛OS本质上是一个基于Debian的轻量NAS系统,自带硬件监控、应用中心、文件管理、Docker管理器,对国内用户很友好。底层既然是Debian系,意味着cloudflared的deb包和Docker镜像都直接能用。

更重要的是,NAS是一个天然的“Cloudflare Tunnel使用场景”。NAS里通常跑着Web管理界面、下载器、媒体服务、FTP、甚至EasyNVR这类流媒体服务。这些服务全都需要远程访问,但又全都不能直接裸奔到公网上。用cloudflared统一接入,是比端口映射安全得多的方案。

3.2 用Docker在飞牛OS上部署cloudflared

我的推荐是优先用Docker方式,而不是直接在系统层装deb包。原因有两个:第一,Docker镜像的升级回滚非常干净,拉错版本也不会污染系统环境;第二,Docker可以对日志做精细限制,直接避免后面要说的overlay2膨胀问题。

部署流程分四步。

第一步,先把cloudflared在本地环境初始化好,并拿到tunnel凭据:

cloudflared tunnel login cloudflared tunnel create nas-tunnel

login会弹出一个浏览器窗口,让你选择要绑定的域名;create会生成一个json格式的凭据文件,里面是tunnel的ID和账户信息。

第二步,在飞牛OS上建代码目录,把你生成好的<tunnel-id>.json和主机上的配置文件都放进去:

mkdir -p /vol1/docker/cloudflared

我习惯把凭据json和config.yml放在同一个目录,方便后续compose文件直接挂载。

第三步,写config.yml。这里有一个非常关键的点:如果你用host网络模式,配置里的localhost指的是NAS本身;如果你用默认的bridge网络,容器里的localhost就是容器自己。为了省心,我直接采用host模式,配置文件长这样:

tunnel: nas-tunnel credentials-file: /etc/cloudflared/<tunnel-id>.json ingress: - hostname: nas.example.com service: http://localhost:80 - hostname: ftp.example.com service: http://localhost:8080 - hostname: livestream.example.com service: http://localhost:8088 - service: http_status:404

第四条service: http_status:404是兜底规则,必须写,否则cloudflared会因为找不到匹配规则而拒绝启动。我见过很多新手在这里卡住,配置文件里只有两条hostname规则,没有兜底,结果报错。

第四步,启动容器:

docker run -d --name cloudflared \ --network host \ --restart always \ -v /vol1/docker/cloudflared:/etc/cloudflared \ cloudflare/cloudflared:latest \ tunnel --config /etc/cloudflared/config.yml run

--restart always很重要,NAS重启后cloudflared会自动拉起来,不需要再手动进终端。启动后查看日志:

docker logs cloudflared

看到Registered tunnel connection字样,说明隧道已经通了。这时候在公网访问nas.example.com,应该能直接打开NAS的Web界面。

3.3 overlay2文件系统占满的真相与清理方案

这是热搜里那个“飞牛os overlay2 文件占用大”的问题,我也是实打实踩了一遍。

症状表现是:cloudflared跑了一周左右,NAS开始告警磁盘空间不足。查一下/var/lib/docker目录,发现overlay2子目录的体积大得离谱。

Docker默认的存储驱动就是overlay2,镜像层是写时复制结构。每次你拉取一个新的cloudflared镜像,旧镜像的层并不会立即消失,再加上容器运行期间产生的可写层、以及容器日志的无限增长,空间就被慢慢吃完了。尤其像cloudflared这种需要长期运行的守护型容器,它的标准输出会不断产生日志,而容器日志文件默认是不做任何大小限制的。日志文件在/var/lib/docker/containers/<容器ID>/目录下,文件名以-json.log结尾。

先用这条命令看清到底是谁在占用空间:

docker system df

这条命令会输出镜像、容器、本地卷、构建缓存的占用情况。我那次排查下来,大头就是两个:一个是构建缓存,另一个是cloudflared容器的json日志文件。

清理和预防分两步走。

第一步,清理现有垃圾:

docker system prune -a -f

这会清除所有未被使用的镜像、容器、网络和构建缓存,是见效最快的一招。注意-a会连所有没在用的镜像层都删掉,下次启动时会重新拉取,但换来的是干净空间,值得。

第二步,从源头限制日志,这是更关键的一步。单容器可以在docker run时加参数:

docker run ... \ --log-opt max-size=5m \ --log-opt max-file=3

或者修改Docker守护进程的全局配置/etc/docker/daemon.json:

{ "log-driver": "json-file", "log-opts": { "max-size": "5m", "max-file": "3" } }

改完重启Docker服务会短暂影响所有容器,建议在业务低峰期操作。我把cloudflared日志上限设成每个文件5MB、保留3个文件,一个月下来日志占用不会超过20MB,从此再没出过overlay2被撑爆的问题。

3.4 网络唤醒与FTP暴露的安全底线

热搜词里还有“HP主机安装飞牛OS如何开启网络唤醒”和“飞牛OS设置FTP”这两个,我也一并说说。

NAS如果平时处于休眠状态,人到了外面想唤醒它,光靠cloudflared是不够的,因为进程都睡着了。你需要先完成三件事:

  • 在BIOS/UEFI里开启Wake-on-LAN。不同主板叫法不同,常见的有“Power On By PCIE/Onboard LAN”“Wake on LAN”“WOL”等;
  • 在NAS系统层面确保网卡允许魔术包唤醒。用ethtool eth0 | grep Wake-on查看,如果显示Wake-on: g说明支持;如果是d,需要执行ethtool -s eth0 wol g开启;
  • 为了让WOL设置持久化,建议写一个systemd服务在开机时执行,或者直接在飞牛OS的网络设置里确认相关选项。

有了WOL之后,手机端的唤醒App发一个魔术包,NAS开机,Docker随系统自启,cloudflared连回Cloudflare边缘网络,整条链路恢复。

再说FTP。飞牛OS自带FTP服务,配置起来也确实方便。但我要强调一句:FTP是明文传输,用户名、密码、数据全部裸奔。哪怕你通过Cloudflare Tunnel转发FTP,隧道的公网段是加密的,但FTP协议本身和Tunnel的内网段仍然明文。如果非要暴露FTP,最低限度要套一层Cloudflare Access做前置身份认证,并要求客户端使用FTPS。更推荐的做法是直接改用SFTP(基于SSH),或者在NAS上开WebDAV并套上HTTPS。总之,别图省事把21端口裸奔,这是我在安全上最坚持的一条经验。

4. 多OS环境绕不开的两个地雷:虚拟机拖拽失效和老系统镜像源404

4.1 VirtualBox的DnD报错:又一个典型的Guest OS问题

热搜里有这么一条报错:

dnd: error: drag and drop to guest not possible -- either the guest os does not support drag&drop or the dnd data format is not accepted

这基本是VirtualBox使用者的共同记忆了。我刚在虚拟机里装飞牛OS做测试的时候,想从宿主机拖一个安装包进虚拟机,结果就弹了这么一段。

这个报错通常指向三个原因,排查顺序我建议按下面的来:

第一,虚拟机没装Guest Additions,或者版本和VirtualBox本体差太多。这其实是最常见的。打开虚拟机的“设备”菜单,选择“安装增强功能”,挂载光盘后在虚拟机里执行安装脚本。Linux需要先安装编译依赖:

sudo apt install -y build-essential linux-headers-$(uname -r) sudo mount /dev/cdrom /mnt sudo /mnt/VBoxLinuxAdditions.run

第二,虚拟机设置里的拖拽方向没打开。在VirtualBox主界面选中虚拟机,进入“设置 → 常规 → 高级”,把“Drag and Drop”从“Disabled”改成“Bidirectional”。改完要重启虚拟机。

第三,部分极简发行版(我遇到过一些精简版Debian和Arch的最小安装)在安装Guest Additions时缺内核头文件,脚本明明显示成功,实际却没编译出内核模块。这时候还是回到第一步,把linux-headers装好重新执行。

一个我自己的经验:在Linux虚拟机里,拖拽功能的效果一直不太稳定。与其死磕拖拽,不如直接配一个共享文件夹,或者用一个临时HTTP服务把文件传到虚拟机里。如果你当前的目标只是在一个干净的Linux环境里测试cloudflared二进制,那直接scp文件进去就行,效率高得多。

4.2 CentOS 7的仓库源报错:Errno -1不是网络问题

热搜里这条报错我也很眼熟:

http://mirrors.aliyun.com/centos/7/os/x86_64/repodata/repomd.xml: [Errno -1]

我一开始以为是网络不通。但仔细看,这台机器访问其他HTTP地址都正常,唯独访问阿里云的这个路径失败。后来查清楚原因:CentOS 7在2024年6月30日已经正式停止维护(EOL),官方把整个7系列的软件仓库从常规目录移到了vault归档目录。你用mirrors.aliyun.com/centos/7这个路径去访问,返回的repodata其实是空的或者直接404,yum就报了Errno -1。

解决办法是把基础源切到vault归档路径。以阿里云为例,正确的路径应该是:

http://mirrors.aliyun.com/centos-vault/7.9.2009/os/x86_64/

或者用清华镜像:

https://mirrors.tuna.tsinghua.edu.cn/centos-vault/7.9.2009/os/x86_64/

操作方式很简单:编辑/etc/yum.repos.d/CentOS-Base.repo,把baseurl里的centos/替换成centos-vault/7.9.2009/,把mirrorlist这一行注释掉,然后执行:

yum clean all yum makecache

做完这一步,老系统又能装包了。接着你就可以按Cloudflare官方文档的步骤添加cloudflared的rpm仓库,然后正常安装。

这个坑给我的启发是:老系统的问题往往不在“装不上某个新工具”,而是“整个系统的软件源地基已经改变”。遇到Errno -1、404这类报错,先别急着怀疑网络,先确认你用的Linux发行版是否已经EOL,再确认镜像源路径是否做了归档迁移。这一点放在“多OS环境”里尤其重要,因为你手头那台机器可能根本不是你想当然的最新版。

5. 把临时调试变成可复现的生产环境

5.1 用systemd把cloudflared做成“杀不死”的服务

如果你不是用Docker,而是直接在Debian系的服务器上装cloudflared,那一定要交给systemd管理。我用的unit文件大致是这样的:

[Unit] Description=Cloudflare Tunnel Agent After=network-online.target Wants=network-online.target [Service] Type=simple User=cloudflared Group=cloudflared ExecStart=/usr/local/bin/cloudflared tunnel --config /etc/cloudflared/config.yml run nas-tunnel Restart=always RestartSec=5s LimitNOFILE=1048576 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target

几个要点:

  • Restart=always:不管因为什么原因被杀掉,5秒后自动拉起来,是保活的关键;
  • After=network-online.target:确保网络真正就绪后才启动,避免开机时网络还没通导致连接失败;
  • LimitNOFILE=1048576:cloudflared要维持大量出站长连接,默认的文件描述符上限可能不够,放宽到1024K比较稳。

把unit文件放到/etc/systemd/system/cloudflared.service,然后:

sudo systemctl daemon-reload sudo systemctl enable --now cloudflared sudo systemctl status cloudflared

这套配置在普通Debian服务器和飞牛OS上都能用——飞牛OS底层是Debian,systemd这一层是完全通用的。

5.2 日志和升级策略:别让Agent自己吃了自己

用systemd管理之后,日志会进journald。如果不管,/var/log/journal也可能悄悄变大。我一般会在/etc/systemd/journald.conf里设一个总上限:

SystemMaxUse=200M

然后重启journald:

sudo systemctl restart systemd-journald

升级方面,我的习惯是固定主版本,不要每次手动拉latest。无论是Docker镜像还是deb/rpm包,升级之前先看一眼当前版本,再拉新版本。用Docker的话:

docker compose pull docker compose up -d

用systemd的话:

sudo systemctl restart cloudflared

升级完一定要看一眼日志,确认Registered tunnel connection重新出现,再离开终端。

5.3 把整套环境固化成一份compose文件

我最后的做法是把cloudflared隔离成一个独立的compose服务,这样不管换到飞牛OS、Debian服务器还是树莓派,只要把目录和文件拷过去,一条命令就能复现整套接入环境。

最终的docker-compose.yml大概是这个样子:

services: cloudflared: image: cloudflare/cloudflared:2024.10.0 container_name: cloudflared restart: always network_mode: host command: tunnel --config /etc/cloudflared/config.yml run nas-tunnel volumes: - /vol1/docker/cloudflared:/etc/cloudflared:ro logging: driver: json-file options: max-size: "5m" max-file: "3"

这里有几个点说一下:

  • network_mode: host:配合config.yml里的localhost使用,让容器和宿主机共享网络栈,不需要额外暴露端口;
  • volumes挂载的是整个cloudflared配置目录,用ro只读挂载,防止容器内改动配置;
  • logging限定了日志大小,从源头杜绝overlay2膨胀。

这套东西我从临时脚本一路折腾成compose加systemd的固定组合,前后踩掉至少两天的坑。最深的体会是:Cloudflare这层云服务给了你很大的自由度,但真正决定“能不能顺畅用起来”的,永远是底下那个操作系统。别急着抱怨CDN配置不对,先看看你的OS在权限、日志、文件系统这些基础环节上是不是已经在埋雷了。把这些地基问题解决掉,“cloudflare-os”这套多设备接入体系才算真正立得住。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 14:29:56

text-to-cad 实战:从自然语言到 CAD 模型与 URDF 的完整链路

1. 从一段话到可编辑模型&#xff1a;text-to-cad 到底在解决什么问题如果你做过机械设计、建筑建模或者机器人仿真&#xff0c;一定经历过这种场景&#xff1a;脑子里已经想清楚了一个零件的形状&#xff0c;甚至能用嘴描述得明明白白——“一个长宽高分别是 80、60、40 毫米的…

作者头像 李华
网站建设 2026/10/7 14:29:37

Linux PCI设备驱动开发实战:从枚举、BAR映射到DMA与中断处理

1. 从一张“掉卡”工单说起&#xff1a;PCI设备驱动到底在管什么前阵子帮朋友排查一台工控机的故障&#xff0c;现象很典型&#xff1a;系统跑着跑着&#xff0c;一块通过PCIe插槽扩展的网卡就“消失”了&#xff0c;lspci里还能看到设备&#xff0c;但ifconfig里网口没了&…

作者头像 李华
网站建设 2026/10/7 14:29:35

MySQL 慢查询:从定位到优化,一套可复用的排查流程

一句话结论&#xff1a;慢查询的根因通常不在 SQL 语法&#xff0c;而在“有没有走对索引”和“一次拿了多少行”。把这两件事查清楚&#xff0c;80% 的性能问题能在十分钟内定位。 一、一个典型的凌晨 线上告警&#xff1a;某个列表接口 RT 从 200ms 涨到 8s&#xff0c;数据库…

作者头像 李华
网站建设 2026/10/7 14:29:15

cursor.execute(sql1, args) 多参数占位符统一用 %s 的写法与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华