ホーム ブロードバンドがパブリック ネットワーク IP を申請した後、IP アドレスは動的になります。便宜上、固定ドメイン名を使用して独自のサーバーにアクセスできるように、ドメイン名を申請して DDNS を設定する必要があります。覚えにくく、頻繁に変更される IP アドレスを使用する必要はありません。
1. ドメイン名の申請
無料のサードレベルドメイン名を申請できる場所はいくつかありますが、便宜上、 Tencent Cloudでdigital.xyzドメイン名を購入しました。これは年間8元で、これも安いです。
無料のドメイン名解決機能を利用して設定する
サブドメインレベル5レベル
2 つのロードバランサー
URL転送2
サブドメインの数は無制限
個人的にはドメイン名に対する需要はあまりなく、現状ではクラス3Aのドメイン名しか設定されておらず、解像度は無料で十分で、ddns機能は自分でスクリプトを書いて実現する必要があります。
2. スクリプト作成
これは Python で書かれており、dnspod API を通じて個人ドメイン名の DNS 解決レコードを取得し、それを外部ネットワーク アドレスと比較し、異なる場合は DNS レコードを更新し、5 ~ 10 分ごとにスクリプトを実行します。
2.1 外部ネットワークアドレスを取得する
https://myip.ipip.netにアクセスして、自分の IP アドレスを取得します。
2.2 ドメイン名解決情報の取得
dnspod API を使用します。手順については、Tencent Cloud のドキュメントを参照してください: https://docs.dnspod.cn/api/call-requency/
2.2.1 トークンの取得
https://console.dnspod.cn/account/token/tokenにアクセスして秘密キーを作成します。
完了後、プログラム内でIDとTOKENを使用してAPIにアクセスできるようになります。
2.2.2 機能
ドメイン名の取得、ドメイン名の追加、ドメイン名の削除、レコードの取得、レコードの追加、レコードの削除、レコードの変更の機能をリクエストのpostメソッドで実現しました。
レコード取得関数にはパラメータとしてドメイン名が必要です
レコードを削除するには、パラメーターとしてレコード ID とドメイン名が必要です。レコード ID は、レコード取得関数を通じて取得されます。
レコードの変更に必要なパラメータは、ドメイン名、サブドメイン名、レコード ID、変更する値、レコード タイプ、および回線タイプです。
レコードを追加するときに同一のサブドメイン名を 2 つしか追加できない場合、その理由は負荷分散の制限です。
具体的なコードは次のとおりです。
"""
访问和修改dnspod域名的类
get_domain无需参数,输出login_token对应的用户拥有的域名。
域名下包含记录,使用getrecord(domain_list)获取,每条记录对应一个子域名,也是最常用的。
对仅拥有一个域名一个公网ip的用户来说,日常使用仅需要在ip地址发生变化时,更新每条记录就可以了。
"""
import requests
import logging
import re
from dnspod_setting import Settings
from time import sleep
class Dnspod:
"""
操作dnspod域名的类
"""
def __init__(self, login_token):
self._login_token = login_token
self._format = 'json'
# 设置 logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
self._logger = logging.getLogger(__name__)
handler = logging.FileHandler(Settings.log_file, encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self._logger.addHandler(handler)
self._add_domain_URL = 'https://dnsapi.cn/Domain.Create' # 添加域名
self._del_domain_URL = 'https://dnsapi.cn/Domain.Remove' # 删除域名
self._add_record_URL = 'https://dnsapi.cn/Record.Create' # 添加记录
self._alter_record_URL = 'https://dnsapi.cn/Record.Modify' # 修改记录
self._get_record_URL = 'https://dnsapi.cn/Record.List' # 获取记录列表
self._del_record_URL = 'https://dnsapi.cn/Record.Remove' # 删除记录
self._get_domain_list_URL = 'https://dnsapi.cn/Domain.List' # 获取域名列表
@staticmethod
def get_outer_ip():
"""
获取外网ip
:return:
"""
ip = ''
for count in range(10): # 重试10次
try:
res = requests.get('https://myip.ipip.net', timeout=5).text
ip = re.findall(r'(\d+\.\d+\.\d+\.\d+)', res)
ip = ip[0] if ip else ''
except:
pass
if ip: # 一旦ip不为空,直接返回
return ip
sleep(1)
return ip
def get_domain_list(self):
"""
获取域名列表
:return: 域名列表: ['xxx.xx', 'xxx.xx'] or []
"""
try:
r = requests.post(self._get_domain_list_URL, data={'login_token': self._login_token,
'format': self._format})
response_record_json = r.json()
if response_record_json['status']['code'] == '1':
domains_list = [doms['name'] for doms in response_record_json['domains'] if doms['status'] == 'enable']
for domain in domains_list:
self._logger.info(f"域名:{domain}")
return domains_list
else:
return []
except Exception as e:
self._logger.error(e)
def add_domain(self, domains_list):
"""
添加域名
:param domains_list:['xxx.xxx.xxx', 'xx2.xxx.xxx']
:return: True or False
"""
try:
for domain in domains_list:
r = requests.post(self._add_domain_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
})
response_record_json = r.json()
if response_record_json["status"]["code"] == "1":
self._logger.info(
f'域名:{response_record_json["domain"]["domain"]},添加成功;域名ID:{response_record_json["domain"]["id"]}')
else:
self._logger.error(f'域名:{domain},添加失败,错误信息:{response_record_json["status"]["message"]}')
except Exception as e:
self._logger.error(e)
def del_domain(self, domains_list):
"""
删除域名
:param domains_list:['xxx.xxx.xxx', 'xx2.xxx.xxx']
:return: True or False
"""
try:
for domain in domains_list:
r = requests.post(self._del_domain_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
})
response_record_json = r.json()
if response_record_json["status"]["code"] == "1":
self._logger.info(f'删除域名:{domain}成功.')
else:
self._logger.error(f'删除域名:{domain},添加失败,错误信息:{response_record_json["status"]["message"]}')
except Exception as e:
self._logger.error(e)
def add_record(self, domain, sub_domain_list, value, record_type='A', record_line_value='默认'):
"""
添加域名和解析记录
:param domain: 要添加记录的域名
:param sub_domain_list: 要添加的子域名列表
:param record_type: 添加记录类型:默认 A
:param record_line_value: 线路类型, 使用 ‘默认’即可
:param value: 要添加的值, A类型是ip地址。
:return:
"""
try:
for sub_domain in sub_domain_list:
r = requests.post(self._add_record_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
'sub_domain': sub_domain,
'record_type': record_type,
'record_line': record_line_value,
'value': value,
})
response_record_json = r.json()
if response_record_json["status"]["code"] == "1":
self._logger.info(f'域名:{domain},成功添加记录:{response_record_json["record"]["name"]} ')
else:
self._logger.error(
f'域名:{domain},添加:{sub_domain},记录失败,错误信息:{response_record_json["status"]["message"]}')
except Exception as e:
self._logger.error(e)
def get_record(self, domain):
"""
获取域名下的记录
:param domain: "xxx.xxx.xx"
:return:
"""
records_list = []
try:
r = requests.post(self._get_record_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
})
response_record_json = r.json()
if response_record_json['status']['code'] == '1':
self._logger.info(
f"域名:{response_record_json['domain']['name']},"
f"共有:{response_record_json['info']['sub_domains']}个子域名"
f"和:{response_record_json['info']['record_total']}条解析记录.")
for record in response_record_json['records']:
if record['type'] == 'A': # 只修改A类型的记录,避免修改dnspod默认记录。
records_list.append({
'domain': domain,
'id': record['id'],
'name': record['name'],
'line': record['line'],
'type': record['type'],
'value': record['value']
})
except Exception as e:
self._logger.error(e)
return []
return records_list
def alter_record(self, domain, sub_domain, record_id, value, record_type='A', record_line='默认'):
"""
修改解析记录
:param domain: 域名,不可缺
:param sub_domain: 子域名,可选,默认值为@
:param record_id: 记录id
:param value: 值
:param record_type: 记录类型,默认 A
:param record_line: 线路类型,使用‘默认’即可
:return:
"""
try:
r = requests.post(self._alter_record_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
'sub_domain': sub_domain,
'record_id': record_id,
'record_type': record_type,
'record_line': record_line,
'value': value,
})
response_record_json = r.json()
if response_record_json['status']['code'] == '1':
self._logger.info(f"修改记录{record_id}成功。")
else:
self._logger.error(f"记录ID:{record_id}修改失败,错误信息: {response_record_json['status']['message']}。")
except Exception as e:
self._logger.error(e)
def del_record(self, domain, record_id):
"""
删除解析记录
:param domain: 域名,
:param record_id: 记录id,通过get_record获取
:return:
"""
try:
r = requests.post(self._del_record_URL, data={'login_token': self._login_token,
'format': self._format,
'domain': domain,
'record_id': record_id,
})
response_record_json = r.json()
if response_record_json['status']['code'] == '1':
self._logger.info(
f"您正在解析记录,ID为:{record_id},"
f"删除状态值为:{response_record_json['status']['code']}, "
f"信息为:{response_record_json['status']['message']}")
else:
self._logger.error(
f"您正在解析记录,ID为:{record_id},"
f"删除状态值为:{response_record_json['status']['code']}, "
f"信息为:{response_record_json['status']['message']}")
except Exception as e:
self._logger.error(e)
def update_ip():
"""
更新公网ip地址,定期运行保证域名正确,
因为只有一个域名,只有一个ip,所以每次运行,获取域名的A类型解析记录,将所有记录ip修改为外网ip
:return:
"""
pod = Dnspod(Settings.login_token)
ip_public = pod.get_outer_ip()
if ip_public:
records = pod.get_record(Settings.domain)
for record in records:
if ip_public == record['value']:
print('地址未改变,无需更改')
else:
pod.alter_record(record['domain'], record['name'], record['id'], ip_public)
if __name__ == '__main__':
update_ip()