使用 Bash 脚本实现服务器资源监控与限制
首先声明,绿云是垃圾。
这个脚本主要是针对绿云的限制写的,没办法,他真的很垃圾。
本文将介绍如何编写一个 Bash 脚本,通过配置文件设定参数,实现对服务器带宽和 CPU 使用率的监控与限制,同时支持通过飞书 Webhook 发送通知。
一、配置文件 config.cfg
首先,我们需要定义一个配置文件,用于存储各种参数,便于后续脚本的读取和使用。以下是配置文件的内容:
♾️ bash 代码:# 配置参数
# 网络接口
INTERFACE="ens3" # 请替换为您的实际网络接口名称
# 带宽限制配置(单位:Mbps)
MAX_BANDWIDTH=10000 # 最大带宽限制(10Gbps)
LIMITED_BANDWIDTH=100 # 限制后的带宽(100Mbps)
# 带宽阈值
BANDWIDTH_THRESHOLD=100 # 带宽阈值(100Mbps)
# 爆发事件判定带宽
BURST_THRESHOLD=9500 # 爆发事件判断阈值(9.5Gbps)
# 爆发次数限制
BURST_TIME_WINDOW=10800 # 爆发次数统计的时间窗口(秒),3小时 = 10800秒
BURST_LIMIT_DURATION=3600 # 带宽限制持续时间(秒),1小时 = 3600秒
# 6小时持续高带宽使用限制
HIGH_USAGE_DURATION_LIMIT=21600 # 持续高带宽使用的时间限制(秒),6小时 = 21600秒
HIGH_USAGE_LIMIT_DURATION=3600 # 高带宽使用限制持续时间(秒),1小时 = 3600秒
# 带宽下降宽限期(秒)
BANDWIDTH_DROP_GRACE_PERIOD=300 # 5分钟
# CPU 使用率限制(百分比)
CPU_AVG_LIMIT=30 # 平均 CPU 使用率限制
CPU_BURST_LIMIT=100 # CPU 突发使用率限制
BURST_DURATION=600 # 突发允许持续时间(秒),例如 600 秒为 10 分钟
# 监控间隔(秒)
MONITOR_INTERVAL=60 # 监控资源使用的时间间隔
# 日志文件
LOG_FILE="/var/log/resource_monitor.log"
# 飞书 Webhook 地址
FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/你的-webhook-url"
配置参数解释
INTERFACE
:需要监控的网络接口名称,需替换为实际的接口名称。MAX_BANDWIDTH
:最大允许的带宽限制,默认是 10Gbps。LIMITED_BANDWIDTH
:当触发限制时,将带宽限制到该值,默认是 100Mbps。BANDWIDTH_THRESHOLD
:带宽使用达到该值时,开始计时监控高带宽使用情况。BURST_THRESHOLD
:带宽使用超过该值时,判定为一次带宽爆发事件。BURST_TIME_WINDOW
:统计带宽爆发事件的时间窗口,默认是 3 小时。BURST_LIMIT_DURATION
:当带宽爆发次数过多时,限制带宽的持续时间,默认是 1 小时。HIGH_USAGE_DURATION_LIMIT
:持续高带宽使用的时间限制,默认是 6 小时。HIGH_USAGE_LIMIT_DURATION
:当持续高带宽使用达到限制时,限制带宽的持续时间,默认是 1 小时。BANDWIDTH_DROP_GRACE_PERIOD
:带宽使用下降时的宽限期,默认是 5 分钟。CPU_AVG_LIMIT
:CPU 平均使用率的限制,超过该值开始监控突发使用。CPU_BURST_LIMIT
:CPU 突发使用率的限制,超过且持续一定时间将触发限制措施。BURST_DURATION
:CPU 突发使用率的持续时间阈值,超过该时间将触发限制措施。MONITOR_INTERVAL
:资源监控的时间间隔,默认是 60 秒。LOG_FILE
:日志文件的路径。FEISHU_WEBHOOK_URL
:飞书机器人 Webhook 地址,用于发送通知。
二、将脚本设置为系统服务
为了使脚本能够持续运行并在系统启动时自动启动,我们需要将其设置为一个系统服务。以下是查看服务状态的命令输出:
♾️ bash 代码:root@server:~# systemctl status resource_monitor.service
● resource_monitor.service - Resource Monitor Service
Loaded: loaded (/etc/systemd/system/resource_monitor.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-10-20 11:34:14 BST; 36min ago
Main PID: 38113 (bash)
Tasks: 3
Memory: 5.2M
CGroup: /system.slice/resource_monitor.service
├─38113 /usr/bin/bash /usr/local/bin/resource_monitor.sh
├─39463 /usr/bin/bash /usr/local/bin/resource_monitor.sh
└─39466 sleep 60
Oct 20 11:58:14 server bash[38715]: 2023-10-20 11:58:14 - CPU 限制已移除
Oct 20 11:59:14 server bash[38736]: 2023-10-20 11:59:14 - 当前带宽使用:0 Mbps
Oct 20 12:00:14 server bash[38760]: 2023-10-20 12:00:14 - 当前 CPU 使用率:0%
Oct 20 12:00:14 server bash[38763]: 2023-10-20 12:00:14 - CPU 限制已移除
Oct 20 12:01:14 server bash[39077]: 2023-10-20 12:01:14 - 当前带宽使用:0 Mbps
Oct 20 12:02:14 server bash[39209]: 2023-10-20 12:02:14 - 当前 CPU 使用率:67%
Oct 20 12:02:14 server bash[39212]: 2023-10-20 12:02:14 - CPU 使用率超过 30%,开始监控突发使用
Oct 20 12:09:15 server bash[39433]: 2023-10-20 12:09:15 - 当前带宽使用:0 Mbps
Oct 20 12:10:15 server bash[39457]: 2023-10-20 12:10:15 - 当前 CPU 使用率:0%
Oct 20 12:10:15 server bash[39460]: 2023-10-20 12:10:15 - CPU 限制已移除
三、核心脚本 resource_monitor.sh
以下是资源监控脚本的完整代码,我们将对其进行详细解释。
♾️ bash 代码:#!/bin/bash
# 加载配置参数
CONFIG_FILE="/usr/local/bin/config.cfg"
if [ ! -f "$CONFIG_FILE" ]; then
echo "配置文件 $CONFIG_FILE 未找到!"
exit 1
fi
source "$CONFIG_FILE"
# 确保脚本以 root 用户运行
if [ "$EUID" -ne 0 ]; then
echo "请以 root 用户运行此脚本。"
exit 1
fi
# 检查必要的工具是否已安装
REQUIRED_TOOLS=("tc" "cgcreate" "cgdelete" "mpstat" "awk" "curl")
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "$tool" &>/dev/null; then
echo "错误:$tool 未安装,请安装后再运行此脚本。"
exit 1
fi
done
# 初始化日志
echo "脚本在 $(date '+%Y-%m-%d %H:%M:%S') 启动" >> "$LOG_FILE"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 发送飞书通知
send_feishu_notification() {
local message="$1"
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\": \"text\", \"content\": {\"text\": \"$message\"}}" \
"$FEISHU_WEBHOOK_URL"
}
# 初始化带宽限制
initialize_bandwidth_limit() {
tc qdisc del dev $INTERFACE root 2>/dev/null
tc qdisc add dev $INTERFACE root handle 1: htb default 10
tc class add dev $INTERFACE parent 1: classid 1:10 htb rate ${MAX_BANDWIDTH}mbit ceil ${MAX_BANDWIDTH}mbit
log "带宽限制已初始化为 ${MAX_BANDWIDTH}Mbps 在接口 $INTERFACE 上"
}
# 设置带宽限制为限制后的带宽
set_limited_bandwidth() {
tc class change dev $INTERFACE parent 1: classid 1:10 htb rate ${LIMITED_BANDWIDTH}mbit ceil ${LIMITED_BANDWIDTH}mbit
bandwidth_limited=true
bandwidth_limit_end_time=$(($(date +%s) + $1))
log "带宽限制已降低到 ${LIMITED_BANDWIDTH}Mbps,限制持续 $(( $1 / 60 )) 分钟"
send_feishu_notification "带宽已限制到 ${LIMITED_BANDWIDTH}Mbps,限制持续 $(( $1 / 60 )) 分钟。原因:$2"
}
# 恢复最大带宽限制
restore_max_bandwidth() {
tc class change dev $INTERFACE parent 1: classid 1:10 htb rate ${MAX_BANDWIDTH}mbit ceil ${MAX_BANDWIDTH}mbit
bandwidth_limited=false
log "带宽限制已恢复到 ${MAX_BANDWIDTH}Mbps"
send_feishu_notification "带宽限制已解除,恢复到 ${MAX_BANDWIDTH}Mbps。"
}
# 初始化 CPU 限制
initialize_cpu_limit() {
cgcreate -g cpu:/cpulimited 2>/dev/null
echo "$((CPU_AVG_LIMIT * 1000))" > /sys/fs/cgroup/cpu/cpulimited/cpu.cfs_quota_us
echo "100000" > /sys/fs/cgroup/cpu/cpulimited/cpu.cfs_period_us
# 将所有进程加入到限制组中
for pid in $(ps -e -o pid=); do
echo $pid > /sys/fs/cgroup/cpu/cpulimited/tasks 2>/dev/null
done
log "CPU 监控阈值已设置为 ${CPU_AVG_LIMIT}%,超过此值将触发限制机制"
}
# 移除 CPU 限制
remove_cpu_limit() {
echo "-1" > /sys/fs/cgroup/cpu/cpulimited/cpu.cfs_quota_us
log "CPU 限制已移除"
}
# 带宽使用监控函数
monitor_bandwidth_usage() {
local rx_bytes_prev=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
local tx_bytes_prev=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
sleep "$MONITOR_INTERVAL"
local rx_bytes_curr=$(cat /sys/class/net/$INTERFACE/statistics/rx_bytes)
local tx_bytes_curr=$(cat /sys/class/net/$INTERFACE/statistics/tx_bytes)
local rx_rate=$(( (rx_bytes_curr - rx_bytes_prev) * 8 / MONITOR_INTERVAL )) # bps
local tx_rate=$(( (tx_bytes_curr - tx_bytes_prev) * 8 / MONITOR_INTERVAL )) # bps
local total_rate=$(( (rx_rate + tx_rate) / 1000000 )) # Mbps
echo "$total_rate"
}
# CPU 使用监控函数
monitor_cpu_usage() {
local cpu_usage=$(mpstat "$MONITOR_INTERVAL" 1 | awk '/Average/ {print 100 - $NF}')
echo "${cpu_usage%.*}" # 转换为整数
}
# 清理函数
cleanup() {
log "脚本退出,正在清理资源..."
tc qdisc del dev $INTERFACE root 2>/dev/null
cgdelete -g cpu:/cpulimited 2>/dev/null
exit 0
}
trap cleanup SIGINT SIGTERM
# 初始化限制
initialize_bandwidth_limit
initialize_cpu_limit
# 初始化变量
burst_events=()
high_bandwidth_start_time=0
bandwidth_limited=false
bandwidth_limit_end_time=0
high_usage_limited=false
high_usage_limit_end_time=0
bandwidth_drop_start_time=0
# 主循环
while true; do
# 获取当前时间
current_time=$(date +%s)
# 带宽监控
bandwidth_usage=$(monitor_bandwidth_usage)
log "当前带宽使用:${bandwidth_usage} Mbps"
# 检查是否需要解除带宽限制
if [ "$bandwidth_limited" = true ] && [ "$current_time" -ge "$bandwidth_limit_end_time" ]; then
restore_max_bandwidth
fi
if [ "$high_usage_limited" = true ] && [ "$current_time" -ge "$high_usage_limit_end_time" ]; then
restore_max_bandwidth
high_usage_limited=false
high_bandwidth_start_time=0
bandwidth_drop_start_time=0
log "6小时高带宽使用限制已结束,恢复带宽限制和计时器"
send_feishu_notification "6小时高带宽使用限制已结束,带宽已恢复到 ${MAX_BANDWIDTH}Mbps。"
fi
# 检查爆发次数限制
if [ "$bandwidth_usage" -ge "$BURST_THRESHOLD" ]; then
# 记录爆发事件
burst_events+=("$current_time")
log "检测到爆发事件,带宽达到 ${BURST_THRESHOLD} Mbps"
# 移除超过时间窗口的事件
for i in "${!burst_events[@]}"; do
if [ $(($current_time - ${burst_events[$i]})) -gt $BURST_TIME_WINDOW ]; then
unset 'burst_events[i]'
fi
done
# 限制带宽
if [ "$bandwidth_limited" = false ]; then
set_limited_bandwidth "$BURST_LIMIT_DURATION" "触发爆发次数限制"
fi
fi
# 检查持续高带宽使用限制
if [ "$bandwidth_usage" -ge "$BANDWIDTH_THRESHOLD" ]; then
if [ "$high_bandwidth_start_time" -eq 0 ]; then
high_bandwidth_start_time=$current_time
log "带宽使用超过 ${BANDWIDTH_THRESHOLD} Mbps,开始计时"
fi
# 带宽使用高于阈值,重置带宽下降开始时间
bandwidth_drop_start_time=0
else
if [ "$bandwidth_drop_start_time" -eq 0 ]; then
bandwidth_drop_start_time=$current_time
log "带宽使用低于 ${BANDWIDTH_THRESHOLD} Mbps,开始宽限期计时"
else
drop_elapsed_time=$((current_time - bandwidth_drop_start_time))
if [ "$drop_elapsed_time" -ge "$BANDWIDTH_DROP_GRACE_PERIOD" ]; then
if [ "$high_bandwidth_start_time" -ne 0 ]; then
log "带宽使用低于阈值超过宽限期,重置高带宽计时器"
high_bandwidth_start_time=0
bandwidth_drop_start_time=0
fi
fi
fi
fi
# 检查是否达到高带宽限制条件
if [ "$high_bandwidth_start_time" -ne 0 ] && [ "$high_usage_limited" = false ]; then
elapsed_time=$((current_time - high_bandwidth_start_time))
if [ "$elapsed_time" -ge "$HIGH_USAGE_DURATION_LIMIT" ]; then
set_limited_bandwidth "$HIGH_USAGE_LIMIT_DURATION" "触发6小时高带宽使用限制"
high_usage_limited=true
high_usage_limit_end_time=$(($current_time + $HIGH_USAGE_LIMIT_DURATION))
log "带宽使用持续超过 ${BANDWIDTH_THRESHOLD} Mbps 达到 6 小时,已限制带宽 1 小时"
fi
fi
# CPU 监控
cpu_usage=$(monitor_cpu_usage)
log "当前 CPU 使用率:${cpu_usage}%"
if [ "$cpu_usage" -gt "$CPU_AVG_LIMIT" ]; then
log "CPU 使用率超过 ${CPU_AVG_LIMIT}%,开始监控突发使用"
cpu_burst_start=$(date +%s)
while [ "$cpu_usage" -gt "$CPU_AVG_LIMIT" ]; do
sleep "$MONITOR_INTERVAL"
cpu_usage=$(monitor_cpu_usage)
current_time=$(date +%s)
burst_duration=$((current_time - cpu_burst_start))
if [ "$burst_duration" -ge "$BURST_DURATION" ]; then
log "CPU 使用率已超过 ${CPU_BURST_LIMIT}% 持续 ${BURST_DURATION} 秒,维持 CPU 限制"
send_feishu_notification "CPU 使用率已超过 ${CPU_BURST_LIMIT}% 持续 ${BURST_DURATION} 秒,已维持 CPU 限制。"
break
fi
done
else
remove_cpu_limit
fi
done
脚本解析
加载配置文件和环境检查
- 使用
source
加载配置文件中的参数。 - 确保脚本以
root
用户运行,否则无法执行某些系统命令。 - 检查必要的工具是否已安装,确保脚本运行所需的环境。
- 使用
日志和通知功能
log
函数用于记录日志,同时将日志输出到屏幕和日志文件。send_feishu_notification
函数用于通过飞书 Webhook 发送通知,便于及时获悉服务器状态。
带宽限制的初始化和管理
initialize_bandwidth_limit
函数使用tc
命令设置带宽限制的初始状态。set_limited_bandwidth
函数在需要时降低带宽限制,并记录限制的结束时间。restore_max_bandwidth
函数在限制结束后,恢复到最大带宽。
CPU 限制的初始化和管理
initialize_cpu_limit
函数使用cgroups
创建一个 CPU 限制组,并设置 CPU 配额。remove_cpu_limit
函数移除 CPU 限制,恢复正常状态。
资源监控函数
monitor_bandwidth_usage
函数监控指定网络接口的带宽使用情况。monitor_cpu_usage
函数监控 CPU 的平均使用率。
清理函数
cleanup
函数在脚本退出时,清理设置的带宽和 CPU 限制,确保系统恢复正常。
主循环逻辑
- 在无限循环中,不断监控带宽和 CPU 使用情况。
- 根据定义的阈值和时间窗口,判断是否需要触发限制措施。
- 在需要解除限制时,自动恢复带宽和 CPU 到正常状态。
- 通过日志和飞书通知,实时反馈服务器资源使用和限制情况。
四、将脚本设置为系统服务
为了让脚本在后台持续运行并在系统启动时自动启动,我们需要将其设置为一个 Systemd 服务。
创建服务文件
在
♾️ ini 代码:/etc/systemd/system/
目录下创建resource_monitor.service
文件:[Unit] Description=Resource Monitor Service After=network.target [Service] Type=simple ExecStart=/usr/bin/bash /usr/local/bin/resource_monitor.sh Restart=always [Install] WantedBy=multi-user.target
重新加载 Systemd 配置并启动服务
♾️ bash 代码:sudo systemctl daemon-reload sudo systemctl enable resource_monitor.service sudo systemctl start resource_monitor.service
查看服务状态
♾️ bash 代码:sudo systemctl status resource_monitor.service
五、注意事项
- 权限要求:脚本需要以
root
用户运行,因为涉及到系统级别的资源限制。 - 工具依赖:确保系统已安装
tc
、cgroup-tools
、sysstat
、curl
等工具。 - 飞书 Webhook:替换配置文件中的
FEISHU_WEBHOOK_URL
为实际的 Webhook 地址。 - 日志查看:日志文件默认保存在
/var/log/resource_monitor.log
,可根据需要修改路径。
六、结语
通过上述脚本,我们实现了对服务器带宽和 CPU 使用率的实时监控和限制。当资源使用超过设定的阈值时,脚本会自动采取限制措施,并通过飞书通知管理员。这个解决方案有助于保障服务器的稳定运行,防止资源滥用。
希望本文对您有所帮助,如果有任何问题或建议,欢迎交流。