SSH 远程自动化实战

系统讲解 SSH 客户端配置与免密登录、ssh-agent 密钥托管、远程执行命令与脚本、rsync 增量同步,以及隧道转发、安全加固与批量主机管理的完整实践。

1. SSH 连接与客户端配置

一句话总结: SSH 是远程自动化的传输底座,客户端配置写在 ~/.ssh/config,主机别名、端口、密钥都能按主机定制。

ssh user@host 是最基本用法,但每次都敲全参数很累,~/.ssh/config 让你用别名一键连接。

# ~/.ssh/config 示例
Host web
    HostName 203.0.113.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/deploy_key

# 之后直接
ssh web uptime

1.1 首次连接与 known_hosts

# 手动首次连接(会提示确认指纹)
ssh web

# 脚本场景跳过指纹交互(有安全代价,慎用)
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null web uptime

一句话总结: 自动化脚本里首次连接会卡在指纹确认,-o StrictHostKeyChecking=no 可跳过,但请配合固定 known_hosts 或提前预置指纹。

1.2 超时与连接保活

# 连接超时与命令超时
ssh -o ConnectTimeout=5 -o ServerAliveInterval=30 web uptime

# 批量连接时避免卡死
timeout 10 ssh web 'top -b -n 1' || echo "连接超时"

2. 免密登录与 ssh-agent

一句话总结: 免密登录靠公钥落盘到远端 authorized_keys,ssh-keygen 生成、ssh-copy-id 安装、ssh-agent 托管私钥密码。

# 生成密钥(-t ed25519 更现代,-N 空口令)
ssh-keygen -t ed25519 -N '' -f ~/.ssh/id_ed25519

# 安装公钥到远端
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host

# 手动安装等价命令
cat ~/.ssh/id_ed25519.pub | ssh user@host \
  'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'

2.1 ssh-agent 托管密钥

私钥带口令时,每次 SSH 都要输入;ssh-agent 在会话内缓存解密后的密钥。

# 启动 agent 并添加密钥
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

# 查看托管的指纹
ssh-add -l

# 转发 agent 到远端(慎用,仅受信主机)
ssh -A web

一句话总结: ssh-agent 免去反复输私钥口令;-A 的 agent 转发有中间人风险,非受信环境别开。

2.2 脚本里的非交互密钥

CI/CD 里通常把私钥作为变量注入,写入临时位置并设权限。

#!/usr/bin/env bash
set -euo pipefail

echo "$DEPLOY_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no deploy@host \
  'systemctl restart web'
rm -f /tmp/deploy_key

3. 远程执行命令

一句话总结: ssh host 'cmd' 执行单条远程命令,复杂逻辑写成本地脚本再 ssh host 'bash -s' 通过 stdin 传过去执行。

# 单条命令
ssh web 'uname -a && df -h /'

# 多条命令用引号包成一段
ssh web 'cd /app && git pull && make deploy'

# 远程执行本地脚本:stdin 传输
ssh web 'bash -s' < ./remote_task.sh

3.1 环境变量与登录 shell

ssh 默认非交互 shell,$PATH 可能不含家目录下的 bin——远程命令里尽量用绝对路径。

# 显式加载环境
ssh web 'export PATH=$HOME/.local/bin:$PATH && mytool run'

# 强制登录 shell
ssh web 'bash -lc "mytool run"'

一句话总结: 远程命令的 PATH 与非交互环境与本地不同,依赖自定义 bin 时用 bash -lc 加载登录环境,或写绝对路径。

3.2 远程返回值与引号层级

# 获取远程退出码
ssh web 'grep -q error /var/log/app.log' && echo "日志有 error" || echo "日志干净"

# 引号层级:本地引号剥掉后,远程再剥一层
ssh web "echo \"主机: \$(hostname)\""
# 若想远程展开 $(), 单引号包裹本地
ssh web 'echo "主机: $(hostname)"'

4. 文件同步与 rsync

一句话总结: rsync 按块差异传输,-a 归档、-z 压缩、-P 断点续传,是跨机同步的事实标准。

# 基本同步:本地 → 远端
rsync -avz ./dist/ web:/var/www/

# 远端 → 本地
rsync -avz web:/var/log/app.log /backup/

# 删除远端多余文件(--delete 谨慎使用)
rsync -avz --delete ./dist/ web:/var/www/

4.1 排除与限速

# 排除缓存目录、限速 1MB/s
rsync -avz --exclude='node_modules' --exclude='.git' \
  --bwlimit=1000 ./ web:/app/

# 排除文件列表文件
rsync -avz --exclude-from=exclude.txt ./ web:/app/

一句话总结: --exclude 跳过无用的目录、--bwlimit 限速防占满带宽,生产同步这两项几乎必配。

4.2 增量备份与校验

# 增量到带日期的目录(--link-dest 硬链接去重)
rsync -avz --link-dest=/backup/latest /data/ /backup/$(date +%F)/

# 只校验不传输
rsync -avzn --itemize-changes ./ web:/app/

5. SSH 隧道与端口转发

