diff --git a/pkg/kubelet/dockershim/exec.go b/pkg/kubelet/dockershim/exec.go index 1d73a8a8d5c6..f7344eb727be 100644 --- a/pkg/kubelet/dockershim/exec.go +++ b/pkg/kubelet/dockershim/exec.go @@ -135,6 +135,9 @@ func (*NsenterExecHandler) ExecInContainer(client libdocker.Interface, container type NativeExecHandler struct{} func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error { + done := make(chan struct{}) + defer close(done) + createOpts := dockertypes.ExecConfig{ Cmd: cmd, AttachStdin: stdin != nil, @@ -149,9 +152,23 @@ func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container // Have to start this before the call to client.StartExec because client.StartExec is a blocking // call :-( Otherwise, resize events don't get processed and the terminal never resizes. - kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { - client.ResizeExecTTY(execObj.ID, int(size.Height), int(size.Width)) - }) + // + // We also have to delay attempting to send a terminal resize request to docker until after the + // exec has started; otherwise, the initial resize request will fail. + execStarted := make(chan struct{}) + go func() { + select { + case <-execStarted: + // client.StartExec has started the exec, so we can start resizing + case <-done: + // ExecInContainer has returned, so short-circuit + return + } + + kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { + client.ResizeExecTTY(execObj.ID, int(size.Height), int(size.Width)) + }) + }() startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty} streamOpts := libdocker.StreamOptions{ @@ -159,6 +176,7 @@ func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container OutputStream: stdout, ErrorStream: stderr, RawTerminal: tty, + ExecStarted: execStarted, } err = client.StartExec(execObj.ID, startOpts, streamOpts) if err != nil { diff --git a/pkg/kubelet/dockershim/libdocker/kube_docker_client.go b/pkg/kubelet/dockershim/libdocker/kube_docker_client.go index f3bbc3f0536e..7fdfb03e1ca0 100644 --- a/pkg/kubelet/dockershim/libdocker/kube_docker_client.go +++ b/pkg/kubelet/dockershim/libdocker/kube_docker_client.go @@ -463,6 +463,15 @@ func (d *kubeDockerClient) StartExec(startExec string, opts dockertypes.ExecStar return err } defer resp.Close() + + if sopts.ExecStarted != nil { + // Send a message to the channel indicating that the exec has started. This is needed so + // interactive execs can handle resizing correctly - the request to resize the TTY has to happen + // after the call to d.client.ContainerExecAttach, and because d.holdHijackedConnection below + // blocks, we use sopts.ExecStarted to signal the caller that it's ok to resize. + sopts.ExecStarted <- struct{}{} + } + return d.holdHijackedConnection(sopts.RawTerminal || opts.Tty, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp) } @@ -593,6 +602,7 @@ type StreamOptions struct { InputStream io.Reader OutputStream io.Writer ErrorStream io.Writer + ExecStarted chan struct{} } // operationTimeout is the error returned when the docker operations are timeout.