duannaikuang1301 2014-10-11 14:14
浏览 224

ssh使用golang中的交互式shell将nsenter作为远程命令执行以调试Docker容器

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

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条回答 默认 最新

  • dreamy1992 2014-10-12 17:08
    关注

    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.

    // 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
    ", exitErr.ExitStatus())
      } else {
        panic("Failed to run: " + err.Error())
      }
    }
    
    session.Close()
    terminal.Restore(fd, oldState)
    
    评论

报告相同问题?

悬赏问题

  • ¥15 用土力学知识进行土坡稳定性分析与挡土墙设计
  • ¥15 帮我写一个c++工程
  • ¥30 Eclipse官网打不开,官网首页进不去,显示无法访问此页面,求解决方法
  • ¥15 关于smbclient 库的使用
  • ¥15 微信小程序协议怎么写
  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?