注: 这是一个没什么鸟用的功能. 不过也算是一种拓展.
通常在那些 "一键化部署" 的 shell 脚本中, 可能需要使用 ssh 执行远程命令来实现一些简单的自动化, 这些远程命令可能需要执行一段时间才能结束(如 yum 命令). 例如, 远程 ssh 配置 yum 源, 远程 ssh 安装软件包.
为了让脚本实现 "并行" 执行, 这个远程 ssh 命令往往还会加上 "-f" 选项使其进入后台执行. 此时, 如果后续的远程任务正好要依赖于这个命令已经执行完成, 那么我们要判断前面的任务是否执行完成. 例如, 在配置软件的时候, 必须先判断软件是否安装结束.
判断的方式挺简单, 只需判断这个 ssh 进程是否存在就可以了. 例如:
- [root@node1 ~]# ssh 192.168.100.101 -f 'yum makecache'
- [root@node1 ~]# killall -s 0 ssh
- [root@node1 ~]# echo $?
这样的方法没错, 也能应付绝大多数情况, 但如果有多个远程后台命令的 ssh 进程, 就无法知道具体是哪个 ssh 进程.
于是, 可以采用另一种方法, 将执行远程命令的 ssh 进程放进后台, 再用 $! 来获取最近的后台 ssh 进程. 例如:
- [root@node1 ~]# ssh 192.168.100.101 -f 'yum makecache' & echo $!
- [1] 76115
- 76115
但这是错误的方法, 如果你现在去查看 ssh 进程, 你会发现进程号不是 76115.
- [root@node1 ~]# pstree -p | grep 'ssh('
- |-ssh(76116)
因为 ssh 在执行远程后台命令 (加上 "-f" 选项) 的时候, 它自身会在建立 ssh 连接后再 fork 一个后台 ssh 进程用来执行远程命令.
也就是说, 当 ssh 执行远程后台命令的时候, 会有两个 ssh 进程:
第一个 ssh 进程是初始 ssh 进程, 用于建立连接, 发送要执行的命令, fork 新的 ssh 进程等, 当这些任务结束后, 这个 ssh 进程会消逝;
第二个 ssh 进程是第一个 ssh 进程 fork 出来的新进程(调试的结果将显示 "debug1: forking to background"), 用来执行远程命令. 它是后台进程, 挂靠在 init/systemd 进程下. 当远程命令执行结束时, 这个后台 ssh 进程会消逝.
注意, 只有使用了 "-f" 选项, 第一个 ssh 进程才会 fork 新的后台 ssh 进程, 因为前台的任务 (没有使用 "-f") 可以直接在第一个 ssh 进程上执行.
第二个后台 ssh 进程无法用 $! 捕捉,$! 捕捉到的只是 & 的后台, 而对于 ssh ... & 中的 "&" 来说, 它是将 ssh 连接进程 (即第一个 ssh 进程) 置于后台, 而不是将 fork 出来的 ssh 后台进程再放入后台. 所以上面的 "echo $!" 的结果 76115 比后台 ssh 进程号 76116 要小.
那么有什么好方法可以判断多个远程 ssh 进程中的每一个? 没有好方法(我个人还没有想到), 只能通过比较愚笨的方式来实现判断: 将每个后台 ssh 进程的 pid 号保存起来(存放到每个变量中, 或数组中).
例如, 有两个执行远程命令的 ssh 进程:
- ssh 192.168.100.101 -f 'sleep 50'
- ssh_pid1=`ps x | awk '/ssh.*slee[p]/{print $1}' | tail -1`
- ssh 192.168.100.101 -f 'sleep 60'
- ssh_pid2=`ps x | awk '/ssh.*slee[p]/{print $1}' | tail -1`
- # ssh_pid1 finished?
- kill -0 $ssh_pid1
- echo $?
- # ssh_pid2 finished?
- kill -0 $ssh_pid2
- echo $?
最后补上 ssh 连接或执行远程命令时, 内部过程的详细信息. 这些信息使用 ssh -vvv 即可获取, 此处给出的是筛选后的一小部分.
当 ssh 建立连接或执行前台远程命令 (没有使用 "-f" 选项) 时:
OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013
- debug1: Reading configuration data /etc/ssh/ssh_config
- debug1: /etc/ssh/ssh_config line 56: Applying options for *
- debug2: ssh_connect: needpriv 0
- debug1: Connecting to 192.168.100.101 [192.168.100.101] port 22.
- debug1: Connection established # tcp 连接建立
- .....................
- debug1: Authentication succeeded (publickey).
- Authenticated to 192.168.100.101 ([192.168.100.101]:22). # 用户认证成功
- debug1: channel 0: new [client-session]
- debug3: ssh_session2_open: channel_new: 0
- debug2: channel 0: send open # ssh 连接建立
- debug1: Requesting no-more-sessions@openssh.com
- debug1: Entering interactive session. # ssh 连接进程进入交互式模式
[1]+ Stopped ssh -vvvv 192.168.100.101
当执行远程后台任务时(加上 "-f" 选项):
- [root@node1 ~]# ssh -vvv 192.168.100.101 -f 'sleep 50' & echo $!
- [1] 65570
- 65570 # echo $! 得到的上一个后台进程位 65570
- [root@node1 ~]# OpenSSH_6.6.1, OpenSSL 1.0.1e-fips 11 Feb 2013
- debug1: Reading configuration data /etc/ssh/ssh_config
- debug1: /etc/ssh/ssh_config line 56: Applying options for *
- debug2: ssh_connect: needpriv 0
- debug1: Connecting to 192.168.100.101 [192.168.100.101] port 22.
- debug1: Connection established. # tcp 连接建立
- ....................................
- debug1: Authentication succeeded (publickey). # 用户认证成功
- Authenticated to 192.168.100.101 ([192.168.100.101]:22).
- debug2: fd 4 setting O_NONBLOCK
- debug1: channel 0: new [client-session]
- debug3: ssh_session2_open: channel_new: 0
- debug2: channel 0: send open # ssh 连接建立
- debug1: Requesting no-more-sessions@openssh.com
- debug1: forking to background # 注意此处: fork 一个新 ssh 进程到后台
- debug1: Entering interactive session. # ssh 连接进程进入交互式模式
- debug2: callback start
- ......................................
回到 Linux 系列文章大纲: http://www.cnblogs.com/f-ck-need-u/p/7048359.html
回到网站架构系列文章大纲: http://www.cnblogs.com/f-ck-need-u/p/7576137.html
回到数据库系列文章大纲: http://www.cnblogs.com/f-ck-need-u/p/7586194.html
来源: https://www.cnblogs.com/f-ck-need-u/p/8785561.html