说实话,别不信,中小团队的运维真的不能太依赖纯手动,哪怕只有5台服务器,重复起来都是折磨。这些脚本都是我从生产环境摸爬滚打出来的,不是网上随便搜的没有验证的垃圾代码,至少加了一些基本的日志记录、成功失败判断,出了问题还能回溯下原因。
先分享最常用的第一个:批量添加/删除指定服务器组的SSH公钥。之前纯手动的话,新人刚入职我得守半天,现在把他们的公钥放在本地的一个指定目录里,一键就能同步到所有我标记的“基础运维组”“预发布测试组”的服务器上,删除离职同事的也一样快。脚本开头要先写授权检查,别让非root用户跑,我刚入行的时候就忘了加,普通开发同事偷偷拿来跑差点删了生产核心机的所有运维公钥,那次差点丢工作。这里敲黑板提醒,生产环境用脚本,授权和失败回滚一定是第一位的,哪怕这个脚本看起来再简单。给你们放个简化版的,但核心的授权、成功失败echo、日志记录(记录到/var/log/batch_ssh_key.log)、服务器组配置都保留了:
#!/bin/bash
生产环境批量添加/删除SSH公钥脚本(简化版2026)
作者:阿强(一线6年运维)
LOG_FILE="/var/log/batch_ssh_key.log"
SERVER_GROUP_FILE="/root/ops_tools/server_groups.txt" # 按组存服务器IP,每行是IP:端口,比如192.168.1.10:2222
KEY_DIR="/root/ops_tools/ssh_keys"
KEY_ACTION=$1 # 参数1是add或者del
基础授权检查
if [ "$(whoami)" != "root" ]; then
echo "当前用户不是root,脚本无法执行!" | tee -a "$LOG_FILE"
exit 1
fi
参数检查
if [ -z "$KEY_ACTION" ] || ([ "$KEY_ACTION" != "add" ] && [ "$KEY_ACTION" != "del" ]); then
echo "请输入正确参数:$0 add/del" | tee -a "$LOG_FILE"
exit 1
fi
日志初始化
echo "===== $(date '+%Y-%m-%d %H:%M:%S') 开始执行SSH公钥$KEY_ACTION操作 =====" >> "$LOG_FILE"
读取服务器组文件
if [ ! -f "$SERVER_GROUP_FILE" ]; then
echo "服务器组配置文件$SERVER_GROUP_FILE不存在!" | tee -a "$LOG_FILE"
exit 1
fi
批量处理
while read -r line; do
IP=$(echo "$line" | cut -d: -f1)
PORT=$(echo "$line" | cut -d: -f2 -s)
[ -z "$PORT" ] && PORT=22 # 默认端口是22
echo "正在处理服务器 $IP:$PORT..." | tee -a "$LOG_FILE"
# 检查服务器连通性
if ! ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "echo test" > /dev/null 2>&1; then
echo "服务器 $IP:$PORT 无法连接!跳过!" | tee -a "$LOG_FILE"
continue
fi
if [ "$KEY_ACTION" = "add" ]; then
# 公钥目录不存在就创建
ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# 逐个添加公钥
for key_file in "$KEY_DIR"/.pub; do
[ ! -f "$key_file" ] && continue
KEY_CONTENT=$(cat "$key_file")
if ! ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "grep -qF "$KEY_CONTENT" ~/.ssh/authorized_keys"; then
echo "$KEY_CONTENT" | ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "cat >> ~/.ssh/authorized_keys"
echo "成功添加公钥文件 $key_file 到服务器 $IP:$PORT" | tee -a "$LOG_FILE"
else
echo "公钥文件 $key_file 已存在于服务器 $IP:$PORT,跳过!" | tee -a "$LOG_FILE"
fi
done
else
# 删除离职同事的公钥,这里简化版假设离职同事的公钥文件名是[姓名]_[工号]_old.pub
for old_key_file in "$KEY_DIR"/_old.pub; do
[ ! -f "$old_key_file" ] && continue
OLD_KEY_CONTENT=$(cat "$old_key_file")
if ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "grep -qF "$OLD_KEY_CONTENT" ~/.ssh/authorized_keys"; then
ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "sed -i.bak '/$OLD_KEY_CONTENT/d' ~/.ssh/authorized_keys && rm -f ~/.ssh/authorized_keys.bak"
echo "成功删除公钥文件 $old_key_file 从服务器 $IP:$PORT" | tee -a "$LOG_FILE"
else
echo "公钥文件 $old_key_file 不存在于服务器 $IP:$PORT,跳过!" | tee -a "$LOG_FILE"
fi
done
fi
done
echo "===== $(date '+%Y-%m-%d %H:%M:%S') SSH公钥$KEY_ACTION操作完成 =====" >> "$LOG_FILE"
这个简化版是我把自己用的备份生产核心配置的部分去掉了,但保留了最重要的东西,新人拿过去改一下目录和文件就行。
第二个脚本是批量清理超过30天的Nginx/Tomcat旧日志,还有清理/tmp目录下超过7天的临时文件。刚才说的边缘CDN推流日志满的问题,就是这个脚本当时还没覆盖到边缘存储的服务器,后来我加进去了。新手运维很容易忽略边缘服务器和预发布机的日志清理,别不信,我刚入行那会预发布机的MySQL慢查询日志存了半年多,占了200多G,差点影响预发布环境的稳定性测试。给你们放个更实用的,支持指定目录和天数,还有检查剩余磁盘空间的功能,剩余空间小于20%的话先清理日志:
#!/bin/bash
批量清理旧日志和临时文件脚本(2026优化版,带磁盘检查)
LOG_FILE="/var/log/batch_cleanup.log"
SERVER_GROUP_FILE="/root/ops_tools/server_groups.txt"
可以修改下面的参数
LOG_PATHS=("/var/log/nginx" "/var/log/tomcat" "/usr/local/nginx/logs" "/usr/local/tomcat/logs")
LOG_DAYS=30
TMP_PATHS=("/tmp" "/var/tmp")
TMP_DAYS=7
DISK_THRESHOLD=20 # 剩余空间小于20%触发紧急清理(只保留3天)
echo "===== $(date '+%Y-%m-%d %H:%M:%S') 开始执行批量清理操作 =====" >> "$LOG_FILE"
while read -r line; do
IP=$(echo "$line" | cut -d: -f1)
PORT=$(echo "$line" | cut -d: -f2 -s)
[ -z "$PORT" ] && PORT=22
echo "正在处理服务器 $IP:$PORT..." | tee -a "$LOG_FILE"
if ! ssh -o ConnectTimeout=3 -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "echo test" > /dev/null 2>&1; then
echo "服务器 $IP:$PORT 无法连接!跳过!" | tee -a "$LOG_FILE"
continue
fi
# 检查根分区剩余空间
ROOT_USED=$(ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "df -P / | tail -1 | awk '{print $5}' | sed 's/%//'")
if [ "$ROOT_USED" -gt $((100
DISK_THRESHOLD)) ]; then
echo "服务器 $IP:$PORT 根分区剩余空间小于${DISK_THRESHOLD}%!触发紧急清理模式!" | tee -a "$LOG_FILE"
CURRENT_LOG_DAYS=3
CURRENT_TMP_DAYS=2
else
CURRENT_LOG_DAYS=$LOG_DAYS
CURRENT_TMP_DAYS=$TMP_DAYS
fi
# 清理旧日志
for log_path in "${LOG_PATHS[@]}"; do

ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "if [ -d "$log_path" ]; then find "$log_path" -type f ( -name ".log" -o -name ".log." -o -name ".gz" -o -name ".tar.gz" ) -mtime +$CURRENT_LOG_DAYS -delete; echo "已清理服务器 $IP:$PORT 的 $log_path 目录下超过${CURRENT_LOG_DAYS}天的旧日志"; fi" | tee -a "$LOG_FILE"
done
# 清理临时文件
for tmp_path in "${TMP_PATHS[@]}"; do
ssh -o StrictHostKeyChecking=no -p "$PORT" root@"$IP" "if [ -d "$tmp_path" ]; then find "$tmp_path" -type f -mtime +$CURRENT_TMP_DAYS -delete; echo "已清理服务器 $IP:$PORT 的 $tmp_path 目录下超过${CURRENT_TMP_DAYS}天的临时文件"; fi" | tee -a "$LOG_FILE"
done
done
echo "===== $(date '+%Y-%m-%d %H:%M:%S') 批量清理操作完成 =====" >> "$LOG_FILE"
这个脚本一定要配合crontab用,我一般设置成每天凌晨2点执行,边缘存储的CDN中转记录设置成每天凌晨1点单独执行(天数改成7天,因为有时候还要回溯7天内的推流问题)。这里再敲黑板提醒,find命令的-delete参数一定要谨慎用,最好先执行find的-dry-run(加-print参数)看看会删除哪些文件,确认无误后再加上-delete,我刚入行的时候就差点用-delete删除了整个/usr/local目录,那次冷汗都下来了。
第三个脚本是一键备份本地预发布MySQL到腾讯云COS,这个也是中小团队常用的,毕竟很多预发布环境的备份没人管,万一不小心删了测试用的大表,哭都没地方哭。这里需要先在服务器上安装腾讯云的COSCMD工具,具体安装方法可以去腾讯云官方文档看,不是特别难,我就不在这里啰嗦了。脚本里也加了备份文件压缩、校验、保留最近10天备份、上传到COS后自动删除本地备份(避免占预发布机的空间)的功能,还有备份成功失败的钉钉通知(中小团队一般用钉钉办公,要是不用的话可以改成邮件或者微信企业号通知)。给你们放个我常用的:
bash
#!/bin/bash
一键备份预发布MySQL到腾讯云COS脚本(2026带钉钉通知版)
配置参数
MYSQL_USER=”backup_user”
MYSQL_PASSWORD=”你的备份用户密码”
MYSQL_HOST=”127.0.0.1″
MYSQL_PORT=”3306″
可以只备份指定数据库,多个用空格分开,比如DATABASES=(“test_db1” “test_db2”)
DATABASES=(“all-databases”)
BACKUP_DIR=”/data/mysql_backup”
BACKUP_PREFIX=”pre_release_mysql_backup”
BACKUP_RETENTION=10
COS_BUCKET=”你的COS桶名称”
COS_REGION=”你的COS桶地域,比如ap-guangzhou”
COS_PATH=”/pre_release_mysql_backups/”
DINGTALK_WEBHOOK=”你的钉钉机器人webhook地址”
DINGTALK_SECRET=”你的钉钉机器人加签密钥(可选, 加上)”
创建备份目录
mkdir -p “$BACKUP_DIR”
生成当前时间戳
BACKUP_TIME=$(date ‘+%Y%m%d_%H%M%S’)
BACKUP_FILE=”$BACKUP_DIR/${BACKUP_PREFIX}_${BACKUP_TIME}.sql.gz”
BACKUP_MD5=”$BACKUP_FILE.md5″
备份MySQL并压缩
echo “开始备份预发布MySQL…”
if mysqldump -u”$MYSQL_USER” -p”$MYSQL_PASSWORD” -h”$MYSQL_HOST” -P”$MYSQL_PORT” single-transaction routines triggers events “${DATABASES[@]}” | gzip > “$BACKUP_FILE”; then
echo “MySQL备份成功!开始生成MD5校验码…”
md5sum “$BACKUP_FILE” > “$BACKUP_MD5”
echo “MD5校验码生成成功!开始上传到腾讯云COS…”
if coscmd -c /root/.cos.conf upload “$BACKUP_FILE” “$COS_PATH” && coscmd -c /root/.cos.conf upload “$BACKUP_MD5” “$COS_PATH”; then
echo “上传到腾讯云COS成功!开始删除本地备份和MD5校验码…”
rm -f “$BACKUP_FILE” “$BACKUP_MD5”
echo “本地文件删除成功!开始清理COS上超过${BACKUP_RETENTION}天的备份…”
coscmd -c /root/.cos.conf delete -r “$COS_PATH” days “$BACKUP_RETENTION”
echo “COS备份清理成功!备份任务全部完成!”
# 发送钉钉成功通知
if [ -n “$DINGTALK_WEBHOOK” ]; then
# 加签的钉钉通知生成
if [ -n “$DINGTALK_SECRET” ]; then
TIMESTAMP=$(date +%s%3N)
SIGN=$(echo -ne “$TIMESTAMPn$DINGTALK_SECRET” | openssl dgst -sha256 -hmac “$DINGTALK_SECRET” -binary | base64)
WEBHOOK_URL=”${DINGTALK_WEBHOOK}×tamp=${TIMESTAMP}&sign=${SIGN}”
else
WEBHOOK_URL=”$DINGTALK_WEBHOOK”
fi
curl -X POST “$WEBHOOK_URL”
-H ‘Content-Type: application/json’
-d ‘{
“msgtype”: “text”,
“text”: {
“content”: “【预发布MySQL备份成功】n备份时间:'”$BACKUP_TIME”‘n备份对象:'”${DATABASES[]}”‘n上传路径:COS桶'”$COS_BUCKET”‘的'”$COS_PATH”‘n保留天数:'”$BACKUP_RETENTION”
},
“at”: {
“isAtAll”: false
}
}’
fi
else
echo “上传到腾讯云COS失败!请检查COSCMD配置和网络!” | tee -a /var/log/mysql_backup.log
# 发送钉钉失败通知
# 这里的加签通知和成功的一样,我就简化了
if [ -n “$DINGTALK_WEBHOOK” ]; then
TIMESTAMP=$(date +%s%3N)
SIGN=$(echo -ne “$TIMESTAMPn$DINGTALK_SECRET” | openssl dgst -sha256 -hmac “$DINGTALK_SECRET” -binary | base64)
WEBHOOK_URL=”${DINGTALK_WEBHOOK}×tamp=${TIMESTAMP}&sign=${SIGN}”
curl -X POST “$WEBHOOK_URL”
-H ‘Content-Type: application/json’
-d ‘{
“msgtype”: “text”,
“text”: {
“content”: “【预发布MySQL备份失败(上传阶段)】n备份时间:'”$BACKUP_TIME”‘n请检查COSCMD配置和网络!”
},
“at”: {
“isAtAll”: true
}
}’
fi
exit 1
fi
else
echo “MySQL备份失败!请检查MySQL用户权限和密码!” | tee -a /var/log/mysql_backup.log
# 发送钉钉失败通知
if [ -n “$DINGTALK_WEBHOOK” ]; then
TIMESTAMP=$(date +%s%3N)
SIGN=$(echo -ne “$TIMESTAMPn$DINGTALK_SECRET” | openssl dgst -sha256 -hmac “$DINGTALK_SECRET” –

评论列表 (0条):
加载更多评论 Loading...