家庭宽带和校园网的公网IP大多是动态分配的。路由器重启、光猫断电或者租约到期,地址就变了。平时感觉不到,一旦家里跑着NAS、监控或个人博客,外网访问就时好时坏。阿里云动态域名解析(DDNS)解决的就是这个问题:脚本定时检查当前出口IP,发现变化就更新域名的A记录,域名本身不用换。这篇文章从准备工作讲到部署,最后说说几类常见故障怎么查。

一、动态IP为什么会让域名失效
域名解析做的事很简单,就是把域名对应到一个IP。记录改了以后,各级DNS会按TTL缓存旧值,所以即便记录已经更新,外部用户仍可能要等一段时间才能拿到新地址。固定IP的服务器不用管这些,动态IP则要把更新频率和TTL一起考虑。
常见的用法大致有这几种:
- 在外面用域名访问家里NAS的文件管理界面;
- 个人开发环境需要临时给外部联调;
- 小团队的内网服务,人在办公地点以外也要能连上;
- 物联网设备只认域名,回传数据时不能依赖固定IP。
二、整个流程怎么跑
典型方案由三部分组成:运行在路由器、树莓派或常开Linux主机上的客户端,阿里云云解析DNS,以及域名下的A记录。客户端定时获取本机出口IP,和上次记录的值比较。有变化时,用AccessKey签名调用云解析接口,改掉对应子域名的记录值。
每一轮的步骤是:读取当前IP;与本地缓存对比,一致就直接退出;不一致则查到记录ID,调用UpdateDomainRecord;成功后把新IP写入缓存。大部分时间脚本只读一次IP就结束了,只有地址真的变化时才会请求API,这样也不容易碰到接口的频率限制。
三、开始前要确认的几件事
- 账号已完成域名实名认证。没认证的域名接不进云解析,这是最容易卡住的一步;
- 域名已接入云解析DNS,或者在注册商后台把NS服务器改成阿里云提供的地址;
- 单独创建一个RAM子账号,只给云解析的读写权限,再生成AccessKey。主账号密钥不要放进脚本;
- 先确认运营商有没有给你公网IP。如果出口是运营商NAT,DDNS能解决地址变化,但外网仍然进不来,这一点要提前想清楚。
权限可以直接用系统自带的AliyunDNSFullAccess,但更推荐自定义策略,只允许对指定域名执行DescribeDomainRecords和UpdateDomainRecord。AccessKey放在环境变量或权限受限的配置文件里,别直接写进脚本,更别推到公开仓库。
四、以Ubuntu主机为例部署
假设家里有台跑Ubuntu的服务器,要维护的是nas.example.com这条A记录。文中的域名和密钥都是占位符,换成自己的即可。
1. 安装依赖
系统需要Python 3和pip,然后安装阿里云官方SDK。生产环境建议把版本号固定下来,避免SDK升级后接口行为变化。
pip3 install alibabacloud_alidns20150109 alibabacloud_tea_openapi requests
2. 编写更新脚本
脚本分三步:获取公网IP,和缓存比较,必要时更新记录。下面是精简版,主要用来理解流程,实际使用时改掉域名相关的常量即可。
import os, json, requests
from alibabacloud_alidns20150109.client import Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_alidns20150109 import models as alidns_models
DOMAIN_RR = "nas"
ROOT_DOMAIN = "example.com"
CACHE_FILE = "/var/lib/ddns/last_ip.json"
def get_public_ip():
return requests.get("https://api.ipify.org", timeout=5).text.strip()
def load_cache():
try:
with open(CACHE_FILE) as f:
return json.load(f).get("ip")
except (FileNotFoundError, json.JSONDecodeError):
return None
def save_cache(ip):
os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True)
with open(CACHE_FILE, "w") as f:
json.dump({"ip": ip}, f)
def build_client():
config = open_api_models.Config(
access_key_id=os.environ["ALIYUN_AK_ID"],
access_key_secret=os.environ["ALIYUN_AK_SECRET"],
endpoint="alidns.aliyuncs.com")
return Client(config)
def find_record_id(client):
req = alidns_models.DescribeDomainRecordsRequest(
domain_name=ROOT_DOMAIN, rrkeyword=DOMAIN_RR, type="A")
resp = client.describe_domain_records(req)
for r in resp.body.domain_records.record:
if r.rr == DOMAIN_RR:
return r.record_id
return None
def update_record(client, record_id, ip):
req = alidns_models.UpdateDomainRecordRequest(
record_id=record_id, rr=DOMAIN_RR, type="A", value=ip, ttl=600)
client.update_domain_record(req)
if __name__ == "__main__":
current = get_public_ip()
if current == load_cache():
raise SystemExit(0)
client = build_client()
rid = find_record_id(client)
if rid is None:
raise SystemExit("未找到对应的A记录,请先在控制台手动创建一条")
update_record(client, rid, current)
save_cache(current)
print(f"已更新 {DOMAIN_RR}.{ROOT_DOMAIN} -> {current}")
脚本从环境变量读取AccessKey,代码里没有明文密钥。TTL设为600秒是个折中:太短会增加解析查询量,太长则切换生效慢。
3. 设置定时任务
用cron每5分钟执行一次就够了。编辑crontab,添加下面一行,路径按实际情况调整:
*/5 * * * * ALIYUN_AK_ID=你的AccessKeyId ALIYUN_AK_SECRET=你的AccessKeySecret /usr/bin/python3 /opt/ddns/update.py >> /var/log/ddns.log 2>&1
不过把密钥直接写进crontab并不稳妥,同机其他用户可能通过进程列表看到它。更好的做法是放在只有root可读(权限600)的环境文件里,再由脚本或启动方式加载。
五、一个家庭NAS的实际例子
有位用户在家里部署了群晖NAS,运营商大约每隔几天换一次公网IP。之前每次出门前都要登录路由器查看地址,很麻烦。后来他在树莓派上运行这个脚本,让nas.example.com指向当前出口IP,在路由器里为NAS开放对应端口,并在NAS上启用HTTPS和双因素认证。日常访问基本感觉不到切换。
这个例子也说明了一点:DDNS只保证域名指向正确的IP,不保证端口一定能访问。如果运营商封了入站端口,或者处在多层NAT之下,就要再考虑内网穿透、VPN或专线。
六、常见故障怎么查
解析没有变化
先看脚本日志,确认UpdateDomainRecord是否调用成功。然后用dig或nslookup直接查询阿里云的权威DNS。如果权威记录已经是新IP,而本地仍是旧值,多半是本地或上游resolver的缓存还没过期,等待TTL时间即可。
认证失败
遇到InvalidAccessKeyId或SignatureDoesNotMatch,先核对AccessKey是否完整复制,有没有多出空格。签名对时间戳很敏感,系统时间偏差过大也会报这个错,检查一下NTP同步状态。
找不到记录
脚本只负责更新已有的A记录。如果控制台里还没有这个子域名,需要先手动添加一条任意IP的记录,之后由脚本接管更新。
IP频繁变化
如果运营商一天之内反复更换IP,可以拉长检测间隔,或者在脚本里加上连续几次检测结果一致才更新的判断,减少无效调用。
七、安全上的几个底线
DDNS一旦让外网能访问家里的服务,安全边界就要重新检查一遍。
- RAM子账号只保留必要权限,AccessKey定期轮换,旧密钥及时禁用;
- 对外服务启用强认证,不要指望域名不被人知道就安全;
- 开启云解析的操作日志,定期查看是否有异常的记录变更;
- 给关键记录配置监控或告警,记录被改动时能第一时间知道。
部署DDNS本身并不复杂,理清客户端、云解析和权限三者的关系就能跑起来。长期要盯的,是可达性和安全。