Golang Docker SDK编写`docker exec`类似功能时遇到的问题
Golang Docker SDK编写docker exec类似功能时遇到的问题
(抱歉,如果看起来像是重复发帖。我最初将此问题发布在“获取帮助”板块,后来发现了这个子论坛,因此删除了原帖,但不知为何主题标题仍然保留。)
我正在使用官方SDK编写一个Docker客户端;到目前为止它运行良好,但我在自己的docker exec克隆功能上遇到了问题。
假设我运行等效于docker exec -tiuroot $CONTAINERNAME bash的命令,然后继续向分配的tty发送一些命令(如ls、ps等),我会得到重复的输出(参见第二个代码片段)。另一个问题是转义序列:如果我在容器内运行tail -f $SOME_FILE,然后按下CTRL+C,它不会停止tail命令,而是会将我从容器中踢出。
我不知道如何改变这两种行为。
第一个代码片段是我实际使用的方法,附带的图片是一个示例,展示了运行dtools exec时出现的“重复输出”现象。
这两个问题(重复输出和转义字符)让我有些抓狂,我希望能在这里得到一些建议。
哦,还有另一个问题是我在上传图片时刚刚发现的(图片中未显示):TAB补全在这个实现中无法工作;在同一个容器中使用“真正的”docker exec是可以的,所以这是我的实现中stdin/stdout的问题。
关于DockerExecConfig变量:它是types.ExecConfig类型,并在其他地方预先填充。
func ContainerExec (uri string, args []string) string {
cli, err := client.NewClientWithOpts(client.WithHost(uri), client.WithAPIVersionNegotiation())
if err != nil {
fmt.Printf("Unable to create docker client: %s\n", err)
os.Exit(-1)
}
DockerExecConfig.AttachStderr = DockerExecConfig.AttachStdout
DockerExecConfig.AttachStdin = DockerExecConfig.AttachStdout
DockerExecConfig.Cmd = args[1:]
execResponse, err := cli.ContainerExecCreate(ctx, args[0], DockerExecConfig)
if err != nil {
return err.Error()
}
execAttachConfig := types.ExecStartCheck{
Tty: !DockerExecConfig.Tty,
}
resp, err := cli.ContainerExecAttach(ctx, execResponse.ID, execAttachConfig)
if err != nil {
return err.Error()
}
defer resp.Close()
errChan := make(chan error, 1)
go func() {
_, errorInContainer := stdcopy.StdCopy(os.Stdout, os.Stderr, resp.Reader)
errChan <- errorInContainer
}()
go func() {
_, err := io.Copy(resp.Conn, os.Stdin)
errChan <- err
}()
select {
case errorInContainer := <-errChan:
if errorInContainer != nil {
fmt.Printf("dtools exec error: %s\n", errorInContainer.Error())
os.Exit(-1)
}
return ""
}
}
在这张图片中,您可以看到在CLI提示符下输入的每个命令也会被发送到stdout,同时请注意$PS1提示符,它显示dtools命令是从主机“bergen”发送到容器“nginx”的,而CTRL+C将控制权返回给了bergen,而不是终止tail -1 -f。随后的docker exec显示tail -1 -f仍在运行。

