Skip to content

Commit

Permalink
Add helper sidecar container to the Pod Spec generated on Podman
Browse files Browse the repository at this point in the history
As explained in [1], this helper sidecar container (aptly named "odo-helper-port-forwarding")
will hold the actual container/host ports mapping in the spec
(to overcome the limitation of using hostPort against a container app listening on the loopback interface);
this running container will be used to execute the actual port-forwarding commands (based on socat), e.g:

```
apiVersion: v1
kind: Pod
metadata:
  name: my-component-app
  labels:
    app: my-component-app
spec:
  containers:
  - name: runtime
    command: [ 'tail', '-f', '/dev/null']
    # Omitted for brevity, but imagine an application being executed by odo
    # and listening on both 0.0.0.0:3000 and 127.0.0.1:5858

  - name: odo-helper-port-forwarding
    image: quay.io/devfile/base-developer-image:ubi8-latest
    command: [ 'tail', '-f', '/dev/null']
    ports:
    - containerPort: 20001
      # A command will be executed by odo, e.g.: 'socat -d -d tcp-listen:20001,reuseaddr,fork tcp:localhost:3000'
      hostPort: 20001
    - containerPort: 20002
      # A command will be executed by odo, e.g.: 'socat -d -d tcp-listen:20002,reuseaddr,fork tcp:localhost:5858'
      hostPort: 20002

# ... Omitted for brevity
```

In this scope, port-forwarding from 20002 to 5858 for example will be equivalent of executing
a socat command in this helper container, like so:

socat -d tcp-listen:20002,reuseaddr,fork tcp:localhost:5858

In the command above, this will open up port 20001 on the helper container,
and forwarding requests to localhost:5858
(which would be in the 'runtime' container, part of the same Pod)

[1] redhat-developer#6510
  • Loading branch information
rm3l committed Mar 1, 2023
1 parent 2802ed6 commit dc5fec0
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 53 deletions.
121 changes: 82 additions & 39 deletions pkg/dev/podmandev/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import (
"k8s.io/klog"
)

// See https://github.com/devfile/developer-images and https://quay.io/repository/devfile/base-developer-image?tab=tags
const (
portForwardingHelperContainerName = "odo-helper-port-forwarding"
portForwardingHelperImage = "quay.io/devfile/base-developer-image@sha256:27d5ce66a259decb84770ea0d1ce8058a806f39dfcfeed8387f9cf2f29e76480"
)

func createPodFromComponent(
devfileObj parser.DevfileObj,
componentName string,
Expand All @@ -42,23 +48,14 @@ func createPodFromComponent(
return nil, nil, fmt.Errorf("no valid components found in the devfile")
}

containers, err = utils.UpdateContainersEntrypointsIfNeeded(devfileObj, containers, buildCommand, runCommand, debugCommand)
fwPorts, err := getPortMapping(devfileObj, debug, randomPorts, usedPorts)
if err != nil {
return nil, nil, err
}

utils.AddOdoProjectVolume(&containers)
utils.AddOdoMandatoryVolume(&containers)

// get the endpoint/port information for containers in devfile
containerComponents, err := devfileObj.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
})
if err != nil {
return nil, nil, err
}
ceMapping := libdevfile.GetContainerEndpointMapping(containerComponents, debug)
fwPorts := addHostPorts(containers, ceMapping, debug, randomPorts, usedPorts)

