diff --git a/pkg/kubelet/dockershim/BUILD b/pkg/kubelet/dockershim/BUILD index 41c8969fe70..d73a27b007e 100644 --- a/pkg/kubelet/dockershim/BUILD +++ b/pkg/kubelet/dockershim/BUILD @@ -21,6 +21,8 @@ go_library( "docker_stats_unsupported.go", "docker_stats_windows.go", "docker_streaming.go", + "docker_streaming_others.go", + "docker_streaming_windows.go", "exec.go", "helpers.go", "helpers_linux.go", diff --git a/pkg/kubelet/dockershim/docker_streaming.go b/pkg/kubelet/dockershim/docker_streaming.go index 3cadf887d3e..2df59c6b05d 100644 --- a/pkg/kubelet/dockershim/docker_streaming.go +++ b/pkg/kubelet/dockershim/docker_streaming.go @@ -22,13 +22,9 @@ import ( "fmt" "io" "math" - "os/exec" - "strings" "time" dockertypes "github.com/docker/docker/api/types" - "k8s.io/klog" - "k8s.io/client-go/tools/remotecommand" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" @@ -72,7 +68,7 @@ func (r *streamingRuntime) PortForward(podSandboxID string, port int32, stream i if port < 0 || port > math.MaxUint16 { return fmt.Errorf("invalid port %d", port) } - return portForward(r.client, podSandboxID, port, stream) + return r.portForward(podSandboxID, port, stream) } // ExecSync executes a command in the container, and returns the stdout output. @@ -174,60 +170,3 @@ func attachContainer(client libdocker.Interface, containerID string, stdin io.Re } return client.AttachToContainer(containerID, opts, sopts) } - -func portForward(client libdocker.Interface, podSandboxID string, port int32, stream io.ReadWriteCloser) error { - container, err := client.InspectContainer(podSandboxID) - if err != nil { - return err - } - - if !container.State.Running { - return fmt.Errorf("container not running (%s)", container.ID) - } - - containerPid := container.State.Pid - socatPath, lookupErr := exec.LookPath("socat") - if lookupErr != nil { - return fmt.Errorf("unable to do port forwarding: socat not found.") - } - - args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)} - - nsenterPath, lookupErr := exec.LookPath("nsenter") - if lookupErr != nil { - return fmt.Errorf("unable to do port forwarding: nsenter not found.") - } - - commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " ")) - klog.V(4).Infof("executing port forwarding command: %s", commandString) - - command := exec.Command(nsenterPath, args...) - command.Stdout = stream - - stderr := new(bytes.Buffer) - command.Stderr = stderr - - // If we use Stdin, command.Run() won't return until the goroutine that's copying - // from stream finishes. Unfortunately, if you have a client like telnet connected - // via port forwarding, as long as the user's telnet client is connected to the user's - // local listener that port forwarding sets up, the telnet session never exits. This - // means that even if socat has finished running, command.Run() won't ever return - // (because the client still has the connection and stream open). - // - // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe - // when the command (socat) exits. - inPipe, err := command.StdinPipe() - if err != nil { - return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err) - } - go func() { - io.Copy(inPipe, stream) - inPipe.Close() - }() - - if err := command.Run(); err != nil { - return fmt.Errorf("%v: %s", err, stderr.String()) - } - - return nil -} diff --git a/pkg/kubelet/dockershim/docker_streaming_others.go b/pkg/kubelet/dockershim/docker_streaming_others.go new file mode 100644 index 00000000000..60a5dc6b8f3 --- /dev/null +++ b/pkg/kubelet/dockershim/docker_streaming_others.go @@ -0,0 +1,86 @@ +// +build !windows + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dockershim + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "strings" + + "k8s.io/klog" +) + +func (r *streamingRuntime) portForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error { + container, err := r.client.InspectContainer(podSandboxID) + if err != nil { + return err + } + + if !container.State.Running { + return fmt.Errorf("container not running (%s)", container.ID) + } + + containerPid := container.State.Pid + socatPath, lookupErr := exec.LookPath("socat") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: socat not found.") + } + + args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)} + + nsenterPath, lookupErr := exec.LookPath("nsenter") + if lookupErr != nil { + return fmt.Errorf("unable to do port forwarding: nsenter not found.") + } + + commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " ")) + klog.V(4).Infof("executing port forwarding command: %s", commandString) + + command := exec.Command(nsenterPath, args...) + command.Stdout = stream + + stderr := new(bytes.Buffer) + command.Stderr = stderr + + // If we use Stdin, command.Run() won't return until the goroutine that's copying + // from stream finishes. Unfortunately, if you have a client like telnet connected + // via port forwarding, as long as the user's telnet client is connected to the user's + // local listener that port forwarding sets up, the telnet session never exits. This + // means that even if socat has finished running, command.Run() won't ever return + // (because the client still has the connection and stream open). + // + // The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe + // when the command (socat) exits. + inPipe, err := command.StdinPipe() + if err != nil { + return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err) + } + go func() { + io.Copy(inPipe, stream) + inPipe.Close() + }() + + if err := command.Run(); err != nil { + return fmt.Errorf("%v: %s", err, stderr.String()) + } + + return nil +} diff --git a/pkg/kubelet/dockershim/docker_streaming_windows.go b/pkg/kubelet/dockershim/docker_streaming_windows.go new file mode 100644 index 00000000000..3d78d183bc8 --- /dev/null +++ b/pkg/kubelet/dockershim/docker_streaming_windows.go @@ -0,0 +1,37 @@ +// +build windows + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dockershim + +import ( + "bytes" + "fmt" + "io" + + "k8s.io/kubernetes/pkg/kubelet/util/ioutils" +) + +func (r *streamingRuntime) portForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error { + stderr := new(bytes.Buffer) + err := r.exec(podSandboxID, []string{"wincat.exe", "localhost", fmt.Sprint(port)}, stream, stream, ioutils.WriteCloserWrapper(stderr), false, nil, 0) + if err != nil { + return fmt.Errorf("%v: %s", err, stderr.String()) + } + + return nil +}