保持SSH隧道活跃:一个实用的Bash监控脚本

引言
如果您正在使用AWS DocumentDB或任何位于堡垒主机后面的云托管服务等远程资源,您可能正在使用SSH隧道来安全地访问它们。虽然设置SSH隧道很简单,但保持其活跃状态并监控其状态可能会有些棘手。
今天,我分享一个我编写的轻量级Bash脚本来设置和监控SSH隧道。它简单、可移植,并为您提供隧道状态的实时反馈。
💡 使用场景
想象一下:您有一个只能通过堡垒主机访问的AWS DocumentDB集群。您想要:
将本地端口(例如27088)转发到远程DocumentDB端口(27017)。
自动监控SSH隧道是否断开。
获得简单的分钟级状态更新。
如果脚本或终端被终止,干净地清理SSH进程。
🧩 脚本代码
以下是完整的脚本:
  1. #!/bin/bash

  2. KEY="my-bastion-host-key-pair.pem"
  3. USER="ec2-user"
  4. HOST="ec2-YYY-YYY-YYY-YYY.compute-1.amazonaws.com"
  5. REMOTE_HOST="docdb-dima-1.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com"
  6. LOCAL_PORT=27088
  7. REMOTE_PORT=27017

  8. START_TIME=$(date +%s)

  9. # 在后台启动SSH隧道
  10. ssh -i "$KEY" \
  11. -o ServerAliveInterval=60 \
  12. -o ServerAliveCountMax=3 \
  13. -L ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} \
  14. ${USER}@${HOST} -N &

  15. SSH_PID=$!

  16. # 如果脚本被终止,清理的陷阱
  17. trap "kill $SSH_PID 2>/dev/null" EXIT

  18. # 监控循环
  19. while kill -0 $SSH_PID 2>/dev/null; do
  20. NOW=$(date +%s)
  21. ELAPSED_MIN=$(( (NOW - START_TIME) / 60 ))
  22. printf "\r⏱️ SSH隧道已活跃 %d 分钟..." "$ELAPSED_MIN"
  23. sleep 60
  24. done

  25. printf "\n❌ SSH隧道在 %d 分钟后断开连接。\n" "$ELAPSED_MIN"
bash
🔍 工作原理
让我们分解一下:
SSH隧道设置:使用ssh -L通过堡垒主机将localhost:27088转发到远程DocumentDB端点。
保活选项
ServerAliveInterval=60:每60秒发送一个保活数据包。
ServerAliveCountMax=3:如果错过三个保活信号,连接被认为已死亡。
后台进程:SSH隧道在后台运行。
监控循环:每分钟检查SSH进程是否仍然活跃,使用kill -0 $SSH_PID
清理陷阱:如果脚本被中断(Ctrl+C或被杀死),它会清理SSH进程以避免僵尸隧道。
何时使用此脚本
在堡垒主机后面的服务进行开发。
需要快速本地连接到远程数据库。
想要在不使用重型工具的情况下了解隧道运行时间。
避免使用autossh或systemd等工具进行更简单的设置。
🧹 最终想法
这个脚本可能看起来很简单,但它在我长时间开发或迁移会话期间节省了无数小时来思考"我的隧道还活着吗?"
🛠️ 脚本详细解析
配置变量
  1. KEY="my-bastion-host-key-pair.pem" # SSH私钥文件路径
  2. USER="ec2-user" # 堡垒主机用户名
  3. HOST="ec2-YYY-YYY-YYY-YYY.compute-1.amazonaws.com" # 堡垒主机地址
  4. REMOTE_HOST="docdb-dima-1.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com" # 目标主机
  5. LOCAL_PORT=27088 # 本地端口
  6. REMOTE_PORT=27017 # 远程端口
bash
SSH连接参数
  1. ssh -i "$KEY" \ # 使用指定的私钥
  2. -o ServerAliveInterval=60 \ # 每60秒发送保活信号
  3. -o ServerAliveCountMax=3 \ # 最多允许3次保活失败
  4. -L ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} \ # 端口转发
  5. ${USER}@${HOST} -N & # 不执行远程命令,后台运行
bash
进程监控
  1. SSH_PID=$! # 获取SSH进程ID
  2. trap "kill $SSH_PID 2>/dev/null" EXIT # 设置退出时清理进程的陷阱

  3. while kill -0 $SSH_PID 2>/dev/null; do # 检查进程是否存活
  4. # 计算运行时间并显示状态
  5. done