更多关于Golang Docker SDK编写`docker exec`类似功能时遇到的问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html
好的,我甚至尝试了ChatGPT,但得到的答案更糟(!):在容器内我有标准输入,但没有标准输出或标准错误。
任何帮助都将不胜感激,我被难住了。
更多关于Golang Docker SDK编写`docker exec`类似功能时遇到的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我在文件顶部附近添加了这行代码:
DockerExecConfig.DetachKeys = "ctrl-a,ctrl-d" … 但 CTRL+C 仍然会让我退出容器。
所以,总结一下我的问题:
. 标准输入同时被复制到标准输出
. CTRL+C 会让我退出容器,而它本应针对的命令(例如 tail -f)却得以继续执行(即没有被中断)
. 容器内的 Tab 键自动补全功能完全不起作用
问题出在ExecStartCheck的Tty设置上。你错误地将Tty设置为!DockerExecConfig.Tty,这会导致终端模式不匹配。正确的做法是保持Tty与DockerExecConfig.Tty一致。
以下是修正后的代码片段:
func ContainerExec(uri string, args []string) string {
cli, err := client.NewClientWithOpts(client.WithHost(uri), client.WithAPIVersionNegotiation())
if err != nil {
fmt.Printf("Unable to create docker client: %s\n", err)
os.Exit(-1)
}
DockerExecConfig.AttachStderr = DockerExecConfig.AttachStdout
DockerExecConfig.AttachStdin = DockerExecConfig.AttachStdout
DockerExecConfig.Cmd = args[1:]
execResponse, err := cli.ContainerExecCreate(ctx, args[0], DockerExecConfig)
if err != nil {
return err.Error()
}
// 关键修正:保持Tty设置一致
execAttachConfig := types.ExecStartCheck{
Tty: DockerExecConfig.Tty, // 这里使用相同的Tty值
}
resp, err := cli.ContainerExecAttach(ctx, execResponse.ID, execAttachConfig)
if err != nil {
return err.Error()
}
defer resp.Close()
// 根据Tty模式选择不同的处理方式
if DockerExecConfig.Tty {
// 终端模式处理
errChan := make(chan error, 1)
go func() {
_, err := io.Copy(os.Stdout, resp.Reader)
errChan <- err
}()
go func() {
_, err := io.Copy(resp.Conn, os.Stdin)
errChan <- err
}()
select {
case err := <-errChan:
if err != nil && err != io.EOF {
return fmt.Sprintf("exec error: %v", err)
}
}
} else {
// 非终端模式处理
errChan := make(chan error, 1)
go func() {
_, err := stdcopy.StdCopy(os.Stdout, os.Stderr, resp.Reader)
errChan <- err
}()
go func() {
_, err := io.Copy(resp.Conn, os.Stdin)
errChan <- err
}()
select {
case err := <-errChan:
if err != nil && err != io.EOF {
return fmt.Sprintf("exec error: %v", err)
}
}
}
// 检查执行状态
inspectResp, err := cli.ContainerExecInspect(ctx, execResponse.ID)
if err != nil {
return fmt.Sprintf("inspect error: %v", err)
}
if inspectResp.ExitCode != 0 {
return fmt.Sprintf("exited with code: %d", inspectResp.ExitCode)
}
return ""
}
对于转义序列和TAB补全问题,还需要确保终端设置正确。可以添加终端原始模式设置:
import "golang.org/x/term"
func ContainerExec(uri string, args []string) string {
// ... 前面的代码保持不变 ...
if DockerExecConfig.Tty {
// 保存原始终端状态
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return fmt.Sprintf("terminal make raw error: %v", err)
}
defer term.Restore(int(os.Stdin.Fd()), oldState)
// 设置终端大小
if err := setupTerminalResize(ctx, cli, execResponse.ID, os.Stdin); err != nil {
return fmt.Sprintf("terminal resize error: %v", err)
}
// ... 其余终端模式代码 ...
}
// ... 其余代码 ...
}
func setupTerminalResize(ctx context.Context, cli *client.Client, execID string, stdin *os.File) error {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
size, err := term.GetSize(int(stdin.Fd()))
if err != nil {
continue
}
cli.ContainerExecResize(ctx, execID, types.ResizeOptions{
Height: uint(size.Height),
Width: uint(size.Width),
})
}
}()
// 初始调整大小
size, err := term.GetSize(int(stdin.Fd()))
if err != nil {
return err
}
return cli.ContainerExecResize(ctx, execID, types.ResizeOptions{
Height: uint(size.Height),
Width: uint(size.Width),
})
}
主要修正点:
ExecStartCheck.Tty必须与DockerExecConfig.Tty保持一致- 根据Tty模式选择不同的数据流处理方式
- 添加终端原始模式设置以正确处理转义序列
- 实现终端大小调整以支持TAB补全等功能
- 添加执行状态检查
这些修正应该能解决重复输出、CTRL+C转义序列和TAB补全的问题。

