SSH无密码登录远程主机执行特定命令的注意事项
最近的一个项目要结合使用rabbitmq、keepalived、supervisor。其中的一个场景为某个keepalived实例被提升为MASTER后需要到部署了rabbitmq client的远程主机上kill掉这些client进程。
一般的思路为配置keepalived所在主机与部署了client的主机之间的无密码ssh登录,然后通过ssh执行远程命令,先获取各client进程的进程号,然后逐个kill掉这些进程。
大体思路很简单,但过程中却碰到了不少问题。配置无密码ssh至远程主机的步骤比较简单。在此不多说。配置好后可以直接在脚本中使用:
ssh -p PORT IP "command"
或者
ssh -p PORT IP 'command'
来在远程主机执行命令了。
先说说总设计问题。
刚开始是直线式的思维:先通过ssh远程获取client进程号,对于每个进程号分别ssh至远程执行kill操作。因为有多台keepalived实例分别运行于不同主机,这就需要在每个主机上维护一份需要处理的client列表,且需要多次通过ssh到远程部署client的主机执行获取进程号、杀进程的命名。这对于维护和性能来讲都是比较恶心的。
换一种思维方式,既然查进程后和kill进程号的操作都是在远程主机中完成的。为何不只在该远程主机维护一份需处理的client列表且把获取进程号和杀进程的这些操作封装成一个脚本放在该远程主机?这样其余的各keepalived实例所在主机只需在ssh命名中调用一次远程主机的脚本便可。省去了很多不必要的步骤。
上边是应用设计上的问题,可见在进行一个项目之前考虑清楚最优方案可为后续的实施减少多少的麻烦。遇到一个问题一定要从多个方面考虑。且尽量使用最简单的方式而不是最高大上的最复杂的的方式。
再来说所碰到的一些细节问题。
ssh -p PORT IP "command"
或者
ssh -p PORT IP 'command'
的command中若包含变量的话,变量要用对应的引号引起来才能得以正确解析(实际上是shell中的字符串拼接)。
如:
consumer=worker_for_summary.py
则
ssh -p PORT IP "ps -ef | grep "$consumer" |grep -v grep"
或
ssh -p PORT IP 'ps -ef | grep '$consumer' |grep -v grep'
使用如下命令时不行的
ssh -p PORT IP 'ps -ef | grep $consumer |grep -v grep'
另外在ssh中使用awk也需要注意,因为awk命令中使用单引号表明要执行的动作,所以相应的ssh中包围command的引号要改成双引号且awk中的“$”值为参数要加转移符
如:
consumer=worker_for_summary.py
则
ssh -p PORT IP "ps -ef | grep "$consumer" |grep -v grep | awk '{print \$2}'"
使用其他任何方式是不行的。
最后说说在shell脚本中使用ssh的注意
一般情况下执行ssh -p PORT IP "command" 默认是使用当前用户到远程主机执行命令的。
若将ssh -p PORT IP "command" 封装进了脚本,则会使用执行脚本时使用的用户登录至远程主机执行命令。
我们的应用中将ssh -p PORT IP "command"封装进了脚本,该脚本会在keepalived实例进入MASTER状态后由keepalived调用,而keepalived是由root用户启动的所以实际上会以root用户至远程主机执行命令。而我们配置的无密码ssh至远程主机用的是非root用户的工作(一般情况下是当前用户的公钥),因此远程命令不能成功执行,提示需要密码,即使是在ssh -p PORT IP "command"中加入用户信息变为ssh -p PORT norootuser@IP "command"也不行。因此通过生成root用户的公钥并配置无密码ssh到远程主机来规避了该问题。
在试密码的时候,记错了密码导致账户被锁定不能登录,可以通过faillog命令查看失败记录并设置登录失败限制。
如:
查看用户登录失败情况
sudo faillog -u op1
重置用户
sudo faillog -u op1 -r