bash
🔧 高级功能扩展
1. 自动重连功能
  1. #!/bin/bash

  2. KEY="my-bastion-host-key-pair.pem"
  3. USER="ec2-user"
  4. HOST="ec2-YYY-YYY-YYY-YYY.compute-1.amazonaws.com"
  5. REMOTE_HOST="docdb-dima-1.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com"
  6. LOCAL_PORT=27088
  7. REMOTE_PORT=27017
  8. MAX_RETRIES=5
  9. RETRY_DELAY=30

  10. function start_tunnel() {
  11. ssh -i "$KEY" \
  12. -o ServerAliveInterval=60 \
  13. -o ServerAliveCountMax=3 \
  14. -o ConnectTimeout=10 \
  15. -L ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} \
  16. ${USER}@${HOST} -N &
  17. return $!
  18. }

  19. function monitor_tunnel() {
  20. local pid=$1
  21. local retry_count=0
  22. while [ $retry_count -lt $MAX_RETRIES ]; do
  23. if kill -0 $pid 2>/dev/null; then
  24. printf "\r⏱️ SSH隧道活跃中... (重试次数: %d)" "$retry_count"
  25. sleep 60
  26. else
  27. printf "\n❌ SSH隧道断开,尝试重连...\n"
  28. kill $pid 2>/dev/null
  29. sleep $RETRY_DELAY
  30. start_tunnel
  31. pid=$!
  32. retry_count=$((retry_count + 1))
  33. fi
  34. done
  35. printf "\n❌ 达到最大重试次数,停止重连。\n"
  36. }

  37. # 启动初始隧道
  38. start_tunnel
  39. SSH_PID=$!
  40. trap "kill $SSH_PID 2>/dev/null; exit" EXIT

  41. # 开始监控
  42. monitor_tunnel $SSH_PID
bash
2. 日志记录功能
  1. #!/bin/bash

  2. # 添加日志功能
  3. LOG_FILE="/tmp/ssh_tunnel.log"

  4. function log_message() {
  5. echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
  6. }

  7. function start_tunnel() {
  8. log_message "启动SSH隧道..."
  9. ssh -i "$KEY" \
  10. -o ServerAliveInterval=60 \
  11. -o ServerAliveCountMax=3 \
  12. -L ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} \
  13. ${USER}@${HOST} -N &
  14. SSH_PID=$!
  15. log_message "SSH隧道已启动,PID: $SSH_PID"
  16. return $SSH_PID
  17. }

  18. # 在监控循环中添加日志
  19. while kill -0 $SSH_PID 2>/dev/null; do
  20. NOW=$(date +%s)
  21. ELAPSED_MIN=$(( (NOW - START_TIME) / 60 ))
  22. printf "\r⏱️ SSH隧道已活跃 %d 分钟..." "$ELAPSED_MIN"
  23. sleep 60
  24. done

  25. log_message "SSH隧道断开连接"
bash
3. 健康检查功能
  1. #!/bin/bash

  2. # 添加健康检查
  3. function check_tunnel_health() {
  4. # 检查本地端口是否可访问
  5. if nc -z localhost $LOCAL_PORT 2>/dev/null; then
  6. return 0
  7. else
  8. return 1
  9. fi
  10. }

  11. function monitor_tunnel() {
  12. local pid=$1
  13. while kill -0 $pid 2>/dev/null; do
  14. NOW=$(date +%s)
  15. ELAPSED_MIN=$(( (NOW - START_TIME) / 60 ))
  16. # 执行健康检查
  17. if check_tunnel_health; then
  18. printf "\r⏱️ SSH隧道健康运行 %d 分钟..." "$ELAPSED_MIN"
  19. else
  20. printf "\r⚠️ SSH隧道可能有问题 %d 分钟..." "$ELAPSED_MIN"
  21. fi
  22. sleep 60
  23. done
  24. }
bash
📊 使用示例
基本使用
  1. # 1. 保存脚本为 tunnel_monitor.sh
  2. chmod +x tunnel_monitor.sh

  3. # 2. 修改配置变量
  4. vim tunnel_monitor.sh

  5. # 3. 运行脚本
  6. ./tunnel_monitor.sh
