【终极版】Hetzner 流量监控自动重建
Q:重建后为什么 IP 不是一样的?
A:这是一个非常正常的现象,这是 ** 云服务器(Cloud Computing)** 的核心机制决定的。
核心原因:IP 是“借”来的,不是“买断”的
在 Hetzner(以及 AWS、阿里云等几乎所有云厂商)中,IP 地址和服务器实例是解耦的:
当你创建服务器时:Hetzner 从他们巨大的“IP 池”里随机捞一个闲置的 IP 借给你用。
当你删除服务器时 (
server.delete()):你相当于“退房”了。这个 IP 会立刻归还回到池子里,洗干净等待分配给下一个客户。当你重建服务器时 (
client.servers.create()):Hetzner 再次从池子里随机捞一个 IP 给你。因为之前的 IP 可能已经被别人拿走了,或者还在冷却中,所以你几乎 100% 会拿到一个新的 IP。
这就像你去住酒店:你退房了(删机),下次再来住(重建),前台大概率会给你分一个新的房间号(新 IP),除非你专门付钱“包房”。
这种机制对你有好处吗?
对于你的这个“流量自动重置”项目来说,IP 变了其实是一件好事:
彻底的身份重置:新 IP 意味着你在网络上是一个全新的身份。如果你之前的 IP 因为跑流量被某些网站限速或拉黑了,新 IP 就是一条好汉。
规避关联:新 IP 能够确保你的新服务器和旧服务器在逻辑上没有任何关联,这对于“刷流量”或“跑业务”来说通常更安全。
如果我非要保持 IP 不变怎么办?
如果你因为域名解析或者其他原因,必须使用同一个 IP,Hetzner 也是支持的,但需要加钱并且修改配置。
这个功能叫 Primary IP (保留 IP)。
操作思路(仅供了解):
你需要在 Hetzner 后台专门购买一个 Primary IP(IPv4 通常需要额外月租,约 €0.5/ 月)。
这个 IP 就变成了你账户里的固定资产,即使没有服务器挂在上面,它也是你的。
修改脚本:在
client.servers.create的时候,不能让它自动分配 IP,而是指定使用你购买的这个primary_ip_id。
❌ 我不建议你在这个项目里这么做,原因如下:
增加成本:要多花钱。
可能影响流量计算:Hetzner 的流量限额通常是跟着服务器实例走的。如果你一直用同一个固定 IP,虽然理论上新服务器是新的 20TB,但为了保险起见(避免风控或逻辑混淆),更换 IP 是最稳妥的“清零”方式。
既然 IP 变了,我怎么连接?
因为你已经有 Telegram 机器人了,这完全不是问题:
看机器人通知:每次重建成功,机器人都会发给你
新 IP: 1.2.3.4。哪吒探针:因为你用了探针,新服务器启动后,探针会自动把新 IP 上报给面板,你甚至都不用管,面板上的链接会自动更新。
结论:习惯就好,这是云服务器自动化的常态。IP 变了说明你的自动化脚本真的在工作!
Q:但是我要去 vertex 里面修改或者重新添加这个新的 IP 的 QB 才行啊,还是麻烦
A:确实,如果还要手动去 Vertex 里面改 IP,那这根本算不上“自动化”,太痛苦了。
要解决这个问题,有两个方案:“不花钱的方案(DDNS)” 和 “花小钱的方案(保留 IP)”。
鉴于你已经在用 Docker 和 Python 脚本了,我强烈推荐方案一(DDNS),这是 PT 圈子的标准玩法。
方案一:自动更新域名 (DDNS) —— 推荐 🔥
原理:
你准备一个域名(比如
hz.yourdomain.com),托管在 Cloudflare。我们在脚本里加一段代码:每次重建完服务器,脚本自动去 Cloudflare 把这个域名的解析记录改成新的 IP。
Vertex 设置:在 Vertex 里,把下载器的地址从
IP改成域名hz.yourdomain.com。
这样不管 IP 怎么变,Vertex 只要认准这个域名就在行了。
操作步骤
1. 准备工作 (Cloudflare)
你需要有一个托管在 Cloudflare 的域名。
获取 Zone ID:在 Cloudflare 域名概览页右下角可以看到
Zone ID。获取 API Token:
点右上角头像 -> My Profile -> API Tokens -> Create Token。
选择模板 Edit zone DNS。
生成后复制 Token。
创建一条初始记录:
在 DNS 设置里,先随便添加一条 A 记录,比如
hz指向随便一个 IP(脚本一会儿会自动改它)。关键点:把那个小云朵(Proxy)关掉(变成灰色),仅 DNS 模式。否则 Vertex 连不上 QB 的端口。
2. 修改 OVH 上的脚本 (main.py)
我们需要在脚本里增加一段“通知 Cloudflare 改 IP”的代码。
编辑脚本:
Bash
nano ~/hetzner_monitor/main.py
直接替换为以下最终极版本代码:
Python
# -*- coding: utf-8 -*-
import time
import threading
import telebot
import requests
from hcloud import Client
from hcloud.images.domain import Image
from hcloud.server_types.domain import ServerType
from hcloud.locations.domain import Location
# ================= ⚠️ 配置区域 (请仔细检查) =================
# 1. Hetzner 基础配置
HETZNER_TOKEN = "你的_HETZNER_API_TOKEN"
SERVER_NAME = "My-Server" # 必须与 Hetzner 后台名称完全一致
SNAPSHOT_ID = 12345678 # 你的快照 ID
LOCATION = "nbg1"
SERVER_TYPE = "cx22"
TRAFFIC_LIMIT_TB = 18.0 # 流量阈值
CHECK_INTERVAL = 300 # 5分钟检查一次
# 2. Telegram 配置
TG_BOT_TOKEN = "你的_TG_BOT_TOKEN"
TG_CHAT_ID = "你的_TG_CHAT_ID"
# 3. Cloudflare DDNS 配置
CF_ENABLE = True # 是否开启
CF_API_TOKEN = "你的_CF_API_TOKEN"
CF_ZONE_ID = "你的_CF_ZONE_ID"
CF_RECORD_NAME = "hz.yourdomain.com" # 完整域名
# 4. 提醒分级
NOTIFY_LEVELS = [10, 20, 30, 40, 50, 60, 70, 80, 90]
# ==========================================================
LIMIT_BYTES = TRAFFIC_LIMIT_TB * 1024 * 1024 * 1024 * 1024
client = Client(token=HETZNER_TOKEN)
bot = telebot.TeleBot(TG_BOT_TOKEN)
rebuild_lock = threading.Lock()
current_notify_level = 0
def update_cloudflare(new_ip):
"""更新 Cloudflare DNS"""
if not CF_ENABLE: return "DNS功能未开启"
print(f"📡 正在向 Cloudflare 推送新 IP: {new_ip}")
headers = {
"Authorization": f"Bearer {CF_API_TOKEN}",
"Content-Type": "application/json"
}
try:
# 获取记录ID
list_url = f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records?name={CF_RECORD_NAME}"
resp = requests.get(list_url, headers=headers).json()
if not resp.get('success'):
return f"❌ CF API 错误: {resp.get('errors')}"
if not resp['result']:
return f"❌ 域名记录不存在: {CF_RECORD_NAME} (请先在 CF 后台手动添加一条A记录)"
record_id = resp['result'][0]['id']
# 更新记录
update_url = f"https://api.cloudflare.com/client/v4/zones/{CF_ZONE_ID}/dns_records/{record_id}"
data = {
"type": "A",
"name": CF_RECORD_NAME,
"content": new_ip,
"ttl": 60,
"proxied": False
}
update_resp = requests.put(update_url, headers=headers, json=data).json()
if update_resp.get('success'):
return f"✅ DNS 已更新 -> {new_ip}"
else:
return f"❌ DNS 更新失败: {update_resp.get('errors')}"
except Exception as e:
return f"❌ 网络请求异常: {str(e)}"
def get_server_usage():
"""获取流量 (带错误详情)"""
try:
server = client.servers.get_by_name(SERVER_NAME)
if server is None:
return None, "❌ 未找到服务器 (名称错误或正在重建)"
current_usage = server.outgoing_traffic
usage_percent = (current_usage / LIMIT_BYTES) * 100
return current_usage, {
"tb": current_usage / (1024 ** 4),
"gb": current_usage / (1024 ** 3),
"percent": usage_percent,
"ip": server.public_net.ipv4.ip
}
except Exception as e:
return None, f"❌ API连接失败: {str(e)}"
def perform_rebuild(source="自动监控"):
global current_notify_level
if not rebuild_lock.acquire(blocking=False): return
try:
bot.send_message(TG_CHAT_ID, f"🔄 **[{source}]** 开始重建...", parse_mode="Markdown")
# 1. 删除
try:
server = client.servers.get_by_name(SERVER_NAME)
if server:
server.delete()
print("旧服务器已删除,等待 15s...")
time.sleep(15)
except Exception as e: print(f"删除跳过: {e}")
# 2. 重建
print(f"正在创建新服务器 (Snapshot: {SNAPSHOT_ID})...")
new_server = client.servers.create(
name=SERVER_NAME,
server_type=ServerType(name=SERVER_TYPE),
image=Image(id=SNAPSHOT_ID),
location=Location(name=LOCATION)
)
new_ip = new_server.server.public_net.ipv4.ip
current_notify_level = 0
# 3. 更新 DNS
dns_result = update_cloudflare(new_ip)
msg = f"✅ **重建完成**\nIP: `{new_ip}`\n{dns_result}"
bot.send_message(TG_CHAT_ID, msg, parse_mode="Markdown")
print("重建流程结束,冷却 60s...")
time.sleep(60)
except Exception as e:
bot.send_message(TG_CHAT_ID, f"❌ 重建炸了: {e}")
print(f"重建严重错误: {e}")
finally:
rebuild_lock.release()
# --- Telegram 命令 ---
@bot.message_handler(commands=['start', 'help'])
def h(m):
if str(m.chat.id) == TG_CHAT_ID:
bot.reply_to(m, "🤖 监控运行中\n/ll - 查流量\n/rebuild - 强制重建")
@bot.message_handler(commands=['liuliang', 'll', 'status'])
def s(m):
if str(m.chat.id) != TG_CHAT_ID: return
u, d = get_server_usage()
if u is not None:
bot.reply_to(m, f"📊 流量: **{d['tb']:.4f} TB** ({d['percent']:.2f}%)\nIP: `{d['ip']}`", parse_mode="Markdown")
else:
bot.reply_to(m, f"⚠️ 查询失败: {d}")
@bot.message_handler(commands=['rebuild'])
def r(m):
if str(m.chat.id) != TG_CHAT_ID: return
threading.Thread(target=perform_rebuild, args=("手动指令",)).start()
# --- 监控循环 ---
def monitor_loop():
global current_notify_level
print("🚀 监控线程启动...")
# === 启动自检 (开机先刷一次DNS) ===
print("🔍 正在执行开机自检...")
u_start, d_start = get_server_usage()
if u_start is not None:
print(f"✅ API 连接成功,当前 IP: {d_start['ip']}")
print(update_cloudflare(d_start['ip']))
# 初始化通知等级
for l in NOTIFY_LEVELS:
if d_start['percent'] >= l: current_notify_level = l
else:
print(f"⚠️ 开机自检失败: {d_start}")
# ================================
while True:
try:
u, d = get_server_usage()
if u is not None:
# 成功获取数据
print(f"[{time.strftime('%H:%M')}] 状态: {d['percent']:.2f}% | IP: {d['ip']}")
# 检查分级提醒
for l in NOTIFY_LEVELS:
if d['percent'] >= l and l > current_notify_level:
bot.send_message(TG_CHAT_ID, f"⚠️ 流量提醒: {l}% 已用 ({d['tb']:.4f} TB)")
current_notify_level = l
# 检查超标
if u > LIMIT_BYTES:
perform_rebuild("流量超标")
else:
# 获取失败 (这里会打印错误原因!)
print(f"[{time.strftime('%H:%M')}] ⚠️ 获取失败: {d}")
except Exception as e:
print(f"主循环异常: {e}")
time.sleep(CHECK_INTERVAL)
if __name__ == "__main__":
# 1. 启动监控线程
t = threading.Thread(target=monitor_loop, daemon=True)
t.start()
# 2. 启动机器人
print("🤖 正在连接 Telegram...")
try:
bot.get_me() # 测试连接
print("✅ Telegram 连接成功,开始监听...")
bot.send_message(TG_CHAT_ID, "🚀 Hetzner 监控机器人已上线 (终极版)")
bot.infinity_polling()
except Exception as e:
print(f"❌ Telegram 连接失败 (检查 Token 或 网络): {e}")
# 即使 TG 挂了,监控线程依然要跑,防止流量超标
while True: time.sleep(60)3. 重启容器
保存退出后,执行:
cd ~/hetzner_monitor
# 1. 重新构建 (确保 requests 等库都在)
docker build -t hetzner-bot .
# 2. 重启
docker rm -f hetzner-monitor
docker run -d --name hetzner-monitor --restart always hetzner-bot
# 3. 立即查看日志
docker logs -f hetzner-monitor
4. 最后一步:修改 Vertex 设置
进入你的 Vertex 面板。
找到你添加的那个下载器(Hetzner 那台)。
把 Host/IP 这一栏,改成你的域名:
hz.yourdomain.com。保存。
完工!
以后流程变成了:
服务器炸了 -> 重建。
脚本拿到新 IP -> 告诉 Cloudflare。
Cloudflare 更新解析。
Vertex 实际上连的是域名,它每分钟会解析一次域名,自动就连上新 IP 了。
方案二:花钱买固定 IP (保留 IP)
如果你实在不想折腾域名,或者觉得 DNS 生效有 1 分钟延迟很不爽,可以选择这个方案。
购买 IP:
Hetzner 后台 -> Select Project -> Primary IPs -> Buy Primary IP。
选择 IPv4,Location 选你服务器所在的机房(如
nbg1)。花费:约 €0.5 / 月。
记住这个 Primary IP 的 ID。
修改脚本:
你需要修改 main.py 里创建服务器的那一段代码,告诉它“别随机分配 IP,用我买的这个”。
你需要先安装库更新支持:pip install hcloud (容器里已有)
代码修改处(client.servers.create 部分):
Python
# 需要引入新的类 from hcloud.server_types.domain import ServerType from hcloud.images.domain import Image from hcloud.locations.domain import Location from hcloud.primary_ips.domain import PrimaryIP # <--- 新增 from hcloud.servers.domain import PublicNetRequest # <--- 新增 # ... # 你的 Primary IP ID (在 Hetzner 后台看) MY_STATIC_IP_ID = 1234567 # 创建时指定 IP client.servers.create( name=SERVER_NAME, server_type=ServerType(name=SERVER_TYPE), image=Image(id=SNAPSHOT_ID), location=Location(name=LOCATION), # ⬇️ 强制指定使用这个固定 IP public_net=PublicNetRequest( ipv4=PrimaryIP(id=MY_STATIC_IP_ID) ) )
总结:
不想花钱:用方案一(Cloudflare DDNS),脚本我都给你写好了,换上 Token 就能用。
想要极致稳定:用方案二(保留 IP),每个月多花几块钱人民币,Vertex 永远不用动。
Q:好的,但是我在浏览器上面输入我解析好的 xxxxxx 这个域名是打不开,这是怎么回事?
A:这是一个非常普遍且正常的现象,请不要慌张。
根据你之前的日志,脚本和 DNS 更新已经 100% 成功了。你现在在浏览器打不开,通常是以下三个原因之一(90% 是因为第一个):
1. 端口问题(最常见原因)🚨
浏览器默认只访问 80 (HTTP) 或 443 (HTTPS) 端口。
但你的服务器如果主要是跑 qBittorrent (QB) 或 哪吒探针,它们通常不监听这两个默认端口,而是有特定的端口。
qBittorrent 默认 WebUI 端口通常是:
8080哪吒探针 面板端口通常是:
8008SSH 端口是:
22(浏览器打不开)
解决方法:
请尝试在域名后面加上端口号访问:
试试:
http://xxxxxx:8080(如果是 QB)试试:
http://xxxxxx:8008(如果是探针面板)
2. HTTPS/SSL 证书问题
我们在脚本里设置了 Cloudflare 为 DNS Only (灰云,不代理)。
这意味着 Cloudflare 不会 为你提供免费的 HTTPS 证书。
如果你直接访问
https://xxxxxx,而你的服务器上没有手动配置 SSL 证书,浏览器会直接报错(连接被拒绝或证书无效)。解决方法:请务必使用
http://开头(不要s),除非你自己手动在服务器里配置了证书。
3. 服务没启动
虽然服务器重建成功了,但你原来的 qBittorrent 或 Nginx/Caddy 并没有设置成 “开机自启”。
快照恢复后,服务器是开机了,但软件没自动跑起来。
解决方法:
请通过 SSH 登录新 IP (xx.xx.xx.xx),手动检查一下服务状态:
Bash
# 检查 QB 是否在运行 (假设是 qbittorrent-nox)
ps -ef | grep qb
# 或者检查端口占用情况
netstat -tulpn
如果发现没运行,你需要手动启动它一次,并设置 systemctl enable 让它下次自动启动。
如何验证“域名解析”是不是真的好了?
要区分是“脚本没生效”还是“网站没配置好”,最简单的办法是:
在你的电脑上打开 CMD (Windows) 或 终端 (Mac),输入:
Bash
ping 域名
如果返回的 IP 是
xx.xx.xx.xx(和你日志里的一样),说明 脚本立大功了,域名完全没问题。打不开网页纯粹是因为你没加端口,或者软件没开。
Q:果然,去掉 s 和加了端口就行,那么我现在是不是就要去 vertex 里面把 QB 的链接改成我的域名链接
A:没错!这就是最后一块拼图! 🧩
你现在 必须 去 Vertex 把连接地址改成域名,这样你的整套“全自动刷流系统”才算真正闭环。
请按照以下步骤操作:
1. 进入 Vertex 修改设置
打开你的 Vertex 面板。
找到 下载器 (Download Clients) 菜单。
点击你那台 Hetzner QB 的 编辑 按钮。
找到 Host (主机 / 地址) 这一栏:
原来的填法:
xx.xx.xx.xx(或者旧 IP)现在的填法:
域名
Port (端口) 这一栏:
保持不变(比如
8080,或者你刚才在浏览器里输入的那个端口)。
点击 保存 或 测试连接。
这样改完后的神奇效果 ✨
从现在开始,你的系统逻辑变成了这样:
爆炸重开:脚本删机重建,拿到新 IP(比如
1.1.1.1)。自动指路:脚本立刻告诉 Cloudflare:“嘿,
域名现在是1.1.1.1了!”无感连接:Vertex 去连接 QB 时,它只认
域名这个名字。Cloudflare 会告诉 Vertex 新 IP 是1.1.1.1,Vertex 就自动连上了。
以后无论你重建 100 次,IP 变 100 次,你都不需要再动 Vertex 一根手指头了! 🥂
快去改吧,改完你就彻底解放双手了!