一句话总结: -L 本地转发、-R 远程转发、-D 动态 SOCKS,隧道把不安全的明文连接装进加密的 SSH 通道。

# 本地转发:本地 8080 → 远端内网 5432(数据库)
ssh -L 8080:db-internal:5432 jump
# 之后本地应用连 localhost:8080 即到达内网数据库

# 动态 SOCKS 代理
ssh -D 1080 jump
# 浏览器配 socks5://localhost:1080

# 远程转发:把本地服务暴露给远端
ssh -R 9000:localhost:80 web

5.1 隧道后台化

# 后台常驻隧道
ssh -fN -L 8080:db-internal:5432 jump

# 配置里声明常驻隧道
# Host jump
#     LocalForward 8080 db-internal:5432
ssh -fN jump

一句话总结: -fN 后台常驻不执行命令;隧道端口被占用时会连接失败,用 -o ExitOnForwardFailure=yes 提前暴露错误。

5.2 跳板机串联

# 经跳板机到达内网:ProxyJump
ssh -J jump db-host uptime

# 配置文件串联
# Host db
#     HostName 10.0.0.5
#     ProxyJump jump
ssh db uptime

6. 安全加固

一句话总结: 安全加固三件套:禁用口令登录、禁用 root 直连、限制来源 IP;密钥权限与 known_hosts 指纹校验是自动化安全的前提。

# /etc/ssh/sshd_config 关键项
# PermitRootLogin no
# PasswordAuthentication no
# PubkeyAuthentication yes
# AllowUsers deploy ops

# 修改后重载
sudo systemctl reload sshd

6.1 密钥与 known_hosts 管理

# 私钥权限必须收紧
chmod 600 ~/.ssh/id_ed25519
chmod 700 ~/.ssh

# 预置 known_hosts,避免首次连接交互
ssh-keyscan -H web >> ~/.ssh/known_hosts

# 校验指纹后连接
ssh-keygen -F web -l

一句话总结: 自动化安全三要点:私钥 600、预置 known_hosts 防中间人、禁用口令与 root 直连防暴力破解。

6.2 fail2ban 与白名单

# fail2ban 拦截暴力破解(示意)
sudo apt install fail2ban
sudo systemctl enable --now fail2ban

# 防火墙只放行白名单 IP
sudo ufw allow from 203.0.113.0/24 to any port 22

7. 批量主机管理

一句话总结: 批量 SSH 的核心是「主机清单 + 并发 + 失败隔离」,把结果收敛成一份汇总报告,而不是被一条失败卡死。

7.1 主机清单与并发执行

#!/usr/bin/env bash
set -euo pipefail

hosts=(web1 web2 db1)

# 串行巡检
for h in "${hosts[@]}"; do
  echo "== $h"
  ssh "$h" uptime || echo "  $h 失败"
done

# 并发巡检:& + wait
for h in "${hosts[@]}"; do
  ssh "$h" 'uptime; df -h /' > "report_$h.txt" &
done
wait
cat report_web*.txt report_db*.txt

7.2 结果汇总与失败重试

# 失败主机单独收集
fail=0
for h in "${hosts[@]}"; do
  if ! ssh -o ConnectTimeout=5 "$h" 'true' 2>/dev/null; then
    echo "$h 不可达" >&2
    ((fail++))
  fi
done
echo "不可达主机数: $fail"

# 超时主机跳过,不拖垮整批
for h in "${hosts[@]}"; do
  timeout 8 ssh "$h" 'uptime' 2>/dev/null || echo "$h: 超时" 
done

一句话总结: 批量巡检三原则:每台主机超时隔离、失败不中断整批、结果落盘合并;并行用 & + wait 或 parallel 都行。

7.3 远程批量部署脚本

#!/usr/bin/env bash
set -euo pipefail

deploy_one() {
  local host="$1"
  echo "==> 部署 $host"
  rsync -az --delete ./app/ "$host:/opt/app/" || return 1
  ssh "$host" 'sudo systemctl restart app && sudo systemctl is-active app' || return 1
  echo "    $host OK"
}

for h in web1 web2 web3; do
  deploy_one "$h" || echo "    $h 失败"
done

8. 总结

环节要点
客户端配置~/.ssh/config 别名化,端口/密钥/跳板按主机定制
免密登录ssh-keygen 生成、ssh-copy-id 安装、agent 托管口令
远程执行ssh host 'cmd'、stdin 传本地脚本、bash -lc 加载环境
文件同步rsync -avz 归档压缩、--delete 对齐、--bwlimit 限速
隧道-L/-R/-D 三类转发、-fN 后台常驻、ProxyJump 跳板
安全禁口令/禁 root/白名单,私钥 600、known_hosts 预置
批量清单 + 并发 & + 超时隔离 + 结果落盘汇总

SSH 自动化把"单机脚本"升级成"多机编排":配置别名化、免密密钥化、批量化并发三步走,配合 rsync 同步与隧道转发,一台机器就能管好一个集群。定时任务与并发结合的下一步,是 cron 与 systemd timer。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「shell」更多文章

  1. 脚本性能优化实战
  2. 网络请求与诊断实战
  3. 定时任务调度实战