Python開發(fā)Telegram Bot實(shí)現(xiàn)服務(wù)器監(jiān)控告警
前言
服務(wù)器出問題了,怎么第一時(shí)間知道?
- 郵件通知?可能漏看
- 短信通知?要錢
- Telegram Bot?免費(fèi)、實(shí)時(shí)、還能遠(yuǎn)程控制
今天用Python開發(fā)一個(gè)服務(wù)器監(jiān)控機(jī)器人。
一、Telegram Bot簡(jiǎn)介
1.1 為什么選擇Telegram
優(yōu)點(diǎn):
- 完全免費(fèi)
- API簡(jiǎn)單易用
- 消息實(shí)時(shí)推送
- 支持群組/頻道
- 可發(fā)送文件、圖片
- 支持命令和按鈕交互
適用場(chǎng)景:
- 服務(wù)器監(jiān)控告警
- CI/CD通知
- 定時(shí)任務(wù)提醒
- 遠(yuǎn)程執(zhí)行命令
- 日志推送
1.2 創(chuàng)建Bot
1. 打開Telegram,搜索 @BotFather
2. 發(fā)送 /newbot
3. 輸入Bot名稱,如:MyServerBot
4. 輸入Bot用戶名,如:my_server_monitor_bot
5. 獲得Token:123456789:ABCdefGHIjklMNOpqrsTUVwxyz
6. 保存好Token!
1.3 獲取Chat ID
方法1:使用@userinfobot
- 搜索@userinfobot
- 發(fā)送任意消息
- 返回你的Chat ID
方法2:API獲取
- 給你的Bot發(fā)送消息
- 訪問:https://api.telegram.org/bot<TOKEN>/getUpdates
- 在返回的JSON中找到chat.id
二、環(huán)境準(zhǔn)備
2.1 安裝依賴
pip install python-telegram-bot pip install psutil # 系統(tǒng)監(jiān)控 pip install aiohttp # 異步HTTP pip install apscheduler # 定時(shí)任務(wù)
2.2 項(xiàng)目結(jié)構(gòu)
telegram_bot/ ├── bot.py # 主程序 ├── config.py # 配置 ├── monitors/ # 監(jiān)控模塊 │ ├── __init__.py │ ├── cpu.py │ ├── memory.py │ ├── disk.py │ └── network.py ├── handlers/ # 命令處理 │ ├── __init__.py │ └── commands.py └── requirements.txt
三、基礎(chǔ)Bot開發(fā)
3.1 Hello World
# bot.py
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
# 配置
TOKEN = "你的Bot Token"
# 命令處理器
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("你好!我是服務(wù)器監(jiān)控機(jī)器人。")
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
help_text = """
可用命令:
/start - 開始
/help - 幫助
/status - 服務(wù)器狀態(tài)
/cpu - CPU使用率
/memory - 內(nèi)存使用
/disk - 磁盤使用
"""
await update.message.reply_text(help_text)
def main():
# 創(chuàng)建應(yīng)用
app = Application.builder().token(TOKEN).build()
# 注冊(cè)命令
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("help", help_command))
# 啟動(dòng)
print("Bot啟動(dòng)中...")
app.run_polling()
if __name__ == "__main__":
main()
3.2 運(yùn)行測(cè)試
python bot.py # 在Telegram中: # 1. 搜索你的Bot # 2. 發(fā)送 /start # 3. 發(fā)送 /help
四、系統(tǒng)監(jiān)控功能
4.1 CPU監(jiān)控
# monitors/cpu.py
import psutil
def get_cpu_info():
"""獲取CPU信息"""
cpu_percent = psutil.cpu_percent(interval=1)
cpu_count = psutil.cpu_count()
cpu_freq = psutil.cpu_freq()
# 每核使用率
per_cpu = psutil.cpu_percent(interval=1, percpu=True)
return {
"percent": cpu_percent,
"count": cpu_count,
"freq": cpu_freq.current if cpu_freq else 0,
"per_cpu": per_cpu
}
def format_cpu_info():
"""格式化CPU信息"""
info = get_cpu_info()
text = f"""
??? CPU狀態(tài)
━━━━━━━━━━━━━━━
總使用率: {info['percent']}%
核心數(shù)量: {info['count']}
當(dāng)前頻率: {info['freq']:.0f} MHz
各核心使用率:
"""
for i, p in enumerate(info['per_cpu']):
bar = "█" * int(p / 10) + "?" * (10 - int(p / 10))
text += f" 核心{i}: [{bar}] {p}%\n"
return text
4.2 內(nèi)存監(jiān)控
# monitors/memory.py
import psutil
def get_memory_info():
"""獲取內(nèi)存信息"""
mem = psutil.virtual_memory()
swap = psutil.swap_memory()
return {
"total": mem.total / (1024**3), # GB
"used": mem.used / (1024**3),
"available": mem.available / (1024**3),
"percent": mem.percent,
"swap_total": swap.total / (1024**3),
"swap_used": swap.used / (1024**3),
"swap_percent": swap.percent
}
def format_memory_info():
"""格式化內(nèi)存信息"""
info = get_memory_info()
bar = "█" * int(info['percent'] / 10) + "?" * (10 - int(info['percent'] / 10))
text = f"""
?? 內(nèi)存狀態(tài)
━━━━━━━━━━━━━━━
使用率: [{bar}] {info['percent']}%
物理內(nèi)存:
總量: {info['total']:.1f} GB
已用: {info['used']:.1f} GB
可用: {info['available']:.1f} GB
交換分區(qū):
總量: {info['swap_total']:.1f} GB
已用: {info['swap_used']:.1f} GB ({info['swap_percent']}%)
"""
return text
4.3 磁盤監(jiān)控
# monitors/disk.py
import psutil
def get_disk_info():
"""獲取磁盤信息"""
partitions = []
for part in psutil.disk_partitions():
try:
usage = psutil.disk_usage(part.mountpoint)
partitions.append({
"device": part.device,
"mountpoint": part.mountpoint,
"total": usage.total / (1024**3),
"used": usage.used / (1024**3),
"free": usage.free / (1024**3),
"percent": usage.percent
})
except:
continue
return partitions
def format_disk_info():
"""格式化磁盤信息"""
partitions = get_disk_info()
text = "?? 磁盤狀態(tài)\n━━━━━━━━━━━━━━━\n"
for p in partitions:
bar = "█" * int(p['percent'] / 10) + "?" * (10 - int(p['percent'] / 10))
text += f"""
{p['mountpoint']}
[{bar}] {p['percent']}%
已用: {p['used']:.1f} GB / {p['total']:.1f} GB
剩余: {p['free']:.1f} GB
"""
return text
4.4 網(wǎng)絡(luò)監(jiān)控
# monitors/network.py
import psutil
import socket
def get_network_info():
"""獲取網(wǎng)絡(luò)信息"""
# 網(wǎng)絡(luò)IO
net_io = psutil.net_io_counters()
# IP地址
hostname = socket.gethostname()
try:
ip = socket.gethostbyname(hostname)
except:
ip = "未知"
# 網(wǎng)絡(luò)連接數(shù)
connections = len(psutil.net_connections())
return {
"hostname": hostname,
"ip": ip,
"bytes_sent": net_io.bytes_sent / (1024**2), # MB
"bytes_recv": net_io.bytes_recv / (1024**2),
"packets_sent": net_io.packets_sent,
"packets_recv": net_io.packets_recv,
"connections": connections
}
def format_network_info():
"""格式化網(wǎng)絡(luò)信息"""
info = get_network_info()
text = f"""
?? 網(wǎng)絡(luò)狀態(tài)
━━━━━━━━━━━━━━━
主機(jī)名: {info['hostname']}
IP地址: {info['ip']}
連接數(shù): {info['connections']}
流量統(tǒng)計(jì):
發(fā)送: {info['bytes_sent']:.1f} MB
接收: {info['bytes_recv']:.1f} MB
發(fā)送包: {info['packets_sent']}
接收包: {info['packets_recv']}
"""
return text
五、完整Bot實(shí)現(xiàn)
5.1 主程序
# bot.py
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
Application, CommandHandler, CallbackQueryHandler, ContextTypes
)
from apscheduler.schedulers.asyncio import AsyncIOScheduler
import psutil
from datetime import datetime
# 配置
TOKEN = "你的Bot Token"
ADMIN_CHAT_ID = 123456789 # 你的Chat ID
# 告警閾值
THRESHOLDS = {
"cpu": 80,
"memory": 85,
"disk": 90
}
# 監(jiān)控函數(shù)(簡(jiǎn)化版,集成上面的模塊)
def get_system_status():
cpu = psutil.cpu_percent(interval=1)
mem = psutil.virtual_memory()
disk = psutil.disk_usage('/')
return {
"cpu": cpu,
"memory": mem.percent,
"disk": disk.percent,
"uptime": datetime.now() - datetime.fromtimestamp(psutil.boot_time())
}
def format_status():
s = get_system_status()
# 狀態(tài)圖標(biāo)
cpu_icon = "??" if s['cpu'] > THRESHOLDS['cpu'] else "??"
mem_icon = "??" if s['memory'] > THRESHOLDS['memory'] else "??"
disk_icon = "??" if s['disk'] > THRESHOLDS['disk'] else "??"
text = f"""
?? 服務(wù)器狀態(tài)概覽
━━━━━━━━━━━━━━━━━━
{cpu_icon} CPU: {s['cpu']}%
{mem_icon} 內(nèi)存: {s['memory']}%
{disk_icon} 磁盤: {s['disk']}%
?? 運(yùn)行時(shí)間: {str(s['uptime']).split('.')[0]}
?? 更新時(shí)間: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
return text
# 命令處理器
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
keyboard = [
[
InlineKeyboardButton("?? 狀態(tài)", callback_data="status"),
InlineKeyboardButton("??? CPU", callback_data="cpu"),
],
[
InlineKeyboardButton("?? 內(nèi)存", callback_data="memory"),
InlineKeyboardButton("?? 磁盤", callback_data="disk"),
],
[
InlineKeyboardButton("?? 網(wǎng)絡(luò)", callback_data="network"),
InlineKeyboardButton("?? 刷新", callback_data="refresh"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text(
"?? 服務(wù)器監(jiān)控機(jī)器人\n選擇要查看的信息:",
reply_markup=reply_markup
)
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(format_status())
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
if query.data == "status" or query.data == "refresh":
text = format_status()
elif query.data == "cpu":
text = format_cpu_info()
elif query.data == "memory":
text = format_memory_info()
elif query.data == "disk":
text = format_disk_info()
elif query.data == "network":
text = format_network_info()
else:
text = "未知命令"
await query.edit_message_text(text=text)
# 告警檢查
async def check_alerts(context: ContextTypes.DEFAULT_TYPE):
s = get_system_status()
alerts = []
if s['cpu'] > THRESHOLDS['cpu']:
alerts.append(f"?? CPU使用率過高: {s['cpu']}%")
if s['memory'] > THRESHOLDS['memory']:
alerts.append(f"?? 內(nèi)存使用率過高: {s['memory']}%")
if s['disk'] > THRESHOLDS['disk']:
alerts.append(f"?? 磁盤使用率過高: {s['disk']}%")
if alerts:
alert_text = "?? 服務(wù)器告警\n━━━━━━━━━━━━━━━\n" + "\n".join(alerts)
alert_text += f"\n\n? {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
await context.bot.send_message(chat_id=ADMIN_CHAT_ID, text=alert_text)
# 定時(shí)報(bào)告
async def daily_report(context: ContextTypes.DEFAULT_TYPE):
text = "?? 每日服務(wù)器報(bào)告\n" + format_status()
await context.bot.send_message(chat_id=ADMIN_CHAT_ID, text=text)
def main():
# 創(chuàng)建應(yīng)用
app = Application.builder().token(TOKEN).build()
# 注冊(cè)命令
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("status", status))
app.add_handler(CallbackQueryHandler(button_callback))
# 定時(shí)任務(wù)
scheduler = AsyncIOScheduler()
# 每分鐘檢查告警
scheduler.add_job(
check_alerts, 'interval', minutes=1,
args=[app]
)
# 每天9點(diǎn)發(fā)送報(bào)告
scheduler.add_job(
daily_report, 'cron', hour=9,
args=[app]
)
scheduler.start()
# 啟動(dòng)
print("Bot啟動(dòng)中...")
app.run_polling()
if __name__ == "__main__":
main()
六、高級(jí)功能
6.1 遠(yuǎn)程執(zhí)行命令
import subprocess
async def exec_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""執(zhí)行系統(tǒng)命令(危險(xiǎn)!僅限管理員)"""
# 權(quán)限檢查
if update.effective_user.id != ADMIN_CHAT_ID:
await update.message.reply_text("? 無權(quán)限")
return
if not context.args:
await update.message.reply_text("用法: /exec <命令>")
return
cmd = " ".join(context.args)
try:
result = subprocess.run(
cmd, shell=True, capture_output=True,
text=True, timeout=30
)
output = result.stdout or result.stderr or "(無輸出)"
# 限制輸出長(zhǎng)度
if len(output) > 4000:
output = output[:4000] + "\n...(輸出過長(zhǎng)已截?cái)?"
await update.message.reply_text(f"```\n{output}\n```", parse_mode='Markdown')
except subprocess.TimeoutExpired:
await update.message.reply_text("? 命令執(zhí)行超時(shí)")
except Exception as e:
await update.message.reply_text(f"? 執(zhí)行失敗: {e}")
6.2 進(jìn)程管理
async def processes(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""查看進(jìn)程列表"""
procs = []
for p in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
try:
procs.append(p.info)
except:
pass
# 按CPU排序
procs.sort(key=lambda x: x['cpu_percent'] or 0, reverse=True)
text = "?? 進(jìn)程列表 (Top 10 CPU)\n━━━━━━━━━━━━━━━━━━\n"
for p in procs[:10]:
text += f"{p['pid']:>6} | {p['cpu_percent']:>5.1f}% | {p['name'][:20]}\n"
await update.message.reply_text(f"```\n{text}\n```", parse_mode='Markdown')
6.3 Docker容器監(jiān)控
import docker
async def docker_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Docker容器狀態(tài)"""
try:
client = docker.from_env()
containers = client.containers.list(all=True)
text = "?? Docker容器狀態(tài)\n━━━━━━━━━━━━━━━━━━\n"
for c in containers:
status_icon = "??" if c.status == "running" else "??"
text += f"{status_icon} {c.name[:20]}: {c.status}\n"
if not containers:
text += "沒有容器"
await update.message.reply_text(text)
except Exception as e:
await update.message.reply_text(f"? Docker連接失敗: {e}")
七、跨網(wǎng)絡(luò)訪問
7.1 問題
場(chǎng)景:
- Bot運(yùn)行在內(nèi)網(wǎng)服務(wù)器
- 想在外面通過Telegram控制服務(wù)器
- 服務(wù)器沒有公網(wǎng)IP
問題:
- Telegram API可以正常調(diào)用(服務(wù)器能訪問外網(wǎng))
- 但無法直接SSH到服務(wù)器進(jìn)行維護(hù)
7.2 解決方案
Bot本身可以正常工作,但如果需要直接訪問內(nèi)網(wǎng)服務(wù)器:
使用組網(wǎng)軟件(如星空組網(wǎng)):
1. 內(nèi)網(wǎng)服務(wù)器安裝組網(wǎng)客戶端
2. 你的手機(jī)/電腦安裝組網(wǎng)客戶端
3. 組建虛擬局域網(wǎng)
4. 通過虛擬IP直接SSH到服務(wù)器
優(yōu)勢(shì):
- Bot負(fù)責(zé)告警和簡(jiǎn)單查詢
- 組網(wǎng)負(fù)責(zé)需要時(shí)的直接訪問
- 互相補(bǔ)充,完美配合
八、部署運(yùn)維
8.1 后臺(tái)運(yùn)行
# 使用nohup nohup python bot.py > bot.log 2>&1 & # 使用screen screen -S bot python bot.py # Ctrl+A, D 退出 # 使用systemd(推薦)
8.2 Systemd服務(wù)
# /etc/systemd/system/telegram-bot.service [Unit] Description=Telegram Server Monitor Bot After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/telegram_bot ExecStart=/usr/bin/python3 /opt/telegram_bot/bot.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable telegram-bot sudo systemctl start telegram-bot sudo systemctl status telegram-bot
九、總結(jié)
Telegram Bot監(jiān)控要點(diǎn):
| 功能 | 實(shí)現(xiàn) |
|---|---|
| 狀態(tài)查詢 | 命令 + 按鈕 |
| 定時(shí)檢查 | APScheduler |
| 告警推送 | 閾值觸發(fā) |
| 遠(yuǎn)程控制 | 命令執(zhí)行 |
Bot能做的:
- 實(shí)時(shí)查看服務(wù)器狀態(tài)
- 自動(dòng)告警通知
- 遠(yuǎn)程執(zhí)行命令
- 定時(shí)報(bào)告
參考資料
- python-telegram-bot文檔:https://docs.python-telegram-bot.org/
- psutil文檔:https://psutil.readthedocs.io/
- Telegram Bot API:https://core.telegram.org/bots/api
?? Telegram Bot是服務(wù)器監(jiān)控的好幫手,配合組網(wǎng)軟件可以實(shí)現(xiàn)更完整的遠(yuǎn)程運(yùn)維方案。
到此這篇關(guān)于Python開發(fā)Telegram Bot實(shí)現(xiàn)服務(wù)器監(jiān)控告警的文章就介紹到這了,更多相關(guān)Python 實(shí)現(xiàn)服務(wù)器監(jiān)控告警內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python中操作Excel的七大模塊對(duì)比終極指南
Python 作為一門強(qiáng)大的編程語(yǔ)言,提供了多個(gè)優(yōu)秀的庫(kù)來操作 Excel 文件,本文將對(duì)?xlrd、xlwt、xlutils、xlwings、XlsxWriter、openpyxl、pandas?這七大模塊進(jìn)行全面對(duì)比,幫助你選擇最合適的工具2025-09-09
python中print的不換行即時(shí)輸出的快速解決方法
下面小編就為大家?guī)硪黄猵ython中print的不換行即時(shí)輸出的快速解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考2016-07-07
Python SQLite3數(shù)據(jù)庫(kù)操作類分享
這篇文章主要介紹了Python SQLite3數(shù)據(jù)庫(kù)操作類分享,需要的朋友可以參考下2014-06-06
python3 pillow生成簡(jiǎn)單驗(yàn)證碼圖片的示例
本篇文章主要介紹了python3 pillow生成簡(jiǎn)單驗(yàn)證碼圖片的示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09
Python?虛擬環(huán)境的價(jià)值和常用命令詳解
在實(shí)際項(xiàng)目開發(fā)中,我們通常會(huì)根據(jù)自己的需求去下載各種相應(yīng)的框架庫(kù),如Scrapy、Beautiful?Soup等,但是可能每個(gè)項(xiàng)目使用的框架庫(kù)并不一樣,或使用框架的版本不一樣,今天給大家分享下Python?虛擬環(huán)境的價(jià)值和常用命令,感興趣的朋友一起看看吧2022-05-05
python對(duì)RabbitMQ的簡(jiǎn)單入門使用教程
RabbitMq是實(shí)現(xiàn)了高級(jí)消息隊(duì)列協(xié)議(AMQP)的開源消息代理中間件,下面這篇文章主要給大家介紹了關(guān)于python對(duì)RabbitMQ的簡(jiǎn)單入門使用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06
Python抓取通過Ajax加載數(shù)據(jù)的示例
在網(wǎng)頁(yè)上,有一些內(nèi)容是通過執(zhí)行Ajax請(qǐng)求動(dòng)態(tài)加載數(shù)據(jù)渲染出來的,本文主要介紹了使用Python抓取通過Ajax加載數(shù)據(jù),感興趣的可以了解一下2023-05-05

