Public/Start-KubePortForward.ps1
|
Function Start-KubePortForward { <# .SYNOPSIS Opens a local port-forward to a pod and relays traffic between the local port and the pod's target port while keeping the connection active for interactive debugging or service access. This command binds local listeners, creates the Kubernetes port-forward session, and forwards data bidirectionally so a local process can connect to a service inside the cluster as though it were running on the host. .EXAMPLE Start-KubePortForward -Pod api -Namespace default -LocalPort 8080 -RemotePort 80 # Forwards local port 8080 to port 80 on the api pod. .EXAMPLE Start-KubePortForward -Pod web -Namespace prod -LocalPort 9090 -RemotePort 8080 -Addresses 127.0.0.1 # Exposes the pod service locally on a specific loopback address. .EXAMPLE Set-KubeSelectedNamespace -Namespace default Start-KubePortForward -Pod api -LocalPort 8080 -RemotePort 80 # Uses the current default namespace when no namespace is explicitly provided. #> [CmdletBinding()] param( [Parameter(Mandatory=$true, Position = 1)] [ValidateRange(0, 65535)] [int]$LocalPort, [Parameter(Mandatory=$true, Position = 2)] [ValidateRange(1, 65535)] [int]$RemotePort, [Parameter(Mandatory=$False, Position = 3)] [string[]]$Addresses = @('127.0.0.1') ) DynamicParam { $DynamicParameterDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() $NewDynamicParams = @{ DynamicParameterDictionary = $DynamicParameterDictionary } $Pods = Get-KubePod -AllNamespaces $Namespaces = Get-KubeNamespace $ParamList = @( @{ ParameterName = 'Pod' ParameterType = ([string]) Mandatory = $true ValidateSet = [array]$Pods.Name Alias = 'Name' ValueFromPipeline = $True ValueFromPipelineByPropertyName = $True HelpMessage = 'The pod to forward a local port to.' Position = 0 }, @{ ParameterName = 'Namespace' ParameterType = ([string]) DefaultValue = $Script:SelectedNamespace ValidateSet = [array]$Namespaces.Name HelpMessage = 'The pod namespace.' Position = 4 } ) ForEach($Param in $ParamList) { Add-DynamicParameter @NewDynamicParams @Param } Return $DynamicParameterDictionary } Process { $PodName = $DynamicParameterDictionary['Pod'].Value $Namespace = $DynamicParameterDictionary['Namespace'].Value If([string]::IsNullOrWhiteSpace($Namespace)) { Write-Error 'Specify -Namespace or use Set-KubeSelectedNamespace.' -Category InvalidArgument Return } $BindAddresses = If($Addresses.Count -gt 0) { $Addresses } Else { @('127.0.0.1') } $Listeners = [System.Collections.Generic.List[System.Net.Sockets.TcpListener]]::new() ForEach($BindAddress in $BindAddresses) { ForEach($IPAddress in [System.Net.Dns]::GetHostAddresses($BindAddress)) { $Listener = [System.Net.Sockets.TcpListener]::new($IPAddress, $LocalPort) $Listener.Start() $Listeners.Add($Listener) $BoundPort = ([System.Net.IPEndPoint]$Listener.LocalEndpoint).Port Write-Verbose "Forwarding $BindAddress`:$BoundPort to $PodName`:$RemotePort in namespace $Namespace. Press Ctrl+C to stop." } } Try { $AcceptTasks = @($Listeners | ForEach-Object { $_.AcceptTcpClientAsync() }) While($true) { $CompletedAccept = [System.Threading.Tasks.Task]::WhenAny([System.Threading.Tasks.Task[]]$AcceptTasks).GetAwaiter().GetResult() $AcceptIndex = [array]::IndexOf([System.Threading.Tasks.Task[]]$AcceptTasks, [System.Threading.Tasks.Task]$CompletedAccept) $TcpClient = $CompletedAccept.Result $AcceptTasks[$AcceptIndex] = $Listeners[$AcceptIndex].AcceptTcpClientAsync() $PodStream = $null $NetworkStream = $TcpClient.GetStream() Try { $PodStream = [k8s.CoreV1OperationsExtensions]::ConnectGetNamespacedPodPortforwardAsync($Script:KubePSDirectClient.CoreV1, $PodName, $Namespace, $RemotePort).GetAwaiter().GetResult() $ToPod = $NetworkStream.CopyToAsync($PodStream) $ToLocal = $PodStream.CopyToAsync($NetworkStream) [System.Threading.Tasks.Task]::WhenAny($ToPod, $ToLocal).GetAwaiter().GetResult() | Out-Null } Finally { If($PodStream) { $PodStream.Dispose() } $NetworkStream.Dispose() $TcpClient.Dispose() } } } Finally { ForEach($Listener in $Listeners) { $Listener.Stop() } } } } |