实战指南:高效批量测试SSH连接的最佳实践与避坑手册

实战指南:高效批量测试SSH连接的最佳实践与避坑手册

引言:当自动化遇到现实挑战

在运维工作中,我们常常需要管理大量服务器。最近我遇到一个实际需求:需要快速测试一批服务器的SSH连接性,所有服务器都使用相同的root账号和密码。这听起来应该很简单,但实施过程中却遇到了各种意想不到的问题。本文将详细记录这次实战经历,分享完整的解决方案,并总结出可靠的最佳实践。

需求分析:明确目标与约束条件

原始需求

  • 测试一批IP地址的22端口SSH连接
  • 使用固定账号:root
  • 使用固定密码:mn150@2099A
  • 需要超时控制(2-3秒)
  • 结果分类:成功与失败的IP分别保存
  • 支持批量处理(IP数量可能达到数百个)

技术约束

  • 运行环境:CentOS 7
  • 网络条件:可能存在网络延迟和防火墙限制
  • 安全考虑:虽然使用密码认证,但这是测试环境

技术选型:多种方案的对比

方案一:sshpass + 循环(最直接)

bash 复制代码
sshpass -p '密码' ssh -o ConnectTimeout=5 root@IP "命令"

优点 :简单直接,无需额外依赖
缺点:缺乏并行处理,速度慢

方案二:expect脚本(更健壮)

bash 复制代码
expect << EOF
spawn ssh root@IP
expect "password:" { send "密码\r" }
expect eof
EOF

优点 :处理交互更稳定
缺点:语法复杂,调试困难

方案三:Python + Paramiko(功能强大)

python 复制代码
import paramiko
ssh = paramiko.SSHClient()
ssh.connect(hostname=ip, username='root', password='密码', timeout=3)

优点 :功能全面,错误处理完善
缺点:需要Python环境

方案四:并行处理(效率最高)

bash 复制代码
cat iplist.txt | parallel -j 20 "测试命令"

优点 :大幅提升测试速度
缺点:并发控制复杂,容易出错

实战过程:从简单到复杂的演进

第一阶段:基础脚本开发

最初的脚本很简单:

bash 复制代码
#!/bin/bash
PASSWORD='mn150@2099A'

while read ip; do
    sshpass -p "$PASSWORD" ssh -o ConnectTimeout=3 root@$ip "echo ok"
    if [ $? -eq 0 ]; then
        echo "$ip" >> success.txt
    else
        echo "$ip" >> fail.txt
    fi
done < iplist.txt

这个脚本能工作,但存在几个问题:

  1. 没有进度显示
  2. 错误处理不完善
  3. 串行执行,速度慢

第二阶段:添加功能增强

改进版本增加了以下功能:

bash 复制代码
#!/bin/bash
# 配置参数
PASSWORD='mn150@2099A'
TIMEOUT=3
IP_FILE="iplist.txt"
SUCCESS_FILE="success_ips.txt"
FAILED_FILE="failed_ips.txt"

# 统计和进度
TOTAL=$(wc -l < "$IP_FILE")
COUNT=0

echo "开始测试 $TOTAL 个IP..."

while read ip; do
    COUNT=$((COUNT + 1))
    echo -n "[$COUNT/$TOTAL] 测试 $ip ... "
    
    timeout $TIMEOUT sshpass -p "$PASSWORD" ssh \
        -o ConnectTimeout=$TIMEOUT \
        -o StrictHostKeyChecking=no \
        root@"$ip" "exit" 2>/dev/null
    
    if [ $? -eq 0 ]; then
        echo "成功"
        echo "$ip" >> "$SUCCESS_FILE"
    else
        echo "失败"
        echo "$ip" >> "$FAILED_FILE"
    fi
done < "$IP_FILE"

这个版本已经有了基本框架,但仍然不够理想。

第三阶段:遇到的坑与解决方案

坑1:脚本编码问题

现象 :脚本只运行了4个IP就停止
错误信息./ssh_batch_test.sh: line 1: i#!/bin/bash: No such file or directory

根本原因:脚本文件编码问题,可能是从Windows复制过来,或者编辑器保存时加入了BOM头。

解决方案

bash 复制代码
# 检查文件编码
file ssh_batch_test.sh

# 修复编码问题
sed -i '1s/^.*#!/#!/' ssh_batch_test.sh
# 或者重新创建脚本
cat > new_script.sh << 'EOF'
#!/bin/bash
# 正确内容
EOF
坑2:IP文件格式问题

现象:虽然IP文件有200多个IP,但脚本只处理了4个

可能原因

  1. Windows换行符(CRLF)
  2. 文件中有特殊字符
  3. 读取逻辑有缺陷

解决方案

bash 复制代码
# 检查文件格式
cat -A iplist.txt | head -10

