Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use custom address for port forwarding #6766

Merged
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