想要更好的阅读体验,可以转我的个人博客。
DDNS 简介
DDNS(Dynamic Domain Name Server),它的作用是将用户的动态IP绑定到一个域名上去。
这样就算你的服务器IP发生了变化,用户仍然可以使用你的域名找到你的服务器。
阿里云提供了一套API,可以让你用编程的方式实现 DDNS,但是需要你的域名是在阿里云上申请的。
感谢我的室友借用给我测试用的域名。
一些可能用到的库
pip install aliyun-python-sdk-corepip install aliyun-python-sdk-alidnspip install pyyaml
获取和缓存 IP 地址
先写一个简单的工具类,可以获取当前电脑的公网IP
地址,有很多提供这类服务的网站,本例程采用/dyndns/getip。
获取 IP 之后最好再把它缓存在一个文件中。
之所以需要缓存是因为阿里云更新两条一样的IP时会报错,我们可以提前缓存,然后下次调用更新服务之前借用缓存的内容,判断当前的 IP 是否无变化。
定义 IPManager 类
定义一个IPManager
类,可以获取本机的公网IP
地址,并使用文件进行缓存。
from urllib.request import urlopenimport loggingclass IPManager:def __init__(self, file_cache=".ipbuffer"):self.ip = ""self.file_cache = file_cachedef get_current_ip(self):# 获取当前的 IPwith urlopen('/dyndns/getip') as response:self.ip = str(response.read(), encoding='utf-8').replace("\n", "")logging.info("current ip: " + self.ip)return self.ipdef sync_cache(self):# 同步当前的 IP 到缓存with open(self.file_cache, "w") as f:f.write(self.ip)logging.info("sync cache ip: " + self.ip)def get_cache(self):# 获取缓存的内容with open(self.file_cache, "r") as f:old_ip = f.read()logging.info("get cache ip: " + self.ip)return old_ip
程序默认使用.ipbuffer
文件存储 IP,我觉得我们还需要先创建这个文件,不然运行的时候可能会报错。
可以使用下面的函数检查和创建一个文件,支持递归创建:
import osdef check_file(filename):# 获取父文件夹dirname = os.path.dirname(filename)if not os.path.exists(dirname) and dirname != "":# 递归创建父文件夹os.makedirs(dirname)# 创建文件with open(filename, "w") as f:f.write("")
IPManager 的简单使用
def main():# 创建一个 IPManagerip_manager = IPManager()# 获取当前的 IPcurrent_ip = ip_manager.get_current_ip()# 如果 IP 已经缓存就返回if ip_manager.get_cache() == current_ip:return# 更新 IP 缓存ip_manager.sync_cache()
这个程序可以获取IP并且在IP无缓存或者IP更新的时候更新缓存。
获取 accessKeyId 和 accessKeySecret
云账号登录RAM控制台。在左侧导航栏的人员管理菜单下,单击用户。在用户登录名称/显示名称列表下,单击目标RAM用户名称。在用户AccessKey区域下,单击创建新的AccessKey。摘抄自 阿里云文档
创建连接阿里云的客户端
from aliyunsdkcore.client import AcsClientprofile = {"accessKeyId": "xxx","accessKeySecret": "xxx","regionId": "cn-hangzhou"}client = AcsClient(profile["accessKeyId"], profile["accessKeySecret"], profile["regionId"])
把上一步的accessKeyId
、accessKeySecret
填进去。
在regionId
填写你的区域号,关于regionId
的说明,可以见 阿里云官方文档。
我们需要借助client.do_action_with_exception
这个函数来发送请求到阿里云。
域名解析记录查询
之所以需要加一步域名解析记录查询是为了校验我们的域名是否已经被其他的 IP 绑定了。
from aliyunsdkalidns.request.v0109.DescribeDomainRecordsRequest import DescribeDomainRecordsRequestimport jsonimport loggingdef describe_domain_records(client, record_type, subdomain):logging.info("域名解析记录查询")request = DescribeDomainRecordsRequest()request.set_accept_format('json')request.set_Type(record_type)request.set_DomainName(subdomain)response = client.do_action_with_exception(request)response = str(response, encoding='utf-8')result = json.loads(response)logging.debug(result)return result
client
是上一步创建的客户端。
record_type
比较复杂,简单来说是DNS域名解析
的解析类型。我们这里使用A记录
就好了。
{% note info %}
常见的 DNS解析类型
A: 将主机名(或域名)指向一个 IPv4 地址
AAAA: 将主机名(或域名)指向一个 IPv6 地址
CNAME: 将域名指向另一个域名
{% endnote %}
subdomain
填你的域名就好了。
# 调用举例describe_domain_records(client, "A", "tuenity.xyz")
添加域名解析记录
from aliyunsdkalidns.request.v0109.AddDomainRecordRequest import AddDomainRecordRequestimport loggingimport jsondef add_record(client, priority, ttl, record_type, value, rr, domain_name):logging.info("添加域名解析记录")request = AddDomainRecordRequest()request.set_accept_format('json')request.set_Priority(priority)request.set_TTL(ttl)request.set_Value(value)request.set_Type(record_type)request.set_RR(rr)request.set_DomainName(domain_name)response = client.do_action_with_exception(request)response = str(response, encoding='utf-8')result = json.loads(response)logging.debug(result)return result
priority
告诉域名解析服务,按照priority
从小到大的顺序对记录搜索,搜索到匹配的记录后,就停止搜索priority
值更大的记录,对于拥有相同priority
的记录将通过weight
再次选择
。
虽然阿里云并不提供weight
的设置接口,但是你要知道它是个什么东西。
对于拥有相同priority
的多条记录,weight
给出了选择某条记录的几率,值越大,被选中的概率就越大,合理的取值范围为0-65535
。
ttl
( Time to live ),当用户在访问一个域名的时候,并不是每次都需要去解析一遍的,DNS服务器
会在用户当地的递归DNS服务器
上缓存一次,在ttl
的时间长度内失效。一般设置 “600”。
record_type
同上一步。
value
就是你的IP地址
。
rr
,阿里云的rr
是主机头的意思,一般设置 “www”。
domain_name
就是你的域名。
更新域名解析记录
from aliyunsdkalidns.request.v0109.UpdateDomainRecordRequest import UpdateDomainRecordRequestimport loggingimport jsondef update_record(client, priority, ttl, record_type, value, rr, record_id):logging.info("更新域名解析记录")request = UpdateDomainRecordRequest()request.set_accept_format('json')request.set_Priority(priority)request.set_TTL(ttl)request.set_Value(value)request.set_Type(record_type)request.set_RR(rr)request.set_RecordId(record_id)response = client.do_action_with_exception(request)response = str(response, encoding='utf-8')logging.debug(response)return response
和上一步的函数接口几乎一摸一样,不过多解释了。
需要注意,不一样的是record_id
。这个需要describe_domain_records
函数的返回值。
des_result = describe_domain_records(client, "A", "tuenity.xyz")
使用des_result["TotalCount"]
就可以查处现在有多少条记录绑定在这个域名上了。
如果没有,我们就需要调用add_record
,否则就调用update_record
。
record_id
可以通过des_result["DomainRecords"]["Record"][0]["RecordId"]
获取。
改造、封装建议
使用 yaml 来作为配置文件使用 python 自带的日志 logging把查询、更新、添加域名解析记录封装成一个类获取完整的代码
Github 项目地址