# 修复换行符
dos2unix iplist.txt
# 或者使用sed
sed -i 's/\r$//' iplist.txt

# 清理文件
grep -v '^[[:space:]]*$' iplist.txt | grep -v '^#' > iplist_clean.txt
坑3:SSH选项冲突

现象:手动测试成功,但脚本测试失败

关键发现-o BatchMode=yes选项会禁用密码认证!

错误配置

bash 复制代码
# 这个会失败
sshpass -p '密码' ssh -o BatchMode=yes root@IP "命令"

# 正确的应该是
sshpass -p '密码' ssh -o BatchMode=no root@IP "命令"
# 或者干脆不要BatchMode选项
坑4:并发写入冲突

现象:并行脚本运行异常,结果不完整

原因:多个进程同时写入同一个文件

解决方案

bash 复制代码
# 每个进程写入独立文件,最后合并
TEMP_DIR=$(mktemp -d)
# 每个进程写入 $TEMP_DIR/result_$PID
# 最后 cat $TEMP_DIR/* > final_result.txt

第四阶段:最终稳定版本

经过多次调试,最终得到了稳定可靠的版本:

bash 复制代码
#!/bin/bash
# SSH批量测试脚本 - 最终稳定版

set -u  # 使用未定义变量时报错
set -e  # 遇到错误时退出

# 配置参数
PASSWORD='mn150@2099A'
CONNECT_TIMEOUT=3
IP_FILE="${1:-iplist.txt}"

# 生成带时间戳的结果文件
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
SUCCESS_FILE="success_ips_${TIMESTAMP}.txt"
FAILED_FILE="failed_ips_${TIMESTAMP}.txt"

# 验证环境
validate_environment() {
    # 检查sshpass
    if ! command -v sshpass &> /dev/null; then
        echo "错误: sshpass 未安装"
        echo "请执行: sudo yum install -y epel-release sshpass"
        exit 1
    fi
    
    # 检查IP文件
    if [ ! -f "$IP_FILE" ]; then
        echo "错误: IP文件 $IP_FILE 不存在"
        exit 1
    fi
    
    # 检查文件是否可读
    if [ ! -r "$IP_FILE" ]; then
        echo "错误: 无法读取IP文件 $IP_FILE"
        exit 1
    fi
}

# 清理和验证IP列表
prepare_ip_list() {
    local input_file="$1"
    local output_file="$2"
    
    # 清空输出文件
    > "$output_file"
    
    # 处理IP文件:移除注释、空行、换行符
    while IFS= read -r line || [ -n "$line" ]; do
        # 移除换行符和回车符
        line=$(echo "$line" | tr -d '\r')
        
        # 跳过空行和注释
        if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then
            continue
        fi
        
        # 提取IP(移除可能的后缀和注释)
        ip=$(echo "$line" | sed 's/[[:space:]].*$//' | sed 's/#.*$//')
        
        # 验证IP格式
        if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
            echo "$ip" >> "$output_file"
        else
            echo "警告: 跳过无效IP格式: $ip" >&2
        fi
    done < "$input_file"
}

# 测试单个IP
test_single_ip() {
    local ip="$1"
    
    # 使用timeout防止命令挂起
    timeout ${CONNECT_TIMEOUT} sshpass -p "$PASSWORD" ssh \
        -o ConnectTimeout=${CONNECT_TIMEOUT} \
        -o StrictHostKeyChecking=no \
        -o PasswordAuthentication=yes \
        -o BatchMode=no \
        -o LogLevel=ERROR \
        root@"$ip" "exit 0" 2>/dev/null
    
    return $?
}

