Skip to content
This repository was archived by the owner on Mar 9, 2022. It is now read-only.

Commit ae91a27

Browse files
committed
Add portforward support.
Signed-off-by: Lantao Liu <[email protected]>
1 parent 83a20c9 commit ae91a27

File tree

6 files changed

+102
-9
lines changed

6 files changed

+102
-9
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ install:
1717
- sudo apt-get install btrfs-tools
1818
- sudo apt-get install libseccomp2/trusty-backports
1919
- sudo apt-get install libseccomp-dev/trusty-backports
20+
- sudo apt-get install socat
2021
- docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter
2122
- make install.tools
2223
- make install.deps

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ specifications as appropriate.
3131
trusty.
3232
2. Install containerd dependencies.
3333
* containerd requires installation of a btrfs development library. `btrfs-tools`(Ubuntu, Debian) / `btrfs-progs-devel`(Fedora, CentOS, RHEL)
34-
3. Install and setup a go1.8.x development environment.
35-
4. Make a local clone of this repository.
36-
5. Install binary dependencies by running the following command from your cloned `cri-containerd/` project directory:
34+
3. Install other dependencies:
35+
* `nsenter`: Required by CNI and portforward.
36+
* `socat`: Required by portforward.
37+
4. Install and setup a go1.8.x development environment.
38+
5. Make a local clone of this repository.
39+
6. Install binary dependencies by running the following command from your cloned `cri-containerd/` project directory:
3740
```shell
3841
# Note: install.deps installs the above mentioned runc, containerd, and CNI
3942
# binary dependencies. install.deps is only provided for general use and ease of

hack/test-cri.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
2222
# FOCUS focuses the test to run.
2323
FOCUS=${FOCUS:-}
2424
# SKIP skips the test to skip.
25-
SKIP=${SKIP:-"attach|portforward|RunAsUser|host port"}
25+
SKIP=${SKIP:-"attach|RunAsUser|host port"}
2626
REPORT_DIR=${REPORT_DIR:-"/tmp"}
2727

2828
if [[ -z "${GOPATH}" ]]; then

pkg/server/container_exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (c *criContainerdService) Exec(ctx context.Context, r *runtime.ExecRequest)
3030
r.GetContainerId(), r.GetCmd(), r.GetTty(), r.GetStdin())
3131
defer func() {
3232
if retErr == nil {
33-
glog.V(2).Infof("Exec for %q returns URL %q", r.GetContainerId(), retRes.Url)
33+
glog.V(2).Infof("Exec for %q returns URL %q", r.GetContainerId(), retRes.GetUrl())
3434
}
3535
}()
3636

pkg/server/sandbox_portforward.go

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,99 @@ limitations under the License.
1717
package server
1818

1919
import (
20+
"bytes"
2021
"errors"
22+
"fmt"
23+
"io"
24+
"os/exec"
25+
"strings"
2126

27+
"github.com/containerd/containerd/api/services/tasks/v1"
28+
"github.com/containerd/containerd/api/types/task"
29+
"github.com/golang/glog"
2230
"golang.org/x/net/context"
23-
2431
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
2532
)
2633

2734
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
28-
func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortForwardRequest) (*runtime.PortForwardResponse, error) {
29-
return nil, errors.New("not implemented")
35+
func (c *criContainerdService) PortForward(ctx context.Context, r *runtime.PortForwardRequest) (retRes *runtime.PortForwardResponse, retErr error) {
36+
glog.V(2).Infof("Portforward for sandbox %q port %v", r.GetPodSandboxId(), r.GetPort())
37+
defer func() {
38+
if retErr == nil {
39+
glog.V(2).Infof("Portforward for %q returns URL %q", r.GetPodSandboxId(), retRes.GetUrl())
40+
}
41+
}()
42+
43+
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
44+
if err != nil {
45+
return nil, fmt.Errorf("unable to find sandbox: %v", err)
46+
}
47+
id := sandbox.ID
48+
49+
info, err := c.taskService.Get(ctx, &tasks.GetTaskRequest{ContainerID: id})
50+
if err != nil && !isContainerdGRPCNotFoundError(err) {
51+
return nil, fmt.Errorf("unable to get sandbox container info: %v", err)
52+
}
53+
54+
if info.Task.Status != task.StatusRunning {
55+
return nil, errors.New("sandbox container is not running")
56+
}
57+
// TODO(random-liu): Verify that ports are exposed.
58+
return c.streamServer.GetPortForward(r)
59+
}
60+
61+
// portForward requires `nsenter` and `socat` on the node.
62+
func (c *criContainerdService) portForward(id string, port int32, stream io.ReadWriteCloser) error {
63+
s, err := c.sandboxStore.Get(id)
64+
if err != nil {
65+
return fmt.Errorf("unable to find sandbox in store: %v", err)
66+
}
67+
pid := s.Pid
68+
69+
socat, err := exec.LookPath("socat")
70+
if err != nil {
71+
return fmt.Errorf("unable to find socat: %v", err)
72+
}
73+
74+
args := []string{"-t", fmt.Sprintf("%d", pid), "-n", socat,
75+
"-", fmt.Sprintf("TCP4:localhost:%d", port)}
76+
77+
nsenter, err := exec.LookPath("nsenter")
78+
if err != nil {
79+
return fmt.Errorf("unable to find nsenter: %v", err)
80+
}
81+
82+
glog.V(2).Infof("executing port forwarding command: %s %s", nsenter, strings.Join(args, " "))
83+
84+
cmd := exec.Command(nsenter, args...)
85+
cmd.Stdout = stream
86+
87+
stderr := new(bytes.Buffer)
88+
cmd.Stderr = stderr
89+
90+
// If we use Stdin, command.Run() won't return until the goroutine that's copying
91+
// from stream finishes. Unfortunately, if you have a client like telnet connected
92+
// via port forwarding, as long as the user's telnet client is connected to the user's
93+
// local listener that port forwarding sets up, the telnet session never exits. This
94+
// means that even if socat has finished running, command.Run() won't ever return
95+
// (because the client still has the connection and stream open).
96+
//
97+
// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
98+
// when the command (socat) exits.
99+
in, err := cmd.StdinPipe()
100+
if err != nil {
101+
return fmt.Errorf("unable to create stdin pipe: %v", err)
102+
}
103+
go func() {
104+
if _, err := io.Copy(in, stream); err != nil {
105+
glog.Errorf("Failed to copy port forward stdin for %q: %v", id, err)
106+
}
107+
in.Close()
108+
}()
109+
110+
if err := cmd.Run(); err != nil {
111+
return fmt.Errorf("nsenter command returns error: %v, stderr: %q", err, stderr.String())
112+
}
113+
114+
return nil
30115
}

pkg/server/streaming.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"io"
23+
"math"
2324
"net"
2425

2526
"golang.org/x/net/context"
@@ -83,7 +84,10 @@ func (s *streamRuntime) Attach(containerID string, in io.Reader, out, err io.Wri
8384
}
8485

8586
func (s *streamRuntime) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
86-
return errors.New("not implemented")
87+
if port < 0 || port > math.MaxUint16 {
88+
return fmt.Errorf("invalid port %d", port)
89+
}
90+
return s.c.portForward(podSandboxID, port, stream)
8791
}
8892

8993
// handleResizing spawns a goroutine that processes the resize channel, calling resizeFunc for each

0 commit comments

Comments
 (0)