使用Golang在远程命令中以交互式shell方式执行nsenter来调试Docker容器。

huangapple go评论137阅读模式
英文:

ssh executing nsenter as remote command with interactive shell in golang to debug docker container

问题

我正在尝试自动化调试CoreOS上的Docker容器。我想要一个脚本,通过SSH连接到主机并执行nsenter命令。这样可以方便地从我的OSX机器直接跳转到容器中,而无需手动执行很多步骤。我知道以这种方式进入容器可能会有问题,但如果情况变得困难,我想使用这样的工具。以下是我目前在Golang中的代码。

我能够创建一个交互式shell。在这里,我遇到的问题是使用ctrl+R进行反向搜索bash历史记录会中断会话。下面的代码已被注释掉,因此不会执行。

然而,我也能够执行单个命令,比如nsenter,但是我收到错误消息stdin: is not a tty,然后什么都不会发生。我想知道为什么我的程序中的stdin不是一个tty,以及如何解决这个问题。

谢谢。

  1. package main
  2. import (
  3. "code.google.com/p/go.crypto/ssh"
  4. "io/ioutil"
  5. "log"
  6. "os"
  7. )
  8. func privateKey() ssh.Signer {
  9. buf, err := ioutil.ReadFile("./id_rsa")
  10. if err != nil {
  11. panic(err)
  12. }
  13. key, err := ssh.ParsePrivateKey(buf)
  14. if err != nil {
  15. panic(err)
  16. }
  17. return key
  18. }
  19. func main() {
  20. privateKey := privateKey()
  21. // 创建客户端配置
  22. config := &ssh.ClientConfig{
  23. User: "core",
  24. Auth: []ssh.AuthMethod{
  25. ssh.PublicKeys(privateKey),
  26. },
  27. }
  28. // 连接到SSH服务器
  29. conn, err := ssh.Dial("tcp", "myhost.com:22", config)
  30. if err != nil {
  31. log.Fatalf("无法连接:%s", err)
  32. }
  33. defer conn.Close()
  34. // 创建会话
  35. session, err := conn.NewSession()
  36. if err != nil {
  37. log.Fatalf("无法创建会话:%s", err)
  38. }
  39. session.Stdout = os.Stdout
  40. session.Stderr = os.Stderr
  41. session.Stdin = os.Stdin // 如何使session.Stdin成为tty?
  42. //////////////////////////////////////////////////////////////////////
  43. // 交互式shell的内容
  44. // 设置终端模式
  45. //modes := ssh.TerminalModes{
  46. // ssh.ECHO: 1, // 启用回显
  47. // ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
  48. // ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 14.4kbaud
  49. //}
  50. // 请求伪终端
  51. //if err := session.RequestPty("xterm-256color", 80, 40, modes); err != nil {
  52. // log.Fatalf("请求伪终端失败:%s", err)
  53. //}
  54. // 启动远程shell
  55. //if err := session.Shell(); err != nil {
  56. // log.Fatalf("启动shell失败:%s", err)
  57. //}
  58. //////////////////////////////////////////////////////////////////////
  59. //////////////////////////////////////////////////////////////////////
  60. // 执行远程命令的内容
  61. // 在我的示例中,2202实际上是正在运行的容器的PID
  62. if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
  63. panic("运行失败:" + err.Error())
  64. }
  65. //////////////////////////////////////////////////////////////////////
  66. session.Wait()
  67. }
英文:

I am trying to automate debugging of docker containers on coreos. I want to have a script that connects to a host via ssh and exectues nsenter. That would be very convenient to jump directly into a container from my OSX box without doing a lot of stuff manually. I know that entering containers that way can be nasty, but if things are getting tough I would like to use such a tool. So here is what I have so far in golang.

I am able to create a interactive shell. Here I have the problem that things like reverse searching bash history using ctrl+R breaks the session. That code is commented below, thus not executed.

However, I am also able to execute a single command, here nsenter, but I receive the error stdin: is not a tty and nothing more happens. I am interested to know why stdin in my programm is not a tty and how I can achieve this.

Thanks

