Skip to content

Commit

Permalink
Use custom address for port forwarding (#6766)
Browse files Browse the repository at this point in the history
* Custom address for port forwarding

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Add integration tests

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Use private variables for custom address and ports

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Update pkg/util/util.go

Co-authored-by: Armel Soro <armel@rm3l.org>

* Assign custom address to HostIP for podman

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Add validation for free port when --port-forward is provided in the Validate() method, add integration test for running parallel dev sessions on same platform, same port and different addresses

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Fix unit test failure

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Attempt at fixing windows failure

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Add documentation

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Use default value for --address flag

Signed-off-by: Parthvi Vala <pvala@redhat.com>

* Changes from review

Co-authored-by: Armel Soro <asoro@redhat.com>
Signed-off-by: Parthvi Vala <pvala@redhat.com>

---------

Signed-off-by: Parthvi Vala <pvala@redhat.com>
Co-authored-by: Armel Soro <armel@rm3l.org>
Co-authored-by: Armel Soro <asoro@redhat.com>
  • Loading branch information
3 people authored May 2, 2023
1 parent 191ee6f commit 010e963
Show file tree
Hide file tree
Showing 23 changed files with 898 additions and 284 deletions.
108 changes: 108 additions & 0 deletions docs/website/docs/command-reference/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,114 @@ The following command will override the `USER` Devfile variable with the `john`
odo dev --var USER=john --var-file config.vars
```


### Using custom port mapping for port forwarding
Custom local ports can be passed for port forwarding with the help of the `--port-forward` flag. This feature is supported on both podman and cluster.

This feature can be helpful when you want to provide consistent and predictable port numbers and avoid being assigned a potentially different port number every time `odo dev` is run.

Supported formats for this flag include:
1. `<LOCAL_PORT>:<CONTAINER_PORT>`
2. `<LOCAL_PORT>:<CONTAINER_NAME>:<CONTAINER_PORT>` - This format is necessary when multiple container components of a Devfile have the same port number.

The flag accepts a stringArray, so `--port-forward` flag can be defined multiple times.

If a custom port mapping is not defined for a port, `odo` will assign a free port in the range of 20001-30001.

```shell
odo dev --port-forward <LOCAL_PORT_1>:<CONTAINER_PORT_1> --port-forward <LOCAL_PORT_2>:<CONTAINER_NAME>:<CONTAINER_PORT_2>
```

<details>
<summary>Example</summary>

```shell
$ odo dev --port-forward 3000:runtime:3000 --port-forward 5000:5858 --debug
__
/ \__ Developing using the "my-nodejs-app" Devfile
\__/ \ Namespace: default
/ \__/ odo version: v3.9.0
\__/
⚠ You are using "default" namespace, odo may not work as expected in the default namespace.
⚠ You may set a new namespace by running `odo create namespace <name>`, or set an existing one by running `odo set namespace <name>`

↪ Running on the cluster in Dev mode
• Waiting for Kubernetes resources ...
⚠ Pod is Pending
✓ Pod is Running
✓ Syncing files into the container [152ms]
✓ Building your application in container (command: install) [27s]
• Executing the application (command: debug) ...
✓ Waiting for the application to be ready [1s]
- Forwarding from 127.0.0.1:8000 -> 3000

- Forwarding from 127.0.0.1:5000 -> 5858


↪ Dev mode
Status:
Watching for changes in the current directory /tmp/nodejs-debug-2

Keyboard Commands:
[Ctrl+c] - Exit and delete resources from the cluster
[p] - Manually apply local changes to the application on the cluster
```
</details>

Note that `--random-ports` flag cannot be used with `--port-forward` flag.

### Using custom address for port forwarding
A custom address can be passed for port forwarding with the help of `--address` flag. This feature is supported on both podman and cluster.
The default value is 127.0.0.1.

```shell
odo dev --address <IP_ADDRESS>
```

<details>
<summary>Example</summary>

```shell
$ odo dev --address 127.0.10.3
__
/ \__ Developing using the "my-nodejs-app" Devfile
\__/ \ Namespace: default
/ \__/ odo version: v3.9.0
\__/

⚠ You are using "default" namespace, odo may not work as expected in the default namespace.
⚠ You may set a new namespace by running `odo create namespace <name>`, or set an existing one by running `odo set namespace <name>`

↪ Running on the cluster in Dev mode
• Waiting for Kubernetes resources ...
⚠ Pod is Pending
✓ Pod is Running
✓ Syncing files into the container [123ms]
✓ Building your application in container (command: install) [15s]
• Executing the application (command: run) ...
✓ Waiting for the application to be ready [1s]
- Forwarding from 127.0.10.3:20001 -> 3000


↪ Dev mode
Status:
Watching for changes in the current directory /tmp/nodejs-debug-2

Keyboard Commands:
[Ctrl+c] - Exit and delete resources from the cluster
[p] - Manually apply local changes to the application on the cluster
```
</details>
:::note
If you are on macOS and using a Cluster platform, you may not be able to run multiple Dev sessions in parallel on address 0.0.0.0 without defining a custom port mapping, or using a different or default address.
For more information, see the following issues:
1. [Cannot start 2 different Dev sessions on Podman due to conflicting host ports](https://github.com/redhat-developer/odo/issues/6612)
2. [[MacOS] Cannot run 2 dev sessions simultaneously on cluster](https://github.com/redhat-developer/odo/issues/6744)
:::
### Running on Podman
Instead of deploying the container into a Kubernetes cluster, `odo dev` can leverage the podman installation on your system to deploy the container.
Expand Down
2 changes: 2 additions & 0 deletions pkg/dev/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type StartOptions struct {
RandomPorts bool
// CustomForwardedPorts define custom ports for port forwarding
CustomForwardedPorts []api.ForwardedPort
// CustomAddress defines a custom local address for port forwarding; default value is 127.0.0.1
CustomAddress string
// if WatchFiles is set, files changes will trigger a new sync to the container
WatchFiles bool
// IgnoreLocalhost indicates whether to proceed with port-forwarding regardless of any container ports being bound to the container loopback interface.
Expand Down
2 changes: 1 addition & 1 deletion pkg/dev/kubedev/innerloop.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func (o *DevClient) innerloop(ctx context.Context, parameters common.PushParamet
fmt.Fprintln(log.GetStdout())
}

err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts)
err = o.portForwardClient.StartPortForwarding(ctx, parameters.Devfile, componentName, parameters.StartOptions.Debug, parameters.StartOptions.RandomPorts, log.GetStdout(), parameters.StartOptions.ErrOut, parameters.StartOptions.CustomForwardedPorts, parameters.StartOptions.CustomAddress)
if err != nil {
return common.NewErrPortForward(err)
}
Expand Down
25 changes: 17 additions & 8 deletions pkg/dev/podmandev/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func createPodFromComponent(
randomPorts bool,
customForwardedPorts []api.ForwardedPort,
usedPorts []int,
customAddress string,
) (*corev1.Pod, []api.ForwardedPort, error) {
var (
appName = odocontext.GetApplication(ctx)
Expand All @@ -60,7 +61,7 @@ func createPodFromComponent(
}

var fwPorts []api.ForwardedPort
fwPorts, err = getPortMapping(*devfileObj, debug, randomPorts, usedPorts, customForwardedPorts)
fwPorts, err = getPortMapping(*devfileObj, debug, randomPorts, usedPorts, customForwardedPorts, customAddress)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -112,7 +113,7 @@ func createPodFromComponent(
return nil, nil, err
}

containers = addHostPorts(withHelperContainer, containers, fwPorts)
containers = addHostPorts(withHelperContainer, containers, fwPorts, customAddress)

pod := corev1.Pod{
Spec: corev1.PodSpec{
Expand Down Expand Up @@ -142,7 +143,10 @@ func createPodFromComponent(
return &pod, fwPorts, nil
}

func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPorts []api.ForwardedPort) []corev1.Container {
func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPorts []api.ForwardedPort, customAddress string) []corev1.Container {
if customAddress == "" {
customAddress = "127.0.0.1"
}
if withHelperContainer {
// A side helper container is added and will be responsible for redirecting the traffic,
// so it can work even if the application is listening on the container loopback interface.
Expand All @@ -164,6 +168,7 @@ func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPor
Name: fwPort.PortName,
ContainerPort: int32(fwPort.LocalPort),
HostPort: int32(fwPort.LocalPort),
HostIP: customAddress,
})
}
containers = append(containers, pfHelperContainer)
Expand All @@ -176,6 +181,7 @@ func addHostPorts(withHelperContainer bool, containers []corev1.Container, fwPor
for _, fwPort := range fwPorts {
if containers[i].Name == fwPort.ContainerName && int(p.ContainerPort) == fwPort.ContainerPort {
p.HostPort = int32(fwPort.LocalPort)
p.HostIP = customAddress
containerPorts = append(containerPorts, p)
break
}
Expand All @@ -191,7 +197,10 @@ func getVolumeName(volume string, componentName string, appName string) string {
return volume + "-" + componentName + "-" + appName
}

func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int, definedPorts []api.ForwardedPort) ([]api.ForwardedPort, error) {
func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool, usedPorts []int, definedPorts []api.ForwardedPort, address string) ([]api.ForwardedPort, error) {
if address == "" {
address = "127.0.0.1"
}
containerComponents, err := devfileObj.Data.GetComponents(common.DevfileOptions{
ComponentOptions: common.ComponentOptions{ComponentType: v1alpha2.ContainerComponentType},
})
Expand Down Expand Up @@ -268,7 +277,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
freePort = getCustomLocalPort(ep.TargetPort, containerName)
if freePort == 0 {
for {
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
if err != nil {
klog.Infof("%s", err)
continue
Expand All @@ -290,15 +299,15 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
rand.Seed(time.Now().UnixNano()) // #nosec
for {
freePort = rand.Intn(endPort-startPort+1) + startPort // #nosec
if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort) {
if !isPortUsedInContainer(freePort) && util.IsPortFree(freePort, address) {
break
}
time.Sleep(100 * time.Millisecond)
}
}
} else {
for {
freePort, err = util.NextFreePort(startPort, endPort, usedPorts)
freePort, err = util.NextFreePort(startPort, endPort, usedPorts, address)
if err != nil {
klog.Infof("%s", err)
continue epLoop
Expand All @@ -316,7 +325,7 @@ func getPortMapping(devfileObj parser.DevfileObj, debug bool, randomPorts bool,
PortName: portName,
IsDebug: isDebugPort,
ContainerName: containerName,
LocalAddress: "127.0.0.1",
LocalAddress: address,
LocalPort: freePort,
ContainerPort: ep.TargetPort,
Exposure: string(ep.Exposure),
Expand Down
Loading

0 comments on commit 010e963

Please sign in to comment.