
家里的宽带每隔一段时间公网IP就会变尤其是不满运营商最长在线时长之后强制断线重拨的那种老域名解析记录一直是旧IP访问家里NAS或者是远程桌面全都得靠临时记IP来连烦得不行。后来我把域名托管到了阿里云云解析直接用了阿里云官方出的alidns命令行工具来做动态解析配合一个小脚本把检测IP、比对缓存、调API更新记录这几件事串起来彻底不用再手动改解析了。这篇文章就记录我这次完整折腾的过程工具选型怎么想、AccessKey权限怎么收缩、脚本怎么写、定时任务怎么挂以及实际跑起来踩过的坑。内容适合家里有动态公网IP、手上域名在阿里云云解析的用户也适合刚接触alidns想快速落地的开发者。1. 项目思路拆解动态IP自动更新的完整链路1.1 痛点本质解析记录和出口IP之间的“失联”没有固定公网IP的家庭宽带IP变化是有周期的。运营商会重启你的拨号连接可能是48小时可能是72小时也可能是随机的时间点。每次重拨拿到的新IP都和上一轮不一样而DNS解析记录里写死的还是上一次的那个A记录值。结果就是域名明明解析正常但访问指向的IP已经不再是你的设备了。这个问题的本质是DNS记录需要跟随出口IP的变化同步刷新。手动做这件事要两步先查当前公网IP再去控制台改解析记录。一次还行长期下来就是纯粹的重复劳动而且白天人不在家、夜里突然断线重拨根本来不及手动改。所以我们需要的是一个能自动完成“检测到IP变化 → 更新云解析记录”的本地脚本或者服务。1.2 为什么选择阿里云官方alidns而不是自研脚本方案其实可以从两个维度看调用API的路径、触发更新的方式。调用API这一步第一反应是直接用阿里云云解析的OpenAPI来做写一个Python脚本调用SDK更新对应记录。这个思路没有问题但实际上做的时候会发现很多边缘细节要处理记录不存在时先创建记录存在时才能更新不同记录类型要分别处理SDK初始化参数要填对区域等等。alidns这个官方命令行工具把这些底层逻辑都封装好了一条命令就能完成解析记录的增删改查天然比“自己从零写SDK调用”省事。触发更新这一步我见到的方案也很多有人在路由器上挂插件有人在软路由里跑容器还有人直接写cron定时任务。我选择最简单、依赖最少的一条路alidns命令行工具加本地bash脚本再挂系统cron。不需要常驻进程不需要额外容器环境脚本五分钟或者十分钟跑一次足够应对IP变化了。结论很直接用官方工具alidns承担API调用细节用脚本做轮询比对用cron做定时触发。这套组合逻辑清晰、故障点少、出问题也好排查。2. 核心细节解析alidns工具的工作机制与关键配置2.1 alidns是什么以及它怎么工作alidns是阿里云云解析配套的命令行工具。它底层调用的就是云解析DNS的OpenAPI你给它的参数最终会转换成对接口的请求。比如更新一条A记录它内部会先查域名下的记录列表找到匹配record参数的记录ID再调用更新接口把value改成新的IP。因为它本身基于阿里云官方的SDK封装所以你不需要关心签名算法、请求串格式、错误重试这些问题。工具支持的操作大致包括add新增解析、update更新解析、delete删除解析、describe查询解析。日常动态DNS场景最常用的就是describe和update。工具的运行环境要求很普通Python 3.6 以上就可以通过pip安装后直接命令行调用。我实际用下来它就是一条命令的事情例如alidns --record home.example.com --method update --value 你的新IP执行完它会在终端里输出解析记录ID、当前记录值这些关键信息返回结果清晰可控。2.2 安装与环境准备过程装工具这块没什么玄学直接pip安装pip3 install alidns装完之后检查一下命令是否可用alidns --help当终端能正常输出参数帮助信息工具就算装好了。这里提醒一句如果机器上有多个Python版本pip3对应的Python版本最好和alidns要求的版本一致否则会出现“装成功但命令找不到”的经典问题。另外一个不太显眼的细节是alidns支持通过--config指定一个配置文件这样每次执行不用把AccessKey信息写在命令行里既安全又省事。我建议在正式用之前就把配置文件的目录规划好比如放在当前用户目录下的隐藏文件夹里权限收紧到只有自己可读写。2.3 AccessKey的创建原则绝不用主账号调任何云API都需要鉴权alidns也是一样。它通过AccessKey ID和AccessKey Secret来确定你的身份和权限。这里有一条非常硬的安全原则坚决不要用自己的主账号AccessKey跑这种定时脚本。主账号AK等于整个账号的最高权限一旦脚本所在机器被入侵或者机器上的配置文件被读到等于把你的整个云上资产都送给对方了。正确做法是走RAM子账号授权。登录阿里云控制台在访问控制里创建一个子用户只给它开放云解析DNS的操作权限。然后针对不同用途权限还能再细分。个人DDNS场景最小权限只要两条查询解析记录、更新解析记录。把权限范围收缩到这两条即使AK泄露了能做的事情也就剩下改解析记录这一件破坏面可控。创建子账号的时候会生成一组独立的AccessKey这组AK专门给alidns用。拿到的AccessKey ID和Secret要妥善保管后面配置和脚本里都要用。2.4 记录类型与域名前置检查做IPv4动态解析对应的记录类型就是A这类记录把域名指向一个IPv4地址。如果你的场景以后需要做IPv6动态解析那就是AAAA记录。alidns对A和AAAA都支持所以这个工具往后扩展空间也够。开始之前一定要确认几件事域名已经添加到阿里云云解析的域名列表里主域名状态正常需要更新的子域名记录已经存在比如我在控制台里先手动建了一条home.example.com的A记录初始值随便填一个。为什么要先手动建一条因为update操作是基于已有记录来更新的如果你要用update就必须保证目标记录已经存在。先手动建一条脚本调试阶段更新成功与否一眼就能看出来比上来就用add更直观。3. 实操过程与核心环节实现3.1 配置文件与认证信息组织这一步我们落地一心一用的认证文件。我在当前用户目录下建了一个.alidns目录然后在里面放了一个config.json内容大概是这样的{ access-key-id: 你的AccessKey ID, access-key-secret: 你的AccessKey Secret, region-id: cn-hangzhou, record: home.example.com, type: A, ttl: 600 }配置文件写好之后立刻处理权限chmod 600 ~/.alidns/config.jsonchmod 600意味着只有文件属主也就是当前用户能读和写这个文件其他用户一律不可读、不可写、不可执行。这是对密钥类文件最基本的保护一定不要跳过。配置里为什么要写region-id因为阿里云的OpenAPI调用需要指定区域云解析产品目前通用的区域是cn-hangzhou这个一般来说不用改保持默认就行。ttl设的是解析记录的缓存时间600秒就是10分钟个人动态解析场景够用。3.2 手动更新与查询验证配置好之后先用describe方式确认工具能正常读取配置并查询记录alidns --config ~/.alidns/config.json --method describe执行完如果正常终端会输出当前解析记录的详情包括记录ID、记录类型、记录值、TTL等信息。看到自己手动建的那条A记录躺在那说明配置和认证链路都通着。接着做一次手动更新先随便指定一个新IP值alidns --config ~/.alidns/config.json --method update --value 8.8.4.4看到工具返回更新成功的提示之后再去控制台刷新一下域名解析列表确认记录值已经变成8.8.4.4。这一步验证的重点是工具确实有权限更新该记录配置文件、记录名、域名关系全部正确。验证完再把记录值手动改回真正的公网IP或者等脚本自己处理都可以。3.3 自动检测公网IP并更新解析工具验证没问题之后剩下的就是让整个流程自动化。自动化的核心是三步检测当前出口公网IP、和上次记录的值做比对、不一致才触发更新。获取公网IP我试过几个渠道比较稳的是这类纯文本输出接口比如icanhazip.com、ifconfig.me。实际用下来单一路由可能有偶发超时所以我习惯写一个备选逻辑第一个接口拿不到就用第二个。示例脚本大概长这样#!/bin/bash # 配置区 CONFIG$HOME/.alidns/config.json CACHE_FILE$HOME/.alidns/last_ip.txt LOG_FILE$HOME/.alidns/ddns.log # 函数获取当前公网IP get_current_ip() { IP$(curl -s -m 10 https://ipv4.icanhazip.com | grep -Eo [0-9]{1,3}(\.[0-9]{1,3}){3} | head -n 1) if [ -z $IP ]; then IP$(curl -s -m 10 https://ifconfig.me | grep -Eo [0-9]{1,3}(\.[0-9]{1,3}){3} | head -n 1) fi echo $IP } # 读取上次记录的IP LAST_IP if [ -f $CACHE_FILE ]; then LAST_IP$(cat $CACHE_FILE) fi CURRENT_IP$(get_current_ip) if [ -z $CURRENT_IP ]; then echo $(date %Y-%m-%d %H:%M:%S) 获取公网IP失败本次终止 $LOG_FILE exit 1 fi if [ $CURRENT_IP $LAST_IP ]; then echo $(date %Y-%m-%d %H:%M:%S) IP无变化跳过更新 $LOG_FILE exit 0 fi # 执行更新 alidns --config $CONFIG --method update --value $CURRENT_IP $LOG_FILE 21 # 更新成功后再记录新的IP到缓存 if [ $? -eq 0 ]; then echo $CURRENT_IP $CACHE_FILE echo $(date %Y-%m-%d %H:%M:%S) 解析已更新为 $CURRENT_IP $LOG_FILE fi脚本的主要思想就是用缓存文件做状态记忆。每次跑完发现IP变了才去调API不变就什么都不做。这样定时任务跑得很轻也不会有多余的API调用。每行日志都带时间戳后面排查问题全靠这个日志文件。脚本存到本地之后记得赋予执行权限chmod x /usr/local/bin/ddns-update.sh3.4 手动执行一次完整流程脚本写完先不急着挂定时任务手动跑一遍。我习惯分两阶段验证先看输出再看日志。执行脚本之后检查终端有没有报错信息然后查看日志文件尾部tail -n 20 ~/.alidns/ddns.log如果看到“解析已更新为xxx”马上去阿里云控制台刷新解析列表确认记录值已经变成当前公网IP。这样一次全链路验证就完成了。验证之后把缓存文件里的IP改成当前值再跑一次脚本应该能触发“IP无变化跳过更新”的日志。这一步说明比对逻辑是正常的。3.5 挂上cron定时任务定时任务我用的是系统自带的crontab。先打开当前用户的定时任务编辑界面crontab -e然后在文件末尾加一行*/5 * * * * /usr/local/bin/ddns-update.sh /dev/null 21*/5 * * * *代表每5分钟执行一次脚本。动态IP变化的频率一般是以小时甚至天为单位的5分钟检查一次已经非常充裕。如果想更保守一点改成*/10或者*/15也可以没必要把间隔压得太狠。这里有一个实际执行中容易踩的坑cron环境变量和当前shell环境不一样。如果你在终端手动执行脚本一切正常但挂上cron之后不出日志多半是因为脚本执行环境里找不到alidns命令的路径。解决办法简单粗暴在脚本最前面把PATH环境变量显式指到位比如export PATH/usr/local/bin:/usr/bin:/bin:$PATH或者干脆在脚本里用alidns命令的绝对路径。我自己的环境里alidns装在/usr/local/bin/alidns脚本里直接用绝对路径调用一劳永逸。4. 脚本的容错加固与安全细节4.1 多域名批量更新的扩展方案如果家里不只一个域名要指向同一个IP甚至有好几个子域名脚本就得稍微改造一下。最直接的做法是把要更新的记录名放在一个数组里循环处理RECORDS(home.example.com nas.example.com vpn.example.com) for REC in ${RECORDS[]}; do alidns --config $CONFIG --record $REC --method update --value $CURRENT_IP $LOG_FILE 21 done不要忽略--record参数的妙用配置文件里如果写了固定的record命令行里再传--record就会覆盖配置里的记录名这样同一个配置账户能管理多个解析。我实际就是这么干的一台小机器上同时维护好几个子域名的映射脚本复用一个逻辑。4.2 权限加固与密钥保护实操安全这个话题必须单独展开。从前面创建RAM子账号开始我自己总结了一套动态DNS场景的密钥保护基线第一子账号权限最小化。在RAM授权时系统策略里有一串现成的权限策略比如AliyunDNSFullAccess这种完整访问权限虽然方便但权限过大。我更推荐自定义一个精简策略只放两条Action{ Version: 1, Statement: [ { Effect: Allow, Action: [ alidns:DescribeDomainRecords, alidns:UpdateDomainRecord ], Resource: * } ] }这个策略只允许查询和更新解析记录无法新增删除整个域名也无法做收费操作。对个人DDNS来说已经够用。第二配置文件绝不进仓库。如果你的脚本和配置是放在代码仓库里的一定要用.gitignore把包含密钥的文件排除掉。.alidns目录、config.json这类东西坚决不能提交一旦提交过再撤回就不干净了因为历史提交里还留着密钥文本。第三密钥轮换要常态化。即使权限设好了也建议每隔半年或者一年重新生成一次AccessKey。RAM控制台里的操作不复杂禁用旧的、新建一对、替换到配置文件里。轮换周期自己掌握但不要“创建后用到天荒地老”。4.3 记录类型和TTL调优的边界很多人会在解析更新时纠结TTL到底设置多少。从动态场景来看TTL设置越短DNS服务器缓存旧值的时间越短IP变化后生效越快。但TTL也不是越低越好太短的TTL会导致全球各地的DNS查询频繁回到权威服务器徒增压力。对于家庭应用场景我实测下来600秒这个默认值已经足够遇到IP变化最长十分钟左右就能全面生效。如果对生效速度特别敏感可以调到120秒代价是解析查询量略微上升。日常使用完全没必要追着60秒跑。再说A和AAAA两类记录。本文的项目标题只要求IPv4所以全部流程都围绕A记录进行。如果你的设备已经接入了IPv6环境也允许使用IPv6地址访问那可以仿照同样的逻辑再写一个AAAA记录更新流程alidns工具本身支持--type AAAA参数改动成本很低。5. 常见问题与排查技巧实录5.1 高频报错与解决方案对照表实操过程中可能会碰到各种报错我整理了一份对照表基本都是容易踩到的高频问题现象大概率原因处理方式提示AccessKey ID不存在配置里的AK信息填写有误核对AccessKey ID和AccessKey Secret是否复制完整注意别混进空格提示权限不足RAM子账号没有授权DNS权限到RAM控制台给子账号增加自定义或系统DNS权限策略提示域名记录不存在目标记录没有在云解析里创建过先在控制台手动创建一条对应记录再执行更新命令找不到alidnsPython环境或PATH未配置正确检查安装时候的Python版本用绝对路径调用alidns脚本能跑但日志为空cron环境变量差异导致curl或alidns无法执行脚本开头显式声明PATH或者全部使用绝对路径curl获取公网IP超时出口网络不稳定或接口被临时限流在脚本里配置多个备用IP接口依次尝试更新成功但访问还是不通本地或运营商DNS缓存了旧值清本地DNS缓存等待TTL时间过后再测试5.2 排查思路从现象定位到具体环节如果问题不是直接报错而是“日志显示更新成功但实际访问不了”这就要按链路拆开排查了。我习惯按下面的顺序逐一排除先确认本机当前出口IP是不是真的已经变了。直接浏览器访问icanhazip.com这类页面看看和脚本获取到的是否一致。如果不一致说明脚本获取IP这一段出了偏差可能是curl解析到了IPv6优先地址或者走了代理。再确认域名解析值是否真的更新成脚本日志里的值。在控制台解析列表里直接看记录值如果控制台显示的就是新IP但本机访问不通那就是本地DNS缓存还没过期。用nslookup或者dig指定公共DNS服务器查询能看到权威解析值已经生效。最后才是网络路径问题。如果域名解析到的新IP能ping通但对应端口不通多半是家里路由器没做端口转发或者设备防火墙拦截。这个已经不是DNS的范畴了但也常被误认为是脚本更新失败值得在排查时提前区分开。5.3 个人坚持的几条运维习惯跑这个动态解析脚本已经几个月了有几条小的运维习惯我特别想分享。日志一定要留。很多人的脚本就是一行alidns --method update跑完就算结束不出问题还好一旦出现“某个时间段解析不对”没有日志你根本回忆不起来当时发生了什么。我在脚本里把每次执行的时间、动作、结果都写进日志成本极低排查时价值极高。缓存文件和配置文件的路径尽量固定。我全放在~/.alidns/下面后面做备份也好、迁移机器也好打包一个目录就全带走了不用到处找配置。定时任务的运行状态也要自己记一笔。cron本身不给你发成功日志我隔三差五会tail一眼脚本的日志文件确保它一直在正常巡检。这个动作习惯了之后你会对家里网络的IP变化周期、稳定性都有一个直觉认识以后再做别的自动化项目也非常顺手。我自己用这套方案之后最大的感受就是很多东西不用过度工程化路由器插件、独立容器这些方案各有各的好处但一个工具加一个脚本的轻量组合在“保持简单、易于维护”这个维度上几乎无敌。alidns顺着官方API的丝滑让整个动态解析逻辑收敛成一条命令。以后如果再遇到IP变化的问题脚本自己就把活干完了我只需要偶尔打开日志看看它又“默默”更新了几次仅此而已。最后分享一个小技巧如果你临时想知道当前配置指向的IP和域名解析值是否一致直接用一条命令查出来对比效率最高echo 当前出口IP$(curl -s https://ipv4.icanhazip.com) alidns --config ~/.alidns/config.json --method describe两行输出放在一起一眼就知道有没有追平。这套玩法我已经跑稳很久了省下来的时间完全可以拿去做别的更有意思的事情。