shell - ssh 在 golang 中使用交互式 shell 执行 nsenter 作为远程命令来调试 docker 容器

标签 shell ssh go docker

我正在尝试在 coreos 上自动调试 docker 容器。我想要一个通过 ssh 连接到主机并执行 nsenter 的脚本。从我的 OSX 盒子直接跳转到一个容器中,而不需要手动做很多事情,这将是非常方便的。我知道以这种方式进入容器可能很麻烦,但如果事情变得艰难,我想使用这样的工具。所以这就是我到目前为止在 golang 中所拥有的。

我能够创建一个交互式 shell。我遇到的问题是,使用 ctrl+R 反向搜索 bash 历史记录会中断 session 。该代码已在下方注释,因此未执行。

但是,我也可以执行单个命令,此处为 nsenter,但我收到错误 stdin: is not a tty 并且没有其他任何反应。我很想知道为什么我程序中的 stdin 不是 tty 以及如何实现这一点。

谢谢

package main

import (
  "code.google.com/p/go.crypto/ssh"
  "io/ioutil"
  "log"
  "os"
)

func privateKey() ssh.Signer {
  buf, err := ioutil.ReadFile("./id_rsa")
  if err != nil {
    panic(err)
  }
  key, err := ssh.ParsePrivateKey(buf)
  if err != nil {
    panic(err)
  }

  return key
}

func main() {
  privateKey := privateKey()

  // Create client config
  config := &ssh.ClientConfig{
    User: "core",
    Auth: []ssh.AuthMethod{
      ssh.PublicKeys(privateKey),
    },
  }

  // Connect to ssh server
  conn, err := ssh.Dial("tcp", "myhost.com:22", config)
  if err != nil {
    log.Fatalf("unable to connect: %s", err)
  }
  defer conn.Close()

  // Create a session
  session, err := conn.NewSession()
  if err != nil {
    log.Fatalf("unable to create session: %s", err)
  }

  session.Stdout = os.Stdout
  session.Stderr = os.Stderr
  session.Stdin = os.Stdin // How can session.Stdin be a tty?

  //////////////////////////////////////////////////////////////////////
  // Stuff for interactive shell
  // Set up terminal modes
  //modes := ssh.TerminalModes{
  //  ssh.ECHO:          1,     // enable echoing
  //  ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  //  ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  //}
  // Request pseudo terminal
  //if err := session.RequestPty("xterm-256color", 80, 40, modes); err != nil {
  //  log.Fatalf("request for pseudo terminal failed: %s", err)
  //}
  // Start remote shell
  //if err := session.Shell(); err != nil {
  //  log.Fatalf("failed to start shell: %s", err)
  //}
  //////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////
  // Stuff for executing remote command
  // 2202 in my example is actually the pid of a running container
  if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
    panic("Failed to run: " + err.Error())
  }
  //////////////////////////////////////////////////////////////////////

  session.Wait()
}

最佳答案

太棒了,我让它工作了,但仍然有一种我无法理解的魔力。但是,我如下更改了代码。导致正确的 pty 行为的基本变化是包 "code.google.com/p/go.crypto/ssh/terminal" 的使用。使用它的 MakeRaw(fd) 似乎会导致启用正确的 pty 行为的副作用。还要感谢 fleet 项目,我在其中找到了工作示例 https://github.com/coreos/fleet/blob/master/ssh/ssh.go .

// The following two lines makes the terminal work properly because of
// side-effects I don't understand.
fd := int(os.Stdin.Fd())
oldState, err := terminal.MakeRaw(fd)
if err != nil {
  panic(err)
}

session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdin = os.Stdin

termWidth, termHeight, err := terminal.GetSize(fd)
if err != nil {
  panic(err)
}

// Set up terminal modes
modes := ssh.TerminalModes{
  ssh.ECHO:          1,     // enable echoing
  ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}

// Request pseudo terminal
if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
  log.Fatalf("request for pseudo terminal failed: %s", err)
}

if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
  // if the session terminated normally, err should be ExitError; in that
  // case, return nil error and actual exit status of command
  if exitErr, ok := err.(*ssh.ExitError); ok {
    fmt.Printf("exit code: %#v\n", exitErr.ExitStatus())
  } else {
    panic("Failed to run: " + err.Error())
  }
}

session.Close()
terminal.Restore(fd, oldState)

关于shell - ssh 在 golang 中使用交互式 shell 执行 nsenter 作为远程命令来调试 docker 容器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26315572/

相关文章:

c# - 使用 Bouncy CaSTLe 将 RSA 公钥转换为 RFC 4716 公钥

postgresql - 自动化数据库连接

linux - 将 awk 脚本合并到 ksh 脚本中

bash - 检查传递的参数是 Bash 中的文件还是目录

svn - 如何使用类似于 svnserve -r 的 svn+ssh 缩短存储库 URL?

nginx - 如何将 nginx 与 Go 一起用于子域?

intellij-idea - 在 intellij 中调试 golang - 找不到相关文件

nginx - 使用 Nginx 部署多个 Go 应用程序

c++ - 使用 grep 命令一个 .hpp 文件获得匹配的二进制文件

Bash 脚本 : Tar not working properly