# 主函数
main() {
    echo "========================================"
    echo "SSH批量连接测试工具"
    echo "========================================"
    echo "账号:        root"
    echo "超时时间:    ${CONNECT_TIMEOUT}秒"
    echo "IP文件:      $IP_FILE"
    echo "结果文件:"
    echo "  成功IP:    $SUCCESS_FILE"
    echo "  失败IP:    $FAILED_FILE"
    echo "========================================"
    
    # 验证环境
    validate_environment
    
    # 准备IP列表
    local clean_ip_file="/tmp/clean_ips_$$.txt"
    prepare_ip_list "$IP_FILE" "$clean_ip_file"
    
    local total_ips=$(wc -l < "$clean_ip_file" 2>/dev/null || echo 0)
    if [ $total_ips -eq 0 ]; then
        echo "错误: 没有有效的IP地址可以测试"
        exit 1
    fi
    
    echo "找到 $total_ips 个有效IP地址"
    echo ""
    
    # 清空结果文件
    > "$SUCCESS_FILE"
    > "$FAILED_FILE"
    
    # 开始测试
    echo "开始测试..."
    echo "----------------------------------------"
    
    local tested=0
    local success=0
    local failed=0
    
    while read ip; do
        tested=$((tested + 1))
        
        # 显示进度(每10个IP显示一次)
        if [ $((tested % 10)) -eq 0 ] || [ $tested -eq $total_ips ]; then
            printf "\r进度: %d/%d (%.1f%%)" $tested $total_ips $(echo "scale=1; $tested*100/$total_ips" | bc)
        fi
        
        # 测试连接
        if test_single_ip "$ip"; then
            echo "$ip" >> "$SUCCESS_FILE"
            success=$((success + 1))
        else
            echo "$ip" >> "$FAILED_FILE"
            failed=$((failed + 1))
        fi
    done < "$clean_ip_file"
    
    echo ""
    echo "----------------------------------------"
    echo ""
    
    # 生成报告
    echo "测试完成!"
    echo "========================================"
    echo "统计结果:"
    echo "  总IP数:    $total_ips"
    echo "  成功数:    $success"
    echo "  失败数:    $failed"
    
    if [ $total_ips -gt 0 ]; then
        local success_rate=$(echo "scale=2; $success * 100 / $total_ips" | bc)
        echo "  成功率:    ${success_rate}%"
    fi
    
    echo ""
    echo "结果文件已生成:"
    echo "  $SUCCESS_FILE"
    echo "  $FAILED_FILE"
    
    # 显示示例结果
    if [ -s "$SUCCESS_FILE" ]; then
        echo ""
        echo "成功IP示例(前5个):"
        head -5 "$SUCCESS_FILE" | cat -n
    fi
    
    # 清理临时文件
    rm -f "$clean_ip_file"
}

# 执行主函数
main

关键技术与最佳实践

1. 健壮的IP文件处理

