英文:
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 (
"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()
}
答案1
得分: 3
太棒了,我搞定了,但还有一个我无法理解的魔法。不过,我按照以下方式更改了我的代码。导致正确的pty
行为的基本更改是使用了"code.google.com/p/go.crypto/ssh/terminal"
包。使用它的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 "code.google.com/p/go.crypto/ssh/terminal"
. 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'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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论