golang并发ssh执行远程命令
2017-10-20 18:35
567 查看
需求
在kubernetes/docker容器化应用中,业务应用由大量容器组成,由于生产环境中出于安全考虑,一般不会允许用户直接登入集群机器,然后登入机器上的容器。况且数量之多,也没有效率。因此设计了一个命令行工具,以权限受控的账号ssh远程连接到容器所在宿主机,然后docker exec到容器内执行命令。而且该过程必须能够批量化的进行。实现
下面是并发执行远程ssh命令的核心实现jobs := make(chan *model.Command, len(instanceList)) results := make(chan *model.CommandResult, len(instanceList)) // 开启多个goroutine去远程登入容器,执行命令 for e := 1; e <= parallelism; e++ { go service.Executor(e, jobs, results) } for _, ins := range instanceList { jobs <- &model.Command{ Host: ins.Host, ContainerId: ins.ContainerId, Command: cmd, } } close(jobs) failCount := 0 size := len(instanceList) for j := 1; j <= size; j++ { rst := <-results success := "Success" if rst.CmdError != nil { success = "Fail" failCount++ } fmt.Printf("[%d/%d] - [%s]\t", j, size, success) fmt.Printf("Host = %s, ContainerId = %s, rst.Host, rst.ContainerId) fmt.Println(rst.Output) if rst.CmdError != nil { if ee, ok := rst.CmdError.(*exec.ExitError); ok { waitStatus := ee.Sys().(syscall.WaitStatus) fmt.Printf("%d\n", waitStatus.ExitStatus()) } fmt.Printf("%s\n", rst.CmdError.Error()) } } //结果汇总 fmt.Printf("[INFO] Total = %d, Success = %d, Fail = %d", size, size-failCount, failCount)
下面是service.Executor的关键代码
func Executor(jobs <-chan *model.Command, jobResults chan<- *model.CommandResult) { for job := range jobs { out, err := ExecuteCommandInContainer(job.Host, job.ContainerId, job.Command) jobResults <- &model.CommandResult{ CmdError: err, ContainerId: job.ContainerId, Host: job.Host, Output: out, } } } // 登录容器,执行一个具体的命令 func ExecuteCommandInContainer(host string, containerId string, command string) (out string, err error) { err = AddRsafile() if err != nil { return } homeDir := os.Getenv("HOME") dockerHost := fmt.Sprintf(`rd@%s`, host) containerLoginCmd := fmt.Sprintf("sudo docker exec -it -u rd %s bash -c \"%s\"", containerId, command) cmd := exec.Command("ssh", "-i", homeDir+"/.ssh/.id_rsa", "-oUserKnownHostsFile=/dev/null", "-oStrictHostKeyChecking=no", "-t", "-t", dockerHost, containerLoginCmd) cmd.Stdin = os.Stdin b, err := cmd.Output() if err != nil { return } out = string(b) return }
问题
上述代码,编译成二进制可执行文件后,在shell终端里执行,当并发度大于1时,终端会被打乱,同时执行完了之后,终端已经假死,必须reset才能继续使用。但是,放到crontab里执行时,并无该问题,这是为什么?追踪
初步怀疑是ssh并发写终端stdout问题,但是代码中明明是串行写的。于是去查ssh相关参数的用法。注意到,上面ssh命令,带有2个-t参数,这是做什么的?参见ssh的帮助
-T Disable pseudo-tty allocation. -t Force pseudo-tty allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g., when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty.
两个-t是强制ssh分配tty,尝试去掉一个,我们发现,在命令行里执行并没有什么问题,但是在crontab里就有问题了,会提示
Pseudo-terminal will not be allocated because stdin is not a terminal.
首先crontab是非登录式shell的环境,分配伪终端时,无法将stdin分配为一个terminal,也就是上面提示的含义。使用2个-t,强制分配。完美解决了crontab里无法正确执行的问题。但是并发执行是什么问题呢?
受到这个启发,由初期怀疑是并发写到控制台导致的,转入怀疑是多个ssh的线程公用了同一个stdin导致的,因为上述代码中,设定了cmd.Stdin = os.Stdin,于是将cmd.Stdin = nil, 本来这个工具也无需输入,调整之后,并发执行问题完美解决。
参考
有关如何在golang中执行shell命令,可参考这篇文章 Shelled-out Commands In Golang相关文章推荐
- SSH 远程执行命令实例
- python使用paramiko模块实现ssh远程命令执行,与sftp文件下载功能
- ssh 远程执行命令简介
- SSH登录到远程linux机器并执行命令
- Golang 实现 SSH 执行远端命令
- 使用expect工具ssh登录远程服务器并执行命令操作
- ssh 远程执行命令简介
- python 使用ssh远程登录并执行命令返回结果
- 自动scp然后ssh登录,执行远程命令
- ssh 远程命令执行时切换用户
- Python paramiko模块 实现 ssh远程执行命令 上传下载文件 堡垒机模式下的远程命令执行
- ssh远程执行命令
- shell技巧--ssh远程执行包含nohup命令的脚本
- erlang 利用 ssh 远程执行 shell 命令
- ssh远程执行命令方法和Shell脚本实例
- SSH远程执行Linux Shell命令
- Linux下非交互式远程执行命令脚本(比ssh更好的方式)
- Linux下使用SSH非交互式远程执行命令脚本
- 解决ssh远程执行命令无法使用awk的问题
- ssh 远程执行绝对路径命令mysqld_multi 报my_print_defaults不存在