📝 Podman 容器域名解析故障排查与修复笔记

1. 问题现象

在 Orange Pi Zero 3(开发板二,ARM64 架构,Podman 4.3.1)上使用 podman-compose 部署服务(如 alistfrpc)并加入同一个自定义桥接网络(znet)时,出现以下异常:

  • alist 容器内 ping frpc 无法解析为局域网内部 IP(如 10.89.0.2),而是直接穿透到公网公共 DNS,解析出了外网 IP(如 74.208.14.186)。
  • 在网络配置中查看,该网络明明已显示 "dns_enabled": true

2. 核心病灶排查与推演

诊断一:容器内解析被拒绝(REFUSED)

在容器内指定网关 IP(10.89.0.1)进行 nslookup 测试:

podman exec alist nslookup frpc 10.89.0.1

错误返回:

** server can't find frpc: REFUSED

推论: 网关 53 端口上有 DNS 服务在运行,但它显式拒绝了容器的请求,或者该服务处于半瘫痪状态。

诊断二:底层 CNI 缺少 dnsname 插件

运行 podman network ls 时系统抛出警告:

WARN[0000] Error validating CNI config file /etc/cni/net.d/znet.conflist: [failed to find plugin "dnsname" in path [...]]

推论: 当前 Podman 使用的是老旧的 CNI 网络后端。自定义网络虽开启了 DNS 开关,但由于系统缺失 dnsname 二进制插件,导致本地 DNS 劫持转发器根本没有正常运转。

诊断三:宿主机 53 端口遭遇“恶霸”占用

补齐 dnsname 插件源码并编译后,启动容器遭遇报错:

Error: plugin type="dnsname" failed (add): ... dnsmasq failed with "\ndnsmasq: failed to create listening socket for 10.89.0.1: Address already in use\n"

通过 netstat -tunlp | grep :53 抓出真凶:

netstat -tunlp | grep :53
# 或者如果 netstat 没装,用 ss 命令:
ss -tunlp | grep :53

tcp        0      0 0.0.0.0:53            0.0.0.0:* LISTEN      1125/dnsmasq

推论: 宿主机上开启了另一个 dnsmasq 服务(通常由 Clash 或系统流控服务拉起),它霸道地监听了 0.0.0.0:53(所有接口)。这导致 Podman 的 dnsname 插件尝试在虚拟网关 10.89.0.1:53 上摆摊时,遭遇端口冲突。


3. 终极解决方案(正攻法)

整个修复流程分为两步:本地编译补齐插件 $\rightarrow$ 隔离宿主机端口霸占

第一步:在本地编译补齐 dnsname 插件

由于 Orange Pi Zero 3 是 64 位系统(aarch64),不能直接从 32 位系统(armv7l)复制二进制文件,最稳妥的方式是本地源码编译:

  1. 安装编译工具链(Go 语言与 Make):

下载二进制文件
https://github.com/containers/dnsname/releases/tag/v1.3.0
解压tar -xzf dnsname-1.3.0.tar.gz 后,编译文件

apt update && apt install -y golang-go make
  1. 进入 dnsname 源码目录并执行编译:

    make
    

注:即使遇到 fatal: not a git repository 警告可直接忽略,Go 编译器已成功在 bin/ 目录下生成了对应的二进制文件。

  1. 将编译好的二进制可执行文件移动到 CNI 插件专属目录并赋予权限:

    mkdir -p /usr/libexec/cni
    cp bin/dnsname /usr/libexec/cni/
    chmod +x /usr/libexec/cni/dnsname
    

第二步:修改宿主机 dnsmasq 配置,释放虚拟网卡端口

限制宿主机老旧 dnsmasq 的全盘通吃行为,让它交出 Podman 虚拟网卡的 53 端口。

  1. 打开或新建宿主机配置文件:

    nano /etc/dnsmasq.conf
    
  2. 在末尾追加以下两行核心参数:

    # 强制 dnsmasq 只绑定它真正监听的物理网卡接口,不要霸占 0.0.0.0
    bind-interfaces
    
    # 指定只在本地环回口、物理网卡上听包(把 eth0 换成你 ip a 看到的真实物理网卡名)
    interface=lo,eth0
    
  3. 保存退出(Ctrl+O 回车,Ctrl+X),并重启宿主机 DNS 服务:

    systemctl restart dnsmasq
    

4. 清理环境与验证启动

完成上述底层网络修复后,建议对 Podman 缓存进行彻底清洗,防止旧的错误配置死灰复燃。

1. 环境大清洗

# 强行删除所有容器
podman rm -fa

# 强行删除所有自定义网络
podman network rm $(podman network ls -q) 2>/dev/null

# 抹除磁盘上的 CNI 残留网络缓存(核心:防止残留配置锁死旧 IP 报错)
rm -rf /var/lib/cni/networks/*

2. 重新拉起 Compose 服务

回到你的项目目录(如 /data/frp),直接执行:

podman-compose up -d

此时,Podman 的 dnsname 成功占领了空闲出来的 10.89.x.1:53 端口,容器顺利启动,不会再报任何 Address already in use 错误。

3. 最终验证

进入 alist 容器,直接通过容器名 ping 同网络下的 frpc

podman exec alist ping frpc

预期正确结果:
容器内部完美秒通,并准确指向内部私有桥接 IP(如 10.89.x.x),容器域名自动解析大功告成!


💡 备选方案(掀桌子法)

如果未来不想折腾任何底层的 DNS 插件与端口冲突,可以直接在 podman-compose.yml 中利用 extra_hosts 绕过 DNS 解析,在容器层面硬编码写死映射:

services:
  alist:
    image: docker.io/xhofe/alist:latest
    # ... 其他配置
    extra_hosts:
      - "frpc:10.89.0.2" # 强行在 alist 的 /etc/hosts 中注入映射,不再询问任何 DNS 服务器

标签: none

添加新评论