0 缘起
最近博士正在把他做的东西分离成服务端/客户端,开放了详细的SDK以方便其他开发人员进行后续的开发,然后把一切都塞进Docker镜像里,推送到自己搭建的私有registry上,部署起来非常方便。
然而有一个服务端的Docker容器非常不稳定,时不时的短线需要重启,加上为了方便使用,要在WebUI上添加一个一键重启的按钮,就提出了这样一个需求,具体分析如下——
Web服务部署在客户端
一键重启服务端容器
操作尽可能简化
首先排除在宿主机装Portainer来管理Docker,因为操作很复杂,而且开放了太多的操作给用户。其次通过Node写的Web服务来执行命令这个我做过,用Express响应请求+node:child_process创建子进程调用其他程序的方式可以实现网页端调用服务器后台程序,就算解决了一半。最后需要实现的就是如何在虚拟机中访问物理机然后重启某个特定的虚拟机了。
1 grep、管道符与awk
当我们处于宿主机时,想要重启某个容器的方法是:
先看看有哪些运行中的容器
$ sudo docker ps -a
然后按照提示输入root
密码,终端会打印出所有运行中的容器。然后
$ sudo dcoker restart <容器ID>
重启它。
这个过程用户操作起来比较直观,但是考虑到有交互的部分,就需要依赖脚本来完成自动化了。
那么第一步依旧还是
$ sudo docker ps -a
找到所有运行中的Docker容器,然后通过管道符 |
和过滤命令 grep
找到所需要重启的容器的所有信息,最后再一次通过管道符和 awk
来读取所输出的容器信息内容的第一部分——即容器ID,最后我们组装一下:
$ sudo docker ps -a | grep ocr_server | awk '{ print $1}'
把它运行一下看看是不是能输出想要的容器ID: f6b6739da119
。成功的第一步~
那 $ sudo dcoker restart <容器ID>
里面 <容器ID>
这部分搞定了,咱们继续组装:
$ sudo docker restart `sudo docker ps -a |\
grep ocr_server | awk '{ print $1}'`
运行一下,确实能成功重启这个容器,但是还有一点不太方便的是每次都需要输入一次 root
密码,能不能把密码输入进去呢?
能~还是需要 echo
和管道符 |
:
$ echo <PASSWORD> |\
sudo -S docker restart `sudo docker ps -a |\
grep ocr_server | awk '{ print $1}'`
但是每一次敲这么一长串东西非常费键盘,还是把它写进shell脚本自动运行比较方便。
#test.sh
#!/bin/bash
echo <PASSWORD> |\
sudo -S docker restart `sudo docker ps -a |\
grep ocr_server | awk '{ print $1}'`
echo done!
运行一下看到终端打印重 done!
这第一步到这里就算完成了。
最后把这个文件放在物理机的 /home
目录下面,或者随便哪个你记得住的目录就行。
2 使用expect+ssh登录物理机
像 sudo
这种交互式的命令可以通过加上 echo <PASSWORD> | sudo -S
来非交互式地传入密码,但是 ssh
并不支持这样的方式,如果想要传入密码,需要使用 sshpass
, SSH
使用直接 TTY
访问来确保密码确实是由交互式键盘用户发出的。 sshpass
在专用的 TTY
中运行 SSH
,让 SSH
认为它是从交互式用户那里获取密码的,从而以非交互式的方式使用交互式的 ssh
指令。
或者就是使用expect
。expect
是由Don Libes基于Tcl
(Tool Command Language)语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh
登录,ftp
登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率.
这一次我选择 expect
,这样除了 ssh
外,还可以相应的进行操作,比如运行某个脚本。
使用 expect
+ ssh
时需要用到的指令有 spawn
、 expect
和 send
,首先需要使用 spawn
来开启一个子线程,并调用 ssh
:
spawn ssh [-p <port>] <user>@<ip>
如果是第一次连接,则会跳出警告,是否记录密钥等,会出现 (yes/no/[fingerprint])?
字样需要交互,如果不是则需要输入密码,以及换行键,那么我们可以将相应的操作写入 expect
:
expect {
"yes/no" { send "yes\r";exp_continue }
"password" { send "<PASSWORD>\r" }
}
登录成功后需要执行上一步中写好的脚本,则需要等终端等待交互时输入相应的指令:
expect "<user>*" { send "sh test.sh\r" }
然后发现需要输密码获得权限,那就继续加,其中空格前需要加转义符 \
:
expect "password\ for\ <user>" { send "<PASSWORD>\r" }
最后退出登录:
expect "<user>*" { send "exit\r" }
然后组装下,把 <ip>
、 <user>
、 <PASSWORD>
或者可能有的 <port>
:
#gogo.sh
#!/usr/bin/expect
ip=<ip> #填写需要远程的ip地址
user=<user> #填写需要远程的用户名
expect <<EOF
set timeout -1
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password" { send "<PASSWORD>\r" }
}
expect "<user>*" { send "sh test.sh\r" }
expect "password\ for\ <user>" { send "<PASSWORD>\r" }
expect "<user>*" { send "exit\r" }
EOF
运行下看看效果。
好了本周的学习成果到此结束。




