ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

树莓派部署ddns-go容器,彻底解决动态IP域名解析难题

树莓派部署ddns-go容器,彻底解决动态IP域名解析难题 其实这一篇是我临时插进来的。前面三期已经把 Nginx、WordPress、Gitea 都容器化跑在树莓派上了证书也续上了按理说服务链路完整了。但我有次在外面折腾半天发现自己写的博客一直打不开回家登录路由器一看宽带的公网 IP 变了。那一刻我才意识到自建服务器真正麻烦的从来不是容器编排而是“域名解析的最后一跳”。当晚我把 ddns-go 用 Docker 容器化部署到了树莓派上从那一刻开始域名解析这件事就再也没手动碰过。这篇文章就是那次部署全过程的记录包括为什么选 ddns-go、容器怎么规划、阿里云和 Cloudflare 的密钥怎么配置、以及和 Nginx 容器联动时踩过的一堆坑。无论是树莓派、NAS 还是任意一台常开小主机只要你想让外面的用户稳定访问家里自建的服务这篇文章都可以当作直接抄作业的手册。我会尽量把每一步的“为什么”也写出来这样你遇到类似问题时能举一反三而不是只复制命令。1. 为什么自建网站卡在“动态 IP”这一关1.1 公网 IP 为什么会变家庭宽带最常见的接入方式是光纤到户光猫或路由器通过 PPPoE 拨号拿到一个公网 IPv4 地址。这个地址并不是永久固定在你家的运营商在宽带接入服务器上维护了一个地址池每次拨号都会动态分配给你一个地址并且设了一个租期。租期到了重新拨号拿到的地址很可能就变了。你可以在路由器后台看到 WAN 口地址但今天看到的是 112.x.x.x明天重启一次光猫可能就变成 223.x.x.x。IPv6 也一样而且更容易踩坑。国内不少地区已经在普及 IPv6 前缀下发但前缀通常也是动态的。再加上设备会启用临时 IPv6 地址很多地址本身就有有效期限制过几小时就可能失效。所以千万不要以为切到 IPv6 就一劳永逸动态性依然是绕不开的前提。这不是某一个运营商的个别现象而是家宽普遍的网络模型。只要你的网站托管在家里就必须接受这个现实公网 IP 是租来的随时可能被收走再换一个。1.2 没有动态解析时外网访问有多痛苦在接入 DDNS 之前我想过很多种“硬扛”的方案也真的用了一阵子直接用 IP 访问。只适合临时调试服务一多就记不住而且 IP 一变所有人都访问不了。每次手动去域名解析控制台改 A 记录。能用但延迟太高。IP 变更通常发生在凌晨或者我不在电脑前的时候用户访问失败半天我才发现体验很差。用路由器自带的 DDNS 功能。很多路由器内置了花生壳、no-ip 这类支持配置简单但它能绑定的服务商很有限。我想用自己的域名、用阿里云解析它就支持不了。借助 frp、ngrok 这类中间转发服务。这类工具确实能解决“没有公网 IP”的情况但所有流量都要先绕到一台有公网 IP 的服务器再转回来多一跳就意味着多一次延迟和带宽瓶颈。对树莓派上的个人站来说我还是希望流量能直接打到家里减少不必要的中间环节。那段时间我的站点状态基本是“薛定谔的可用”刚改完解析能访问过两天又失联。每一次失联都要重复一遍定位问题、登录控制台、修改解析的流程。后来我意识到真正缺的是一个能自动感知 IP 变化、自动更新 DNS 记录的组件。1.3 DDNS 的原理一台自动更新 DNS 记录的“机器人”用个生活化的类比动态 IP 相当于一个经常换手机号的人每次换号都得重新通知一遍通讯录里的所有人。DDNS 工具就是那个通讯录助手检测到你公网 IP 变了就自动把通讯录里的号码更新成最新的所有人都联系得到你。具体到技术层面DDNS 工具其实只做三件事获取当前出口的公网 IPIPv4 和 IPv6 分开处理实时对比这个 IP 和域名当前解析记录是否一致不一致时调用 DNS 服务商的 API把 A 记录或 AAAA 记录更新到最新 IP。这每一步听起来都不复杂但麻烦的地方在于不同服务商的 API 格式千差万别密钥体系也不同。如果自己写脚本每个服务商都要适配一遍用现成工具就必须找支持面广、维护活跃的那种。ddns-go 正好满足这个需求。2. ddns-go 是什么我为什么在众多方案里选了它2.1 一句话介绍 ddns-goddns-go 是一个用 Go 语言写的开源动态域名解析工具支持阿里云、腾讯云、Cloudflare、DNSPod、华为云等多个主流 DNS 服务商同时也支持 Webhook 通知。它最大的特点是部署极其简单启动后打开一个网页管理界面配置好密钥和域名就不用管了。工具本身是单个二进制文件无额外的运行时依赖跑在 Docker 里也很轻。最初吸引我的是它直接用 Web 界面配置不用手写 JSON 配置文件。对于一个需要长期无人值守运行的小工具来说界面做的“傻瓜”反而是优点配置改起来直观出问题也容易排查。2.2 和路由器自带 DDNS、手动脚本相比优势在哪我把自己考虑过的几种 DDNS 方案放在一起对比过方案上手成本可定制性支持服务商资源占用适合场景路由器自带 DDNS极低很低有限路由器固件内完成家用临时访问NAS 自带 DDNS低中中NAS 应用内完成NAS 用户自己写脚本 定时任务中高取决于脚本低想彻底折腾ddns-go低高多很低容器化环境的理想选择我家里路由器比较老固件里的 DDNS 模块已经不再更新支持的服务商少得可怜。NAS 我没有用自己写脚本倒是能跑但每次运营商改 API 或者密钥过期都要自己维护长期看并不省心。ddns-go 的优势在于它把“检测 IP 变化、调用服务商 API、更新记录、发送通知”这一整套流程都封装好了而且 UI 配起来比写脚本直观太多。2.3 树莓派上跑它资源占用真的很低有些人可能担心树莓派本身性能有限再多一个 Docker 容器会不会吃紧。我实测下来ddns-go 容器在树莓派 4B 上正常空闲时内存占用大约 20 到 30 MBCPU 占用基本是 0%。它默认每 5 分钟检查一次 IP 变化检查动作本身也就是一次 HTTP 请求加一次 API 对比整个流程毫秒级完成基本不会对同机运行的 Nginx、数据库等其他容器造成任何可见影响。而且它非常契合 Docker 的运维方式容器重启、日志查看、镜像升级都是标准 docker 命令不用单独为它写 systemd 服务也不需要额外安装 Python 或其他依赖。对树莓派这种 SD 卡空间和金贵性能的设备来说这就很友好。3. 把 ddns-go 容器化部署到树莓派3.1 确认 Docker 环境已经就绪如果树莓派系统是 Debian / Raspberry Pi OS / UbuntuDocker 还没装的话最快的方式是执行官方安装脚本。一条命令能搞定但注意脚本执行完以后要把当前用户加入 docker 组否则后面每次都要 sudocurl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER装好后重新登录一下终端确认版本docker --version docker compose version如果你之前是用较老版本安装的 Docker可能没有docker compose子命令只有docker-compose。这个影响不大只是启动命令从docker compose换成docker-compose。另外有一个很实用的经验如果公共镜像仓库拉取速度很慢建议先按网上教程给 Docker 配置国内镜像加速地址。这个一次性配置对后续拉取任何镜像都有帮助尤其在国内网络环境下属于尽早做受益越大的操作。3.2 方式一先用 docker run 快速跑起来第一步我习惯用 docker run 验证工具本身能不能正常工作先跑起来再说docker run -d \ --name ddns-go \ --restartalways \ --network host \ -v /opt/ddns-go:/root \ jeessy/ddns-go:latest这里逐项解释一下参数避免大家直接抄了但不知道含义-d后台模式运行--name ddns-go给容器起个固定的名字--restartalways树莓派重启后容器自动拉起这是自建服务器的刚需--network host容器使用宿主机网络不单独分配端口映射-v /opt/ddns-go:/root把容器内 ddns-go 的配置目录挂载到宿主机/opt/ddns-go下配置持久化升级容器不会丢。运行完成后在浏览器里访问http://树莓派IP:9876就能看到 ddns-go 的配置界面。注意这里我用了--network host而不是常规的-p 9876:9876这是有讲究的放在 3.4 单独说。3.3 方式二docker-compose 固定配置长期管理更稳docker run 适合验证但不利于长期管理。我最终更推荐用 docker-compose 把配置固化到文件里。在/opt/ddns-go/下新建一个docker-compose.ymlservices: ddns-go: image: jeessy/ddns-go:latest container_name: ddns-go restart: always network_mode: host volumes: - /opt/ddns-go:/root然后在文件所在目录执行docker compose up -d之后升级镜像就两行命令docker compose pull docker compose up -d我建议所有容器都用这种组合管理同一个目录里放好 compose 文件统一处理升级、重启、日志。像我这种树莓派上同时跑着 Nginx、MySQL、WordPress 好几个容器的情况如果不统一管理时间一长自己都可能忘了哪个容器是干嘛的。3.4 为什么 ddns-go 推荐用 host 网络模式而不是桥接模式绝大多数容器我们都用桥接模式也就是-p 端口:端口容器拿到一个独立的 Docker 内网 IP通过 NAT 和宿主机共享网络。但 ddns-go 不太一样它对宿主机网络信息的感知程度要求比较高。桥接模式下容器看见的是 Docker 虚拟网卡的 IP 地址而宿主机真正的 WAN 口信息、物理网卡绑定的 IPv6 地址容器都看不到。对 IPv4 场景来说问题不大ddns-go 可以通过请求外部 API 获取出口公网 IP但 IPv6 就麻烦了——运营商会把 IPv6 地址分配到宿主机的物理网卡上桥接模式下容器要么拿不到要么拿到一个 Docker 内部的临时 IPv6 地址更新出去的 AAAA 记录很可能是错的。host 模式让容器和宿主机共享同一个网络栈。ddns-go 可以像直接在宿主机上运行一样读取网卡信息9876 端口也能直接访问IPv6 记录更新才可能正确。代价是容器的网络隔离性变弱但由于 ddns-go 只开一个配置管理端口而且我会给它设置登录密码这个风险可以接受。4. 配置解析服务商阿里云和 Cloudflare 实战4.1 第一次打开 ddns-go 管理界面浏览器进入http://树莓派IP:9876后会看到一个简洁的后台页面。我强烈建议第一件事就是设置登录用户名和密码。ddns-go 默认的管理界面是完全开放的只要局域网里有人猜到你树莓派 IP就能打开这个页面改配置。家里有多台设备或者有访客网络的环境这一个安全习惯能省很多麻烦。设置完密码后接下来就是选择 DNS 服务商、填写密钥和域名。不同服务商的密钥体系差异很大下面拿出最常见的两个做演示一个是国内的阿里云一个是国外的 Cloudflare思路通了之后其他服务商基本都是同一个套路。4.2 阿里云 AccessKey 的最小权限配置国内用户大概率用的是阿里云域名解析。登录阿里云控制台进入 RAM 访问控制创建一个子用户。创建过程中要勾选“编程访问”因为 ddns-go 需要 AccessKey ID 和 AccessKey Secret。创建成功后你会得到一对密钥务必立刻保存到密码管理器里因为 Secret 只在当时显示一次页面上不会再给第二次机会。接下来最关键的一步授权。千万不要为了省事把子用户加入“AdministratorAccess”管理员组这和前面容器化时不要随意把端口暴露到公网是同一个道理——权限越小越安全。正确做法是给这个子用户添加一条“自定义权限”只允许操作域名解析。阿里云提供了一个现成的系统权限AliyunDNSFullAccess只覆盖云解析 DNS 相关的操作不会让子用户动到 ECS 实例、OSS 存储桶等其他资源。添加这个权限就够了。然后回到 ddns-go 后台DNS 服务商选择“阿里云”填 AccessKeyId、AccessKeySecret在“Domains”里填要解析的域名比如.example.com和www.example.comIPv4 类型勾选 A 记录IPv6 类型勾选 AAAA 记录保存后工具会自动检测并更新解析。阿里云控制台里新版本的产品名可能显示为“云解析 DNS”找到“解析设置”进去管理域名即可。4.3 Cloudflare 的 API Token 配置Cloudflare 的密钥体系和阿里云完全不同。它不鼓励生成一个全局可用的账号密钥而是推荐使用 API Token。登录 Cloudflare 后进入 My Profile → API Tokens → Create Token选 “Edit zone DNS” 这个现成模板。在 Zone Resources 里选择 Include → Specific zone然后把你需要用到的域名加进去。这个模板默认就带有 DNS 记录的编辑权限对 ddns-go 来说已经足够了。如果你有多个域名也可以选择 All zones但基于最小权限原则我建议还是按域名限制万一 token 泄露影响范围可控。创建完成后会得到一串 token把它填到 ddns-go 的对应字段里。注意Cloudflare 的 API Token 不需要再单独填账号邮箱token 本身就能定位到你在 Zone Resources 里指定的域名。4.4 页面里的关键参数到底怎么填除了密钥以外ddns-go 配置页面里还有几个字段值得细看填错了解析也不会正常工作。Domains / 解析记录填法一般是.example.com和www.example.com分开表示主域名本身www是子域名。如果你跑着多个子域名的服务也可以一起填进去每次 IP 变化它会统一更新。获取 IP 方式IPv4 一般选“通过接口获取”工具会请求外部接口查询出口公网 IPIPv6 则可以选“通过网卡获取”或“通过接口获取”。如果运营商没有分配公网 IPv6或者你还没确认网络环境就先不要勾选 IPv6否则硬更新出来的 AAAA 记录可能是无效内网地址。检查间隔默认 5 分钟对个人站来说已经够用。调短会增加请求频率调长意味着 IP 变化后会有较长的空窗期。Webhook很多朋友会忽略这个字段。它可以在 IP 变化或者更新失败时向指定地址发送一条通知。接钉钉、企业微信、Server酱之类的都行我把成功和失败都开了后面会细说。5. 与 Nginx 容器联动打通外网访问链路5.1 先要把路由器端口映射配好域名解析只是第一步它把你家宽出的公网 IP 和域名绑定了。但如果用户访问的 80/443 端口流量进不到树莓派网站依然访问不通。所以第二步是配置路由器端口映射。登录路由器后台找到“端口映射”或“虚拟服务器”菜单不同品牌叫法不同把公网侧的 80 和 443 端口映射到树莓派内网 IP 的 80 和 443 端口。这里有一个重点树莓派如果之前是 DHCP 自动获取 IP建议在路由器里给它绑定一个静态 DHCP 租约或者在树莓派系统里直接配置静态 IP否则树莓派内网 IP 一变端口映射就会失效表现同样是“外网打不开网站”。有些路由器支持 UPnP可以让容器自动发布端口但这种方式通常无法精确控制端口段和映射规则手动固定映射更可控也更安全。5.2 Nginx 反向代理与证书续期配合树莓派局域网里的 80/443 端口流量最终要落到哪个容器上呢我在前面几期已经把 Nginx 容器作为统一的入口所有外部请求都先到 NginxNginx 再根据server_name把请求转发给 WordPress、Gitea 等上游容器。ddns-go 在这里只负责“把域名解析到正确的 IP”它不承担流量代理的职责。Nginx 里关键的 server 块大概长这样server { listen 443 ssl http2; server_name www.example.com; ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; location / { proxy_pass http://wordpress:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这套配置要在动态 IP 时代正常跑通前提就是 ddns-go 已经把域名解析到了正确的 IP。Lets Encrypt 在申请证书时需要通过域名访问到你的 80 端口如果 DNS 解析本身都是错的证书永远也签不下来。所以 ddns-go 其实是整个证书自动化体系的前置条件。有了正常的解析以后我用 certbot 的自动续期配置每 60 天自动续一次证书全程没有人工干预。5.3 完整访问链路到底长什么样把整个链路用文字描述出来会更清楚用户浏览器访问https://www.example.com→ 本地 DNS 查询得到当前公网 IP → 运营商网络把请求路由到你家路由器 → 路由器按端口映射把 80/443 流量转给树莓派 → 树莓派 Docker 里的 Nginx 容器根据server_name命中配置 → Nginx 反代到上游容器返回响应。这条链路只要中间任何一个节点断了表现都是“网站打不开”。所以我排错时会固定一个顺序先看域名解析到了哪里再 ping 公网 IP 看通不通最后去看 Nginx 的 access log 和 error log。顺序固定下来之后排查速度会快很多不用每次都从头瞎猜。6. 常见问题与排查实录6.1 域名解析一直不更新日志也没有报错我遇到过最让人头疼的情况是ddns-go 运行正常日志里也没报错但域名解析就是不更新。后来发现问题出在运营商 NAT 后的 IP 上。有些宽带虽然看起来拨号成功了但 WAN 口 IP 是 100.64.x.x 这种运营商大内网地址外部根本无法直接访问。这时候 DDNS 工具再努力也没用它拿到的“公网 IP”根本不是真正公网可达的 IP。遇到这种网络环境处理办法有两个联系宽带客服确认是否可以开通公网 IPv4如果运营商只提供 IPv6那就走纯 IPv6 域名解析同时在路由器上放行相应的 IPv6 防火墙策略。记住一个判断技巧拨号后如果 WAN 口 IP 是 10.x、100.64.x、172.16.x 这类保留网段基本就是被运营商 NAT 了。6.2 IPv6 记录总是获取不到多半是网络模式的问题IPv6 无法更新在容器部署里特别常见尤其是用默认桥接模式跑 ddns-go 的时候。桥接模式下容器看到的网络接口是 Docker 虚拟网卡它看到的 IPv6 地址往往不是宿主机物理网卡上的真实地址或者干脆没有。这个问题最早让我困惑了很久日志不报错但 AAAA 记录始终更新不上去。解决办法就是我前面反复强调的改用 host 网络模式。然后到 ddns-go 后台把 IPv6 获取方式改成“通过网卡获取”选择宿主机真实的物理网卡一般是 eth0 或者 end0。保存后立刻去看解析记录正常情况下 AAAA 记录很快就出来了。6.3 报错 401 / AccessDenied几乎都是密钥权限问题如果你把密钥填进去之后保存报错或者日志里出现 401 Unauthorized、AccessDenied大概率就是密钥本身的权限不够。阿里云和腾讯云都要去子账户的授权页面添加“DNS 解析管理”相关的系统权限Cloudflare 则要检查 API Token 是否选对了 Zone Resources 和 “Edit zone DNS” 权限。另外有一点要特别提醒API 密钥千万别提交到公开仓库也别贴到聊天工具里到处转发。哪怕只在临时测试里用过一次一旦泄露都建议去控制台禁用并重新生成。密钥管理这个习惯放在 DDNS 场景和放在云服务器场景同样重要。6.4 其他几个容易被忽略的细节整理一份避坑清单我把能想到的都列出来了问题现象验证/修复方案运营商大内网 IPWAN 口 100.64.x.x外网无法访问联系宽带客服确认公网 IPv4或改用 IPv6容器桥接网络IPv6 记录无法更新改用 host 网络模式API 密钥权限不足保存更新报 401授予 DNS 解析管理权限树莓派内网 IP 变化端口映射失效给树莓派设静态 DHCP 租约管理页面无密码局域网内可被任意修改设置登录用户名密码时区未设置日志和证书验证时间漂移容器挂载本地时区或设置 TZ 环境变量还有一个文件权限的细节/opt/ddns-go目录的属主和容器内用户如果不一致可能导致容器没有权限写入配置文件表现是配置保存了但容器重建后配置清零。最省事的办法是把挂载目录的属主改成容器默认用户或者直接 chmod让对应目录可写然后再重启容器验证。7 我后续还会继续优化的几个方向7.1 给 ddns-go 管理页再加一层保护设置密码只是第一步如果树莓派暴露在公网且你通过端口映射把 9876 端口也映射到了公网那管理页的风险就大多了。我的做法是9876 端口绝不映射到公网管理页面只在局域网里使用。如果需要在外网管理 DDNS我会走 Nginx 反代配合 Basic Auth 或者客户端证书。这个习惯我强烈建议你也养成毕竟 DDNS 工具一旦被恶意篡改解析记录就可能被指向钓鱼服务器。7.2 Webhook 通知开了之后心里踏实很多之前我总觉得“DDNS 这东西跑着就行”直到有一次运营商半夜换了 IP我早上起来才发现网站已经失联四五个小时。后来我把 Webhook 打通了接到企业微信群里。现在只要公网 IP 发生变化或者更新失败我手机上立刻就能收到消息。成功通知和失败通知我都会开因为失败通知往往比成功通知更重要它能让我第一时间介入处理而不是等用户发现网站挂了再去排查。7.3 树莓派重启后的快速自检方法最后分享一个小技巧树莓派重启之后怎么快速判断 DDNS 是否正常在局域网里直接执行curl -s https://www.example.com -I | head -n 1如果返回 HTTP 200 或 30x就说明域名解析、路由器端口映射、Nginx 容器整个链路都是通的。如果打不开就按我前面说的顺序逐层检查先看域名解析到的 IP 对不对再看路由器 WAN 口 IP 有没有变最后看 Nginx 日志里有没有上游连接错误。把这三个位置刻在脑子里DDNS 引起的问题基本不会让你卡超过五分钟。
返回列表