<!-- language: lang-go -->

  1. package main
  2. import (
  3. &quot;code.google.com/p/go.crypto/ssh&quot;
  4. &quot;io/ioutil&quot;
  5. &quot;log&quot;
  6. &quot;os&quot;
  7. )
  8. func privateKey() ssh.Signer {
  9. buf, err := ioutil.ReadFile(&quot;./id_rsa&quot;)
  10. if err != nil {
  11. panic(err)
  12. }
  13. key, err := ssh.ParsePrivateKey(buf)
  14. if err != nil {
  15. panic(err)
  16. }
  17. return key
  18. }
  19. func main() {
  20. privateKey := privateKey()
  21. // Create client config
  22. config := &amp;ssh.ClientConfig{
  23. User: &quot;core&quot;,
  24. Auth: []ssh.AuthMethod{
  25. ssh.PublicKeys(privateKey),
  26. },
  27. }
  28. // Connect to ssh server
  29. conn, err := ssh.Dial(&quot;tcp&quot;, &quot;myhost.com:22&quot;, config)
  30. if err != nil {
  31. log.Fatalf(&quot;unable to connect: %s&quot;, err)
  32. }
  33. defer conn.Close()
  34. // Create a session
  35. session, err := conn.NewSession()
  36. if err != nil {
  37. log.Fatalf(&quot;unable to create session: %s&quot;, err)
  38. }
  39. session.Stdout = os.Stdout
  40. session.Stderr = os.Stderr
  41. session.Stdin = os.Stdin // How can session.Stdin be a tty?
  42. //////////////////////////////////////////////////////////////////////
  43. // Stuff for interactive shell
  44. // Set up terminal modes
  45. //modes := ssh.TerminalModes{
  46. // ssh.ECHO: 1, // enable echoing
  47. // ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  48. // ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  49. //}
  50. // Request pseudo terminal
  51. //if err := session.RequestPty(&quot;xterm-256color&quot;, 80, 40, modes); err != nil {
  52. // log.Fatalf(&quot;request for pseudo terminal failed: %s&quot;, err)
  53. //}
  54. // Start remote shell
  55. //if err := session.Shell(); err != nil {
  56. // log.Fatalf(&quot;failed to start shell: %s&quot;, err)
  57. //}
  58. //////////////////////////////////////////////////////////////////////
  59. //////////////////////////////////////////////////////////////////////
  60. // Stuff for executing remote command
  61. // 2202 in my example is actually the pid of a running container
  62. if err := session.Run(&quot;sudo nsenter --target 2202 --mount --uts --ipc --net --pid&quot;); err != nil {
  63. panic(&quot;Failed to run: &quot; + err.Error())
  64. }
  65. //////////////////////////////////////////////////////////////////////
  66. session.Wait()
  67. }

答案1

得分: 3

太棒了,我搞定了,但还有一个我无法理解的魔法。不过,我按照以下方式更改了我的代码。导致正确的pty行为的基本更改是使用了&quot;code.google.com/p/go.crypto/ssh/terminal&quot;包。使用它的MakeRaw(fd)似乎会产生副作用,从而实现了正确的pty行为。还要感谢我在fleet项目中找到的工作示例https://github.com/coreos/fleet/blob/master/ssh/ssh.go。

  1. // 下面两行代码使终端正常工作,因为有一些我不理解的副作用。
  2. fd := int(os.Stdin.Fd())
  3. oldState, err := terminal.MakeRaw(fd)
  4. if err != nil {
  5. panic(err)
  6. }
  7. session.Stdout = os.Stdout
  8. session.Stderr = os.Stderr
  9. session.Stdin = os.Stdin
  10. termWidth, termHeight, err := terminal.GetSize(fd)
  11. if err != nil {
  12. panic(err)
  13. }
  14. // 设置终端模式
  15. modes := ssh.TerminalModes{
  16. ssh.ECHO: 1, // 启用回显
  17. ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
  18. ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 14.4kbaud
  19. }
  20. // 请求伪终端
  21. if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
  22. log.Fatalf("请求伪终端失败:%s", err)
  23. }
  24. if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
  25. // 如果会话正常终止,err应该是ExitError;在这种情况下,返回nil错误和实际命令的退出状态
  26. if exitErr, ok := err.(*ssh.ExitError); ok {
  27. fmt.Printf("退出代码:%#v\n", exitErr.ExitStatus())
  28. } else {
  29. panic("运行失败:" + err.Error())
  30. }
  31. }
  32. session.Close()
  33. terminal.Restore(fd, oldState)
英文:

Super cool, I got it working, but there is still a magic I cannot comprehend. However, I changed my code as followed. The basic change leading to the correct pty behaviour, was the usage of the package &quot;code.google.com/p/go.crypto/ssh/terminal&quot;. Using its MakeRaw(fd) seems to lead to side effects that enable the correct pty behaviour. Also thanks to the fleet project where I found the working example https://github.com/coreos/fleet/blob/master/ssh/ssh.go.

<!-- language: lang-go -->

  1. // The following two lines makes the terminal work properly because of
  2. // side-effects I don&#39;t understand.
  3. fd := int(os.Stdin.Fd())
  4. oldState, err := terminal.MakeRaw(fd)
  5. if err != nil {
  6. panic(err)
  7. }
  8. session.Stdout = os.Stdout
  9. session.Stderr = os.Stderr
  10. session.Stdin = os.Stdin
  11. termWidth, termHeight, err := terminal.GetSize(fd)
  12. if err != nil {
  13. panic(err)
  14. }
  15. // Set up terminal modes
  16. modes := ssh.TerminalModes{
  17. ssh.ECHO: 1, // enable echoing
  18. ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
  19. ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
  20. }
  21. // Request pseudo terminal
  22. if err := session.RequestPty(&quot;xterm-256color&quot;, termHeight, termWidth, modes); err != nil {
  23. log.Fatalf(&quot;request for pseudo terminal failed: %s&quot;, err)
  24. }
  25. if err := session.Run(&quot;sudo nsenter --target 2202 --mount --uts --ipc --net --pid&quot;); err != nil {
  26. // if the session terminated normally, err should be ExitError; in that
  27. // case, return nil error and actual exit status of command
  28. if exitErr, ok := err.(*ssh.ExitError); ok {
  29. fmt.Printf(&quot;exit code: %#v\n&quot;, exitErr.ExitStatus())
  30. } else {
  31. panic(&quot;Failed to run: &quot; + err.Error())
  32. }
  33. }
  34. session.Close()
  35. terminal.Restore(fd, oldState)

huangapple
  • 本文由 发表于 2014年10月11日 22:14:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/26315572.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定