记录范围
本文记录本机(docker 容器方式)部署 mihomo(clash)代理 + 管理面板过程中遇到的全部问题:现象 → 根因 → 修法 → 客观证据。所有数字都来自本机实测。
环境与形态
| 项 | 值 |
|---|---|
| 部署方式 | docker compose,跑在 rootless daemon(用户 cat) |
| 目录 | ~/www/mihomo/(compose 与 data/);容器内挂载为 /root/.config/mihomo |
| 镜像 | metacubex/mihomo:v1.19.24 + 面板 ghcr.io/metacubex/metacubexd:v1.247.0 |
| 端口 | 7890 代理;9091 -> 9090 控制 API(避开面板的 9090);9090 -> 80 面板 |
| 节点来源 | proxy-providers 一份订阅(HTTP,interval 1800) |
| 当前实测 | 10MB 样本经代理均速约 574 KB/s;选中节点 新加坡-SG-3-2;容器用量 clash 52MiB / 512MiB、CPU 0.05% |
一、问题清单
| # | 现象 | 根因 | 修法 |
|---|---|---|---|
| P1 | 走代理下载极慢(nix 拉包慢到不可用) | 健康检查的测速目标选错 —— 用通用 CDN/测速站时,节点排序完全不可信,自动选择长期停在「对通用站快、对真实下载主机慢」的坏节点上 | 把健康检查 URL 改成真实下载主机(本机场景 = https://cache.nixos.org/nix-cache-info,仅 51 字节) |
| P2 | 容器跑在 rootful(system)daemon 下,而不是 rootless(cat 用户) | NixOS 同时开了两个 daemon;DOCKER_HOST 指向哪个就用哪个 | 一次性搬迁镜像/网络到 rootless(见第三节)。不需要改 .nix |
| P3 | 并发时代理吞吐上不去 | compose 里 cpus: 0.5 / mem_limit: 256M —— 半核成为 TLS 解密 + 多并发的天花板,nofile 也偏小 | cpus 2.0 / mem 512M / nofile 65535 + 健康检查 + 日志轮转 |
| P4 | 换订阅/节点增减就要改配置 | 分组里写死节点名(「死值」) | 节点只在 provider 定义一次,所有分组一律 use: + filter / exclude-filter 动态匹配 |
| P5 | 调参过程中踩到若干配置项陷阱 | 见第六节逐条 | 见第六节 |
二、P1:下载慢的真正原因(最有价值的一条)
根因不是带宽、不是配置写错、不是 DNS,而是自动选择长期停在坏节点上:实测代理选中 爱沙尼亚-EE-1 时对 cache.nixos.org 只有 41 KB/s,而同订阅里 澳大利亚-AU-3 有 5183 KB/s —— 相差 126 倍。
为什么排序会选错:通用测速目标会骗人
对 35 个节点逐点实测(每个节点分别下载 cache.nixos.org 与通用测速站 speedtest.tele2.net,原始数据存 clash-sweep-results.json):
| 节点 | 对 cache.nixos.org(真实目标) | 对 tele2(通用目标) | 结论 |
|---|---|---|---|
| 澳大利亚-AU-3 | 5183 KB/s | 241 KB/s | 真实最快,通用目标却低估它 |
| 荷兰-NL-1 | 4243 KB/s | 4862 KB/s | 两个口径一致 |
| 美国-US-2 | 4220 KB/s | 失败 | 通用目标测不出来 |
| 日本-TY-5-HY2 | 2057 KB/s | 390 KB/s | 被低估 |
| 德国-DE-2 | 1767 KB/s | 1724 KB/s | 一致 |
| 英国-UK-1 | 212 KB/s | 3172 KB/s | 骗人:差 15 倍 |
| 爱沙尼亚-EE-1 | 41 KB/s | 2778 KB/s | 骗人:差 67 倍 ← 就是当时被选中的那个 |
另外:35 个节点里有 11 个对 cache.nixos.org 吞吐为 0(完全不通或极慢),包括订阅里的「剩余流量/有效期」这种伪节点行 —— 这些行必须用 exclude-filter 排除,否则会被当成节点参与测速。
修法与效果
把健康检查 URL 从「通用站点」改成真实下载主机上的极小文件:
# 51 字节,几乎零流量 → 所以 interval 可以压到 300 秒,排序跟着实时更新
url: "https://cache.nixos.org/nix-cache-info"
interval: 300
lazy: false # 启动就测,不要「用到才测」
效果(实测):单线程 0.017 → 0.49 MiB/s(28 倍);8 并发聚合 2.42 MiB/s。
方法论:健康检查的测速目标必须对准你真正要下载的东西。「延迟低」和「下载快」是两件事 —— 有的节点 ping 很低但带宽极差。
三、P2:容器应该跑 rootless,而不是 rootful
现象:以普通用户执行 docker ps 看到空列表,但代理确实在跑。判定证据(两条独立途径都指向 rootful):
# 1) 容器进程的 cgroup
cat /proc/<mihomo pid>/cgroup
→ system.slice/docker-048bc6e0….scope # rootful 是 system.slice
→ user.slice/user-1000.slice/…/docker-*.scope # rootless 是 user.slice(现在已经是这个)
# 2) 父进程
ps -o ppid= -p <mihomo pid> → containerd-shim-runc-v2
# 3) DOCKER_HOST 从哪来(/etc/set-environment,由 NixOS 的 setSocketVariable 生成)
if [ -z "$DOCKER_HOST" -a -n "$XDG_RUNTIME_DIR" ]; then
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"
fi
迁移步骤(一次性,不联网,避开 Docker Hub 被墙)
# 1) 镜像从 rootful 搬到 rootless
DOCKER_HOST=unix:///var/run/docker.sock docker save \
metacubex/mihomo:v1.19.24 ghcr.io/metacubex/metacubexd:v1.247.0 | docker load
# 2) rootless 里建同名网络(compose 里是 external: true)
docker network create ghost_net
# 3) 拆掉 rootful 那份
cd ~/www/mihomo && DOCKER_HOST=unix:///var/run/docker.sock docker compose down
# 4) 用 rootless 起(顺带应用新的 compose 资源限制与新 config.yaml)
cd ~/www/mihomo && docker compose up -d
结论:开机自启不需要改 .nix
rootless 侧的声明式条件本来就齐备:systemctl --user is-enabled docker = enabled、Linger=yes、subuid/subgid 已分配、DOCKER_HOST 由 /etc/set-environment 开机自动导出;再加上容器 restart: always,rootless dockerd 启动时会自动恢复容器。本次实测确认:重启后两个容器自动以 rootless 起来(Up (healthy),cgroup 在 user@1000.service/user.slice/docker-*.scope)。
坑:不要用 docker -H unix:///var/run/docker.sock 这种写法 —— 本机实测会卡死;一律用 DOCKER_HOST=… docker … 前置环境变量。迁移的第 3、4 步之间别让两份同时存在(会抢 7890 端口)。
四、P3:compose 资源限制(改前 → 改后)
| 项 | 改前 | 改后 | 理由 |
|---|---|---|---|
clash cpus | 0.5 | 2.0 | 半核是 TLS 解密 + 多并发的天花板(单节点实测能跑 5MB/s) |
clash mem_limit | 256M | 512M | 35 节点订阅 + 规则集偏紧 |
nofile | (默认) | 65535 | 连接多时文件描述符要够 |
| healthcheck | 无 | wget -qO- http://127.0.0.1:9090/version,30s | 用镜像内自带的 busybox wget(已确认存在) |
| 日志 | 默认(无上限) | json-file,max-size 10m × 3 | 避免日志无限增长 |
现状用量:clash 52MiB / 512MiB、CPU 0.05%;面板 26MiB / 256MiB。
五、P4:配置里不许写死节点名
硬要求:节点只在 proxy-providers 里定义一次,分组一律用 use: + filter / exclude-filter 动态匹配,这样上游订阅节点增减、换订阅都不用改分组:
proxy-providers:
lite20280602: # 订阅只在这里出现一次
type: http
url: "<你的订阅链接>"
interval: 1800
proxy-groups:
- name: 自动选择
type: url-test
use: [lite20280602] # ← 动态:订阅里的节点自动进组
exclude-filter: "(?i)v6|剩余|有效期|到期|官网|Expire" # 排除 v6-only 与到期信息伪节点
url: "https://cache.nixos.org/nix-cache-info"
interval: 300
lazy: false
- name: 日本
type: url-test
use: [lite20280602]
filter: "(?i)日本|japan|-jp-|-ty|-os" # 按国家关键词动态圈节点
exclude-filter: "(?i)v6"
分组清单:节点选择 / 自动选择 / 全部节点 / 下载聚合(load-balance)/ 若干国家组。所有组都指向同一个 provider。
六、P5:调参踩过的具体坑
| 现象 | 原因 | 改法 |
|---|---|---|
| 某个国家组没有节点、类型被改成 COMPATIBLE | filter 关键词没匹配到任何节点 → 空组 | 用节点名的真实写法做 filter(如 日本|japan|-jp-|-ty|-os),改完重启验证组内节点数 |
| 健康检查一直不通过 | expected-status: 204 与被测站点返回 200 不符 | 要么删掉该字段,要么选返回 204 的探测目标 |
| 启动时报废弃项 | global-client-fingerprint 已废弃 | 删除该行 |
| mihomo 警告测试地址 | 用了 HTTP 明文测试地址 | 改 HTTPS |
| 刚启动那几秒代理不可用、吞吐为 0 | 启动瞬间落到订阅里的第一个节点(可能是坏节点),而健康检查是「懒加载」 | lazy: false + interval: 300(启动即测、定时重测) |
加了一条 nixos.org,DIRECT 规则后 nix 又下不动了 | 本机直连 cache.nixos.org 根本不通(直连超时、0 字节) | 删掉该规则 —— 这类域名必须走代理 |
七、交付物与备份
| 文件 | 说明 |
|---|---|
~/www/mihomo/data/config-fixed.yaml | 保守修复版:11 组,纯 filter 动态分组,并行下载规则保留为注释 |
~/www/mihomo/data/config-githubmix.yaml | 激进混合版:21 组、7 个 YAML 锚点、应用分流组、广告拦截、并行下载走聚合 |
~/www/mihomo/data/config.yaml | 当前生效(内容与 config-fixed.yaml 一致,已核对 md5) |
~/www/mihomo/docker-compose.yml | 资源限制 / 健康检查 / 日志轮转 / 面板后端地址可用 .env 覆盖 |
~/www/mihomo/backup-20261006-213321/ | 改动前的 config.yaml 与 docker-compose.yml 备份 |
clash-sweep-results.json | 35 节点逐点吞吐原始数据(在 Hermes scratch 目录,会被定期清理) |
八、验证命令
# 1) 容器跑在哪个 daemon(rootless 才是目标状态)
cat /proc/$(pgrep -x mihomo | head -1)/cgroup # 应见 user.slice/…/docker-*.scope
DOCKER_HOST=unix:///run/user/1000/docker.sock docker ps # 应有 clash / clash-ui
DOCKER_HOST=unix:///var/run/docker.sock docker ps # 应为空
# 2) 当前选中的节点与延迟
curl -s http://127.0.0.1:9091/proxies/自动选择 | python3 -m json.tool | grep -E '"now"|"delay"'
# 3) 真实吞吐(用像样的体积,别用 51 字节的小文件 —— 那只能反映延迟)
curl -x http://127.0.0.1:7890 -o /dev/null --max-time 45 \
-w "均速=%{speed_download} B/s\n" https://proof.ovh.net/files/10Mb.dat
# 4) 健康检查目标本身是否可达
curl -sS -o /dev/null -w "http=%{http_code} 首字节=%{time_starttransfer}s\n" \
-x http://127.0.0.1:7890 https://cache.nixos.org/nix-cache-info
九、遗留与注意
- 实例:同一个荷兰-NL-1,21:20 实测 4243 KB/s,21:55 再测只有 14 KB/s
- 结论:配置能消除灾难性选错并用并行摊平波动,但保证不了持续高速;要稳定只能换更好的订阅/自建,或始终走「下载聚合」组
- 它仍写着「当前实际是跑在 rootful 上」和迁移步骤;实际已经跑在 rootless 且重启后自启正常,该注释应更新为「现状:rootless」
- config-fixed.yaml 里该规则是注释态;要聚合提速就启用「下载聚合」组并把下载类规则指过去
- 代价:同一域名并发多连接,部分站点可能不友好
- compose 里用
${CLASH_API_URL:-http://<宿主局域网IP>:9091},换机器/换网段改.env即可,不用改 compose 文件
- compose 里用