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

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

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,以及如何解决这个问题。

谢谢。

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()

  // 创建客户端配置
  config := &ssh.ClientConfig{
    User: "core",
    Auth: []ssh.AuthMethod{
      ssh.PublicKeys(privateKey),
    },
  }

  // 连接到SSH服务器
  conn, err := ssh.Dial("tcp", "myhost.com:22", config)
  if err != nil {
    log.Fatalf("无法连接:%s", err)
  }
  defer conn.Close()

  // 创建会话
  session, err := conn.NewSession()
  if err != nil {
    log.Fatalf("无法创建会话:%s", err)
  }

  session.Stdout = os.Stdout
  session.Stderr = os.Stderr
  session.Stdin = os.Stdin // 如何使session.Stdin成为tty?

  //////////////////////////////////////////////////////////////////////
  // 交互式shell的内容
  // 设置终端模式
  //modes := ssh.TerminalModes{
  //  ssh.ECHO:          1,     // 启用回显
  //  ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
  //  ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 14.4kbaud
  //}
  // 请求伪终端
  //if err := session.RequestPty("xterm-256color", 80, 40, modes); err != nil {
  //  log.Fatalf("请求伪终端失败:%s", err)
  //}
  // 启动远程shell
  //if err := session.Shell(); err != nil {
  //  log.Fatalf("启动shell失败:%s", err)
  //}
  //////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////
  // 执行远程命令的内容
  // 在我的示例中,2202实际上是正在运行的容器的PID
  if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
    panic("运行失败:" + err.Error())
  }
  //////////////////////////////////////////////////////////////////////

  session.Wait()
}
英文:

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 -->

package main
import (
&quot;code.google.com/p/go.crypto/ssh&quot;
&quot;io/ioutil&quot;
&quot;log&quot;
&quot;os&quot;
)
func privateKey() ssh.Signer {
buf, err := ioutil.ReadFile(&quot;./id_rsa&quot;)
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 := &amp;ssh.ClientConfig{
User: &quot;core&quot;,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(privateKey),
},
}
// Connect to ssh server
conn, err := ssh.Dial(&quot;tcp&quot;, &quot;myhost.com:22&quot;, config)
if err != nil {
log.Fatalf(&quot;unable to connect: %s&quot;, err)
}
defer conn.Close()
// Create a session
session, err := conn.NewSession()
if err != nil {
log.Fatalf(&quot;unable to create session: %s&quot;, 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(&quot;xterm-256color&quot;, 80, 40, modes); err != nil {
//  log.Fatalf(&quot;request for pseudo terminal failed: %s&quot;, err)
//}
// Start remote shell
//if err := session.Shell(); err != nil {
//  log.Fatalf(&quot;failed to start shell: %s&quot;, err)
//}
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Stuff for executing remote command
// 2202 in my example is actually the pid of a running container
if err := session.Run(&quot;sudo nsenter --target 2202 --mount --uts --ipc --net --pid&quot;); err != nil {
panic(&quot;Failed to run: &quot; + err.Error())
}
//////////////////////////////////////////////////////////////////////
session.Wait()
}

答案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。

// 下面两行代码使终端正常工作,因为有一些我不理解的副作用。
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)
}

// 设置终端模式
modes := ssh.TerminalModes{
  ssh.ECHO:          1,     // 启用回显
  ssh.TTY_OP_ISPEED: 14400, // 输入速度 = 14.4kbaud
  ssh.TTY_OP_OSPEED: 14400, // 输出速度 = 14.4kbaud
}

// 请求伪终端
if err := session.RequestPty("xterm-256color", termHeight, termWidth, modes); err != nil {
  log.Fatalf("请求伪终端失败:%s", err)
}

if err := session.Run("sudo nsenter --target 2202 --mount --uts --ipc --net --pid"); err != nil {
  // 如果会话正常终止,err应该是ExitError;在这种情况下,返回nil错误和实际命令的退出状态
  if exitErr, ok := err.(*ssh.ExitError); ok {
    fmt.Printf("退出代码:%#v\n", exitErr.ExitStatus())
  } else {
    panic("运行失败:" + err.Error())
  }
}

session.Close()
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 -->

// The following two lines makes the terminal work properly because of
// side-effects I don&#39;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(&quot;xterm-256color&quot;, termHeight, termWidth, modes); err != nil {
log.Fatalf(&quot;request for pseudo terminal failed: %s&quot;, err)
}
if err := session.Run(&quot;sudo nsenter --target 2202 --mount --uts --ipc --net --pid&quot;); 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(&quot;exit code: %#v\n&quot;, exitErr.ExitStatus())
} else {
panic(&quot;Failed to run: &quot; + err.Error())
}
}
session.Close()
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:

确定