bash 复制代码
# 正确处理各种格式的IP文件
while IFS= read -r line || [ -n "$line" ]; do
    # 处理换行符
    line=$(echo "$line" | tr -d '\r')
    
    # 验证IP格式
    if [[ "$line" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        # 有效IP
    fi
done < ipfile.txt

2. 适当的SSH选项配置

bash 复制代码
# 推荐的SSH选项组合
sshpass -p "$PASSWORD" ssh \
    -o ConnectTimeout=$TIMEOUT \      # 连接超时
    -o StrictHostKeyChecking=no \     # 不检查主机密钥(测试环境)
    -o PasswordAuthentication=yes \   # 允许密码认证
    -o BatchMode=no \                 # 不禁用密码认证!
    -o LogLevel=ERROR \               # 只显示错误日志
    root@"$IP" "命令"

3. 合理的错误处理

bash 复制代码
# 使用timeout防止命令挂起
timeout $TIMEOUT 命令

# 检查退出码
if [ $? -eq 0 ]; then
    # 成功
elif [ $? -eq 124 ]; then
    # 超时
elif [ $? -eq 5 ]; then
    # 认证失败
else
    # 其他错误
fi

4. 进度反馈机制

bash 复制代码
# 显示进度百分比
printf "\r进度: %d/%d (%.1f%%)" $current $total $(echo "scale=1; $current*100/$total" | bc)

# 或使用进度条
draw_progress_bar() {
    local current=$1
    local total=$2
    local width=50
    local percent=$((current * 100 / total))
    local completed=$((current * width / total))
    
    printf "\r["
    printf "%${completed}s" | tr ' ' '='
    printf "%$((width - completed))s" | tr ' ' '.'
    printf "] %3d%%" $percent
}

性能优化技巧

并行处理方案

对于大量IP的测试,顺序执行太慢。以下是安全的并行方案:

bash 复制代码
#!/bin/bash
# 安全的并行测试

PASSWORD='mn150@2099A'
TIMEOUT=3
MAX_JOBS=10

# 创建临时目录存放结果
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT

# 测试函数
test_ip() {
    local ip=$1
    local pid=$$
    
    timeout $TIMEOUT sshpass -p "$PASSWORD" ssh \
        -o ConnectTimeout=$TIMEOUT \
        -o StrictHostKeyChecking=no \
        root@"$ip" "exit" 2>/dev/null
    
    if [ $? -eq 0 ]; then
        echo "$ip" >> "$TEMP_DIR/success_$pid"
    else
        echo "$ip" >> "$TEMP_DIR/failed_$pid"
    fi
}

# 导出函数
export -f test_ip
export PASSWORD TIMEOUT TEMP_DIR

# 并行执行
cat iplist.txt | xargs -P $MAX_JOBS -I {} bash -c 'test_ip "$@"' _ {}

# 合并结果
cat "$TEMP_DIR"/success_* 2>/dev/null | sort -u > success.txt
cat "$TEMP_DIR"/failed_* 2>/dev/null | sort -u > failed.txt

资源限制

bash 复制代码
# 避免过多的并发连接
ulimit -n 4096  # 增加文件描述符限制

# 限制每个进程的资源使用
prlimit --nproc=100 --nofile=100 --cpu=10 sshpass ...

安全注意事项

尽管这是测试脚本,但仍需注意安全:

  1. 密码处理:不要在脚本中硬编码密码,考虑使用环境变量或配置文件
  2. 结果保护:测试结果可能包含敏感信息,妥善保管
  3. 权限控制:脚本应运行在受控环境中
  4. 日志清理:定期清理日志文件
bash 复制代码
# 更安全的方式:从环境变量读取密码
PASSWORD=${SSH_PASSWORD:-'default_password'}

# 或者从加密文件读取
if [ -f ~/.ssh_test_pass ]; then
    PASSWORD=$(openssl enc -d -aes-256-cbc -in ~/.ssh_test_pass)
fi

扩展功能

1. 添加重试机制

bash 复制代码
test_with_retry() {
    local ip=$1
    local max_retries=2
    
    for ((i=1; i<=max_retries; i++)); do
        if test_single_ip "$ip"; then
            return 0
        fi
        sleep 1
    done
    return 1
}

2. 收集系统信息

bash 复制代码
# 测试时顺便收集信息
collect_info() {
    local ip=$1
    sshpass -p "$PASSWORD" ssh root@"$ip" "
        echo '=== 系统信息 ==='
        uname -a
        echo ''
        echo '=== 内存信息 ==='
        free -h
        echo ''
        echo '=== 磁盘信息 ==='
        df -h
    " > "info_${ip}.txt" 2>/dev/null
}

3. 生成HTML报告

bash 复制代码
generate_html_report() {
    cat > report.html << EOF
<html>
<head>
    <title>SSH测试报告</title>
    <style>
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #ddd; padding: 8px; }
        th { background-color: #f2f2f2; }
        .success { background-color: #dff0d8; }
        .failed { background-color: #f2dede; }
    </style>
</head>
<body>
    <h1>SSH连接测试报告</h1>
    <p>生成时间: $(date)</p>
    <p>总计: $total_ips 个IP</p>
    <p>成功: $success 个</p>
    <p>失败: $failed 个</p>
    
    <h2>成功IP列表</h2>
    <table>
        <tr><th>序号</th><th>IP地址</th></tr>
        $(cat success.txt | awk '{print "<tr class=\"success\"><td>"NR"</td><td>"$1"</td></tr>"}')
    </table>
</body>
</html>
EOF
}

总结与建议

经过这次实战,我总结了以下经验:

核心原则

  1. 简单可靠胜过复杂先进:先确保基础功能稳定
  2. 逐步增强:从简单版本开始,逐步添加功能
  3. 充分测试:小范围测试验证后再批量运行
  4. 错误处理:预见到可能的问题并提前处理

技术建议

  1. 使用set -euo pipefail让脚本更健壮
  2. 总是验证输入数据的有效性
  3. 为长时间运行的任务添加进度反馈
  4. 使用临时文件处理并发写入问题
  5. 清理临时文件和资源

运维建议

  1. 保持脚本的日志记录能力
  2. 考虑添加监控和告警机制
  3. 定期回顾和优化脚本
  4. 文档化脚本的使用和维护方法

批量测试SSH连接虽然看起来简单,但实际实施时会遇到各种预料之外的问题。通过系统的分析和逐步的改进,我们最终得到了一个稳定、高效、功能完善的解决方案。这个过程不仅解决了眼前的问题,也为类似任务积累了宝贵经验。

记住:好的自动化脚本不是一蹴而就的,而是在不断遇到问题、解决问题的过程中逐渐完善起来的。每个错误和调试过程都是学习和提高的机会。

相关推荐
wheeldown2 小时前
【Linux网络编程】应用层自定义协议和序列化
linux·运维·网络
晚风吹长发2 小时前
初步理解Linux中的信号概念以及信号产生
linux·运维·服务器·算法·缓冲区·inode
Zfox_3 小时前
【Docker#2】容器化虚拟化
运维·后端·docker·容器
xiaozenbin3 小时前
宝塔8.5在nginx中部署动态php页面出现找不到页面处理
运维·nginx·php
晨非辰3 小时前
Linux权限实战速成:用户切换/文件控制/安全配置15分钟掌握,解锁核心操作与权限模型内核逻辑
linux·运维·服务器·c++·人工智能·后端
草莓熊Lotso3 小时前
Linux 进程创建与终止全解析:fork 原理 + 退出机制实战
linux·运维·服务器·开发语言·汇编·c++·人工智能
信创新态势5 小时前
财经媒体研判:内存疯涨多米诺效应推倒,服务器涨价箭在弦上
运维·服务器·媒体
Physicist in Geophy.5 小时前
本地调用服务器数据
运维·服务器
爱莉希雅&&&11 小时前
LVS+Keepalived+DNS+Web+NFS 高可用集群项目完整部署流程
运维·nginx·dns·lvs·keepalived·nfs·ipvsadm