bash
后台运行
  1. # 在后台运行并保存PID
  2. nohup ./tunnel_monitor.sh > tunnel.log 2>&1 &
  3. echo $! > tunnel.pid

  4. # 检查状态
  5. ps -p $(cat tunnel.pid)

  6. # 停止隧道
  7. kill $(cat tunnel.pid)
bash
系统服务集成
  1. # /etc/systemd/system/ssh-tunnel.service
  2. [Unit]
  3. Description=SSH Tunnel Monitor
  4. After=network.target

  5. [Service]
  6. Type=simple
  7. User=your-user
  8. ExecStart=/path/to/tunnel_monitor.sh
  9. Restart=always
  10. RestartSec=10

  11. [Install]
  12. WantedBy=multi-user.target
ini
🔍 故障排除
常见问题
权限问题
  1. # 确保SSH密钥权限正确
  2. chmod 600 my-bastion-host-key-pair.pem
bash
端口冲突
  1. # 检查端口是否被占用
  2. netstat -tlnp | grep :27088
  3. lsof -i :27088
bash
连接超时
  1. # 增加连接超时时间
  2. ssh -o ConnectTimeout=30 -o ServerAliveInterval=60 ...
bash
调试模式
  1. #!/bin/bash
  2. # 添加调试信息
  3. set -x # 启用调试模式

  4. # 在SSH命令中添加详细输出
  5. ssh -v -i "$KEY" \
  6. -o ServerAliveInterval=60 \
  7. -o ServerAliveCountMax=3 \
  8. -L ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT} \
  9. ${USER}@${HOST} -N &
bash
🎯 最佳实践
1. 安全性考虑
  1. # 使用环境变量存储敏感信息
  2. export SSH_KEY_PATH="/path/to/key.pem"
  3. export SSH_USER="ec2-user"
  4. export SSH_HOST="your-bastion-host"

  5. # 在脚本中使用
  6. ssh -i "$SSH_KEY_PATH" -o UserKnownHostsFile=/dev/null ...
bash
2. 资源管理
  1. # 限制SSH连接数量
  2. ssh -o ControlMaster=no -o ControlPath=none ...

  3. # 设置合理的超时时间
  4. ssh -o ConnectTimeout=10 -o ServerAliveInterval=60 ...
bash
3. 监控集成
  1. # 集成到监控系统
  2. function send_alert() {
  3. local message="$1"
  4. # 发送到Slack、邮件或其他通知系统
  5. curl -X POST -H 'Content-type: application/json' \
  6. --data "{\"text\":\"$message\"}" \
  7. https://hooks.slack.com/services/YOUR/WEBHOOK/URL
  8. }
bash
🚀 性能优化
1. 减少资源使用
  1. # 使用更高效的监控间隔
  2. sleep 30 # 而不是60

  3. # 优化SSH参数
  4. ssh -o Compression=yes -o TCPKeepAlive=yes ...
bash
2. 并发处理
  1. # 支持多个隧道
  2. declare -A tunnel_pids

  3. function start_multiple_tunnels() {
  4. local tunnels=(
  5. "27088:docdb1:27017"
  6. "27089:docdb2:27017"
  7. "27090:redis:6379"
  8. )
  9. for tunnel in "${tunnels[@]}"; do
  10. IFS=':' read -r local_port remote_host remote_port <<< "$tunnel"
  11. start_single_tunnel $local_port $remote_host $remote_port
  12. done
  13. }
bash
📈 监控指标
1. 连接统计
  1. # 记录连接统计
  2. function log_stats() {
  3. local uptime=$1
  4. local retry_count=$2
  5. echo "隧道统计:" >> tunnel_stats.log
  6. echo " 运行时间: ${uptime}分钟" >> tunnel_stats.log
  7. echo " 重试次数: ${retry_count}" >> tunnel_stats.log
  8. echo " 时间戳: $(date)" >> tunnel_stats.log
  9. echo "---" >> tunnel_stats.log
  10. }
bash
2. 性能监控
  1. # 监控网络延迟
  2. function check_latency() {
  3. local latency=$(ping -c 1 $HOST | grep "time=" | cut -d "=" -f4)
  4. echo "延迟: $latency"
  5. }
bash
🎉 总结
这个SSH隧道监控脚本虽然简单,但非常实用。它提供了:
自动隧道监控
实时状态反馈
优雅的进程清理
可扩展的架构
易于集成到现有系统
对于需要稳定SSH隧道连接的开发者和运维人员来说,这是一个非常有价值的工具。