0%

AWS Lightsail 修改 IP 并更新 DNS 记录

购买了 AWS Lightsail 的 VPS 后,使用了一段时间,发现偶尔会出现 IP 不可访问的情况,参考 AWS 的开发文档,用 Python 写了一个更换 IP 的脚本。
同时参考了 Cloudflare API,在更换了 IP 之后,把域名对应的记录也更新一下。

开始使用脚本之前,需要准备一些必要的信息,以下脚本已提供符合格式的假数据,可参考替换。
将密钥信息明文存储在脚本内是非常危险的行为!

AWS

IAM 管理面板创建用户,附加策略,添加操作 Lightsail 的最小权限,以下是示例的 json 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lightsail:AttachStaticIp",
"lightsail:ReleaseStaticIp",
"lightsail:SetIpAddressType",
"lightsail:GetInstance",
"lightsail:AllocateStaticIp"
],
"Resource": "*"
}
]
}

然后在用户的安全凭证中,生成访问密钥,用于脚本中的 access_key_idsecret_access_key

Amazon Lightsail 实例中,记录以下信息:
vps_name:实例名字
region:实例所在的区域名
ip_name:附加到实例中静态 IP 的名字,脚本会先释放掉这个 IP,然后重新创建同名的 IP 后附加到实例

参考:Creating an IAM user in your AWS account

Cloudflare

  • 获取 API 令牌:
    生成 API 令牌,权限选择允许编辑 DNS,令牌用于脚本中的 Authorization

  • 获取域名区域 ID:
    Cloudflare 管理面板中,找到域名,右侧的 API 栏里的区域 ID,用于脚本中的 zone_uuid

  • 获取域名记录 ID:
    将上面获取到的 Authorizationzone_uuid 填入以下 shell 脚本,运行后找到域名记录对应的 id,用于脚本中的 ipv4_idipv6_id

    1
    2
    3
    4
    5
    6
    zone_uuid="386ece75e4340c05fa147666a991fc31"
    Authorization="Bearer EuWDbgnjW7SY0Y13SerCVfNeBIApDTs3Ou61BMrw"
    curl --request GET \
    --url https://api.cloudflare.com/client/v4/zones/${zone_uuid}/dns_records \
    --header 'Content-Type: application/json' \
    --header "Authorization: ${Authorization}"

参考:List DNS Records

测试 IP 可用性

因为这个 VPS 的主要作用是提供 https 服务,所以测试 IP 可用性使用的是 tcping
这个更换 IP 的脚本主要是在 WSL 2 使用,而 WSL 2 目前并没有 IPv6 支持,所以需要先将 tcping.exe 放到 Windows 或 WSL 2 的 PATH 路径中。
脚本通过 os.popen 调用 ping_cmd 里指定的命令内容执行并获取结果分析。

其他方式(未测试):
pouriyajamshidi/tcping: Ping TCP ports using tcping. Inspired by Linux’s ping utility. Written in Go
EnginEken/tcppinglib: An Easy Way to Measure the Connectivity and Latency with TCP Ping

完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from boto3 import client
from os import popen
from requests import put

edited = False
Data = {
"ping_cmd": {
"4": "tcping.exe -n 20 -w 1 -i 0.2 -4 -p 443 ",
"6": "tcping.exe -n 20 -w 1 -i 0.2 -6 -p 443 ",
},
"zone_uuid": "13f4b0c417156e434898232d6d119aee",
"header": {
"Authorization": "Bearer MGuANoHaTt7RNSrcN3OGtL53SnQocQI8fnuGJkIn",
"Content-Type": "application/json",
},
"access_key_id": "QHWUPQZCKMM37XLP5KWD",
"secret_access_key": "eLWehBnAuhO3Y6f3iuz7UPCQYN9L+mtTqUm277D3",
"vps_name": "vps_name",
"ip_name": "ip_name",
"region": "us-east-2",
"ipv4_name": "v4.dummy.local",
"ipv4_id": "05e41a9cb30e8052b726dae955cc09a2",
"ipv6_name": "v6.dummy.local",
"ipv6_id": "4f09ffdc1e2754a17021b5cf6d5b0571",
}

aws_api = client(
"lightsail",
region_name=Data["region"],
aws_access_key_id=Data["access_key_id"],
aws_secret_access_key=Data["secret_access_key"],
)


def get_instance_data():
try:
return aws_api.get_instance(instanceName=Data["vps_name"])
except Exception as err:
print(str(err))
exit(1)


try:
instance_data = get_instance_data()
print("Test...")
v4_stat = (
popen(Data["ping_cmd"]["4"] + instance_data["instance"]["publicIpAddress"])
.read()
.split("\n")
)
v6_stat = (
popen(Data["ping_cmd"]["6"] + instance_data["instance"]["ipv6Addresses"][0])
.read()
.split("\n")
)
print(
"IPv4: {}\n{}\n{}\nIPv6: {}\n{}\n{}".format(
instance_data["instance"]["publicIpAddress"],
v4_stat[-4].strip(),
v4_stat[-2].strip(),
instance_data["instance"]["ipv6Addresses"][0],
v6_stat[-4].strip(),
v6_stat[-2].strip(),
)
)
except Exception as err:
print(str(err))

if input("Change IPv4?(y/n):") != "n":
try:
aws_api.release_static_ip(staticIpName=Data["ip_name"])
aws_api.allocate_static_ip(staticIpName=Data["ip_name"])
aws_api.attach_static_ip(
staticIpName=Data["ip_name"], instanceName=Data["vps_name"]
)
except Exception as err:
print(str(err))
edited = True

if input("Change IPv6?(y/n):") != "n":
try:
aws_api.set_ip_address_type(
resourceType="Instance",
resourceName=Data["vps_name"],
ipAddressType="ipv4",
)
aws_api.set_ip_address_type(
resourceType="Instance",
resourceName=Data["vps_name"],
ipAddressType="dualstack",
)
except Exception as err:
print(str(err))
edited = True

if edited:
try:
print("update DNS record...")
instance_data = get_instance_data()
print(
"IPv4: {}\nIPv6: {}".format(
instance_data["instance"]["publicIpAddress"],
instance_data["instance"]["ipv6Addresses"][0],
)
)
put(
"https://api.cloudflare.com/client/v4/zones/"
+ Data["zone_uuid"]
+ "/dns_records/"
+ Data["ipv4_id"],
json={
"type": "A",
"name": Data["ipv4_name"],
"content": instance_data["instance"]["publicIpAddress"],
},
headers=Data["header"],
)

put(
"https://api.cloudflare.com/client/v4/zones/"
+ Data["zone_uuid"]
+ "/dns_records/"
+ Data["ipv6_id"],
json={
"type": "AAAA",
"name": Data["ipv6_name"],
"content": instance_data["instance"]["ipv6Addresses"][0],
},
headers=Data["header"],
)
except Exception as err:
print(str(err))

该脚本先是获取了实例的具体信息,并通过定义的命令测试 IP,然后展示测试结果询问是否需要更换 IP。
确认要更换后,将会释放并重新创建静态 IP,然后附加到实例,然后切换一下实例的 IPv6 开关来更新 IPv6 的地址。
更新完成后,重新查询实例的信息,用新的 IP 信息更新 DNS 记录。

测试 IP 那部分,可以改为自动判断,如果丢包达到一定的程度就切换 IP,这样可以作为定时检测脚本来自动更新 IP。

关联文章:在订阅中显示 Lightsail 流量使用情况