公众号旧文
多服务器做主备主控,免密 SSH 的注意事项
#终于可以关掉密码登录
当服务器数量从几台增长到十几台、几十台之后,单靠记住每台服务器密码,然后逐台登录的方式很快就会失控。
更合理的做法,是建立一台统一的主控服务器,通过内网 IP、Tailscale、WireGuard 或其他私网方式,集中 SSH 登录所有节点,再通过统一的 hosts.txt 和批量脚本做日常运维。
但只配置一台主控还不够。
真正稳定的运维体系,至少应该解决两个问题:
- 主控本身不能成为单点故障
- 所有管理入口都应该使用独立、可撤销的 SSH 密钥
这次实际搭建下来,最终形成了一套比较清晰的结构。
一、主控服务器一定要有主备
最开始的架构很简单:
JP-IIJ
↓
所有服务器
JP-IIJ 上维护:
/opt/cluster/hosts.txt
/opt/cluster/scripts/
并且可以免密 SSH 登录 hosts.txt 中的全部服务器。
这种方式日常使用非常方便。
例如批量检查:
cd /opt/cluster
./scripts/check-cluster.sh
就可以一次查看所有服务器的:
- hostname
- 时区
- vnStat
- Tailscale 版本
- 磁盘占用
- 内存使用
问题在于:
如果 JP-IIJ 某一天失联怎么办?
如果所有集中管理能力都依赖这一台服务器,那么主控自己就成了最大的单点故障。
因此更合理的架构应该是:
┌── JP-IIJ 主控
│
管理体系 ──────┤
│
└── JP-PRO 备用主控
两台机器都可以独立访问全部服务器。
最终效果:
JP-IIJ → 全部节点
JP-PRO → 全部节点
这样即使 JP-IIJ 完全失联,也只需要登录 JP-PRO:
ssh root@JP-PRO
cd /opt/cluster
就能继续进行集群管理。
二、备用主控不要复制主控私钥
这里有一个很重要的安全原则。
最省事的做法显然是:
把 JP-IIJ 的 SSH 私钥复制到 JP-PRO
这样两台机器马上都能登录所有服务器。
但不推荐这么做。
更好的方式是:
JP-IIJ
└── Key A
JP-PRO
└── Key B
两台主控各自生成独立密钥。
例如 JP-PRO:
ssh-keygen \
-t ed25519 \
-f /root/.ssh/id_ed25519_cluster \
-N '' \
-C 'jp-pro-cluster-admin'
然后只把:
id_ed25519_cluster.pub
下发到其他服务器。
私钥:
id_ed25519_cluster
永远只留在 JP-PRO 本机。
这样有一个非常现实的好处:
如果 JP-PRO 某一天被入侵,只需要从所有服务器的:
/root/.ssh/authorized_keys
删除:
jp-pro-cluster-admin
对应的公钥即可。
JP-IIJ 完全不受影响。
如果两台机器共用同一把私钥,就无法做到这种精细撤权。
三、备用主控最好同步完整运维目录
只配置 SSH 还不算真正的备用主控。
如果 JP-IIJ 上有:
/opt/cluster/
其中包含:
hosts.txt
scripts/
audit/
backup/
README.md
upgrade-tailscale.sh
find-cert.sh
search-history.sh
那么最好直接同步到备用主控。
例如:
scp -r /opt/cluster/ root@100.64.0.20:/opt/
这样 JP-PRO 不只是“能 SSH 到其他机器”,而是连运维工具、脚本、主机清单也都有一份。
真正验证备用主控是否可用,不应该只测试:
ssh root@100.64.0.3
而应该直接跑一遍真实脚本:
cd /opt/cluster
./scripts/check-cluster.sh
只有当它可以正常遍历全部节点,才说明备用主控是真的可以接管。
四、最好再保留本地 Windows 管理入口
主备服务器都在云上,仍然不代表万无一失。
更稳妥的结构是:
JP-IIJ
JP-PRO
Windows A
Windows B
四个管理入口分别拥有独立密钥。
例如:
JP-IIJ → Key A
JP-PRO → Key B
Windows A → Key C
Windows B → Key D
这样即使两台云主控都发生问题,只要自己的 Windows 还能连接 Tailscale,就仍然可以直接:
ssh root@100.64.0.15
进入任意服务器。
这实际上已经从“主备”进一步变成了:
多入口管理
可靠性会高很多。
五、批量配置免密登录时,最容易踩的坑
免密 SSH 本身并不复杂,但批量操作时有几个细节非常容易出问题。
1. 每台管理设备使用独立密钥
这是最重要的一条。
不要把同一把私钥复制到:
主控 A
主控 B
Windows A
Windows B
应该分别生成:
jp-iij-admin
jp-pro-cluster-admin
windows-cluster-admin
home-windows-cluster-admin
这样以后哪台设备丢失,就只撤销哪一把 key。
2. 只复制公钥,不复制私钥
服务器之间需要分发的是:
id_ed25519.pub
或者:
id_ed25519_cluster.pub
不是:
id_ed25519
私钥应该永远只保留在发起 SSH 登录的那台设备。
3. authorized_keys 要避免重复写入
最简单的追加方法:
cat id_ed25519.pub >> /root/.ssh/authorized_keys
能用,但如果重复执行很多次,会出现重复公钥。
更稳妥的是:
PUBKEY=”$(cat id_ed25519.pub)”
grep -qxF ”$PUBKEY” /root/.ssh/authorized_keys ||
echo ”$PUBKEY” >> /root/.ssh/authorized_keys
这样即使脚本重复执行,也不会重复插入。
4. 权限必须正确
OpenSSH 对权限比较敏感。
推荐统一:
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
chmod 600 /root/.ssh/id_ed25519
如果权限过宽,有些 SSH Server 会直接拒绝使用公钥。
5. while read 中调用 SSH 要加 -n
这是批量脚本里一个很隐蔽的问题。
错误写法:
while read -r host; do
ssh ”$host” hostname
done < hosts.txt
第一台执行完后,循环可能直接结束。
原因是 SSH 继承了 stdin,把后面的 hosts.txt 内容读掉了。
正确写法:
while read -r host; do
ssh -n ”$host” hostname
done < hosts.txt
或者:
ssh ”$host” hostname </dev/null
批量 SSH 时最好养成默认加 -n 的习惯。
六、Host Key 和登录密钥是两回事
很多人第一次批量配置时会混淆两个概念:
SSH Host Key
和:
SSH User Key
它们不是同一件东西。
用户密钥决定:
你有没有权限登录服务器。
Host Key 决定:
你连接的到底是不是原来的那台服务器。
因此即使公钥配置完全正确,也可能遇到:
Host key verification failed
或者:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
这种错误和登录私钥本身没有关系。
例如服务器重装后,SSH Host Key 改变,本地 known_hosts 仍保存旧值,就会拒绝连接。
确认服务器身份确实正确后,可以:
ssh-keygen -R 100.64.0.3
然后重新建立连接。
推荐使用:
StrictHostKeyChecking=accept-new
而不是:
StrictHostKeyChecking=no
accept-new 会自动接受第一次出现的新主机,但如果已经记录的主机指纹发生变化,仍然会报警。
安全性更合理。
七、不要把“批量写入成功”当成“免密登录成功”
在批量下发公钥时,可能看到:
OK
OK
OK
OK
但这只能说明:
SSH 到服务器并执行写入命令成功。
并不能证明:
新生成的那把私钥真的可以登录服务器。
因此必须做第二轮验证。
例如:
ssh \
-i /root/.ssh/id_ed25519_cluster \
-o IdentitiesOnly=yes \
root@100.64.0.3 hostname
其中:
-i
明确指定新私钥。
而:
IdentitiesOnly=yes
可以防止 SSH 偷偷尝试其他已有密钥。
这样才能真正确认:
当前成功登录用的,就是刚刚配置的那一把 key。
八、批量测试一定要考虑单台机器卡死
ConnectTimeout=5 只保证连接阶段不会无限卡住。
它不保证:
SSH 建连之后
远程命令也一定按时返回。
所以批量检测时,最好再加一层外部超时。
Windows PowerShell 可以使用:
Start-Job
Wait-Job -Timeout 10
这样某一台机器即使异常,也只会显示:
TIMEOUT
而不会阻塞后面所有服务器。
Linux 上也可以使用:
timeout 10 ssh ...
避免“一台服务器拖死整批任务”。
九、最好通过 SSH config 固定管理密钥
如果密钥文件名不是默认的:
id_ed25519
而是:
id_ed25519_cluster
那么每次都写:
ssh -i ~/.ssh/id_ed25519_cluster root@100.64.0.15
会很麻烦。
更合适的是配置:
~/.ssh/config
例如:
Host 100.64.0.*
User root
IdentityFile ~/.ssh/id_ed25519_cluster
IdentitiesOnly yes
以后直接:
ssh root@100.64.0.15
SSH 会自动选择正确的管理密钥。
如果某一台服务器使用特殊 key,也可以单独覆盖:
Host 100.64.0.55
User root
IdentityFile ~/.ssh/id_ed25519_special
IdentitiesOnly yes
Host 100.64.0.*
User root
IdentityFile ~/.ssh/id_ed25519_cluster
IdentitiesOnly yes
具体规则放前面,通用规则放后面即可。
十、最终推荐的管理架构
一套比较合理的小规模服务器管理体系可以是:
┌── JP-IIJ
│
├── JP-PRO
管理员 ──────────────┤
├── Windows TTX
│
└── Windows ABCD
│
↓
Tailscale 私网
│
↓
所有服务器节点
每个入口:
独立 SSH 私钥
独立公钥身份
独立撤销能力
主控服务器之间:
都维护 hosts.txt
都保留 /opt/cluster
都能运行批量脚本
最终目标不是:
“有一台机器可以登录所有服务器。”
而应该是:
“任何一个主要管理入口失效,都不会让我失去整个集群的管理能力。”
这才是主备主控真正解决的问题。
总结
多服务器集中运维最值得坚持的两个原则其实很简单。
第一:
主控本身也必须做冗余。
不要让负责管理全部服务器的机器,反而成为最危险的单点故障。
第二:
免密 SSH 应该按设备独立发放密钥,而不是到处复制同一把私钥。
配合 authorized_keys、known_hosts、IdentityFile、IdentitiesOnly、连接超时以及真实登录验证,就可以构建一套既方便,又比较容易维护和撤权的服务器管理体系。