volumes := []corev1.Volume{
{
Name: storage.OdoSourceVolume,
Expand Down Expand Up @@ -98,6 +95,34 @@ func createPodFromComponent(
}
}

containers, err = utils.UpdateContainersEntrypointsIfNeeded(devfileObj, containers, buildCommand, runCommand, debugCommand)
if err != nil {
return nil, nil, err
}

// Remove all containerPorts, as they will be set afterwards in the helper container
for i := range containers {
containers[i].Ports = nil
}
// Add helper container for port-forwarding
pfHelperContainer := corev1.Container{
Name: portForwardingHelperContainerName,
Image: portForwardingHelperImage,
Command: []string{"tail"},
Args: []string{"-f", "/dev/null"},
}
for _, fwPort := range fwPorts {
pfHelperContainer.Ports = append(pfHelperContainer.Ports, corev1.ContainerPort{
// It is intentional here to use the same port as ContainerPort and HostPort, for simplicity.
// In the helper container, a process will be run afterwards and will be listening on this port;
// this process will leverage socat to forward requests to the actual application port.
Name: fwPort.PortName,
ContainerPort: int32(fwPort.LocalPort),
HostPort: int32(fwPort.LocalPort),
})
}
containers = append(containers, pfHelperContainer)

pod := corev1.Pod{
Spec: corev1.PodSpec{
Containers: containers,
Expand All @@ -123,21 +148,44 @@ func getVolumeName(volume string, componentName string, appName string) string {
return volume + "-" + componentName + "-" + appName
}

func addHostPorts(containers []corev1.Container, ceMapping map[string][]v1alpha2.Endpoint, debug bool, randomPorts bool, usedPorts []int) []api.ForwardedPort {
func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int) ([]api.ForwardedPort, error) {
containerComponents, err := devfileObj.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
})
if err != nil {
return nil, err
}
ceMapping := libdevfile.GetContainerEndpointMapping(containerComponents, debug)

var existingContainerPorts []int
for _, endpoints := range ceMapping {
for _, ep := range endpoints {
existingContainerPorts = append(existingContainerPorts, ep.TargetPort)
}
}

isPortUsedInContainer := func(p int) bool {
for _, port := range existingContainerPorts {
if p == port {
return true
}
}
return false
}

var result []api.ForwardedPort
startPort := 20001
endPort := startPort + 10000
usedPortsCopy := make([]int, len(usedPorts))
copy(usedPortsCopy, usedPorts)
for i := range containers {
var ports []corev1.ContainerPort
for _, port := range containers[i].Ports {
containerName := containers[i].Name
portName := port.Name
for containerName, endpoints := range ceMapping {
epLoop:
for _, ep := range endpoints {
portName := ep.Name
isDebugPort := libdevfile.IsDebugPort(portName)
if !debug && isDebugPort {
klog.V(4).Infof("not running in Debug mode, so skipping container Debug port: %v:%v:%v",
containerName, portName, port.ContainerPort)
klog.V(4).Infof("not running in Debug mode, so skipping Debug endpoint %s (%d) for container %q",
portName, ep.TargetPort, containerName)
continue
}
var freePort int
Expand All @@ -149,46 +197,41 @@ func addHostPorts(containers []corev1.Container, ceMapping map[string][]v1alpha2
rand.Seed(time.Now().UnixNano()) //#nosec
for {
freePort = rand.Intn(endPort-startPort+1) + startPort //#nosec
if util.IsPortFree(freePort) {
if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort) {
break
}
time.Sleep(100 * time.Millisecond)
}
}
} else {
var err error
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
if err != nil {
klog.Infof("%s", err)
continue
for {
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
if err != nil {
klog.Infof("%s", err)
continue epLoop
}
if !isPortUsedInContainer(freePort) {
break
}
startPort = freePort + 1
time.Sleep(100 * time.Millisecond)
}
startPort = freePort + 1
}
// Find the endpoint in the container-endpoint mapping
containerPort := int(port.ContainerPort)
fp := api.ForwardedPort{
Platform: commonflags.PlatformPodman,
PortName: portName,
IsDebug: isDebugPort,
ContainerName: containerName,
LocalAddress: "127.0.0.1",
LocalPort: freePort,
ContainerPort: containerPort,
}

for _, ep := range ceMapping[containerName] {
if ep.TargetPort == containerPort {
fp.Exposure = string(ep.Exposure)
break
}
ContainerPort: ep.TargetPort,
Exposure: string(ep.Exposure),
}
result = append(result, fp)
port.HostPort = int32(freePort)
ports = append(ports, port)
}
containers[i].Ports = ports
}
return result
return result, nil
}

func addVolumeMountToContainer(containers []corev1.Container, devfileVolume storage.LocalStorage) error {
Expand Down
Loading

0 comments on commit dc5fec0

Please sign in to comment.