-
Notifications
You must be signed in to change notification settings - Fork 40k
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
kubectl port-forward should resolve service port to target port #59809
kubectl port-forward should resolve service port to target port #59809
Conversation
/sig cli |
pkg/kubectl/cmd/portforward.go
Outdated
if err != nil { | ||
return err | ||
} | ||
|
||
o.PodName = forwardablePod.Name | ||
|
||
if cmdutil.GetFlagBool(cmd, "disable-target-port-translation") == false { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd avoid adding the translation flag until we have a clear need for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was thinking to have an escape hatch with this flag, thus default to false, in case there are use cases that I can't conceive. I have no problem removing it and added it back later if needed. I will do that also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
port-forwarding to service is new function, so let's start small and iterate as needed... someone can always go look the pod up themselves if they want to target a specific port on the pod
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed.
pkg/kubectl/cmd/portforward.go
Outdated
pod := &v1.Pod{} | ||
svc := &v1.Service{} | ||
apiv1.Convert_core_Pod_To_v1_Pod(forwardablePod, pod, nil) | ||
apiv1.Convert_core_Service_To_v1_Service(t, svc, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think importing and manually converting is worse that just writing the port mapping to work on internal service and pod objects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I will give that a shot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
pkg/kubectl/util/service_port.go
Outdated
if svc.Spec.ClusterIP == "None" { | ||
mapping[svcportspec.Port] = svcportspec.Port | ||
} else { | ||
if svcportspec.TargetPort.Type == intstr.Int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TargetPort can be omitted, and in that case, mapping[svcportspec.Port] = svcportspec.Port
should be used:
If this is not specified, the value of the 'port' field is used (an identity map).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix that and add a test case for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the only safe way to detect omitted is to assume the IntValue() is 0, but if there is a better way please let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, looks like defaulting takes care of this for you and sets targetPort to match port when omitted (https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/core/v1/defaults.go#L120). Can you verify that is the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it does. I can delete targetPort from a service, and it gets regenerated. I think that is why I did not notice this case before. However, since this code now support internal object, and may be reused by others, it is probably safer to assume defaulting may not happen and leave it as is.
pkg/kubectl/util/service_port.go
Outdated
mapping[svcportspec.Port] = int32(svcportspec.TargetPort.IntValue()) | ||
} | ||
} else { | ||
portnum, err := LookupContainerPortNumberByName(pod, svcportspec.TargetPort.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has the potential to return an error and short-circuit on a port that we're not even going to use... you could just omit the target from the mapping (or map to zero), and handle missing cases in translateServicePortToTargetPort if we actually try to make use of ports that don't have corresponding ports in the pod
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better if we just look up as we process the arguments, and the error would be much clear? I did the gathering first because it was easier for me to implement at the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realized that lookup failure can't be treated as failures, because service/pod is not required to declare every port a pod listens on, so the point of surfacing clear error is irrelevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think it would work by surfacing only the named port lookup error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
services are required to declare the ports they surface, and pods must declare a named port for named mappings to work
I think it would work by surfacing only the named port lookup error
that sounds right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
services are required to declare the ports they surface
So in that expectation, if a Service does not declare a port, but the user still wants to port-forward to that port, should that be treated as error? Right now I am pass it as-is to match the use case for other types of resources.
pkg/kubectl/util/service_port.go
Outdated
// It returns an error when a named port can't find a match (with -1 returned), or when the service does not | ||
// specify such port (with the input port number returned). | ||
func LookupContainerPortNumberByServicePort(svc api.Service, pod api.Pod, port int32) (int32, error) { | ||
if svc.Spec.ClusterIP == "None" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use the api constant
kubernetes/staging/src/k8s.io/api/core/v1/types.go
Lines 3580 to 3584 in 0dda5c8
const ( | |
// ClusterIPNone - do not assign a cluster IP | |
// no proxying required and no environment variables should be created for pods | |
ClusterIPNone = "None" | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
pkg/kubectl/util/service_port.go
Outdated
for _, ctr := range pod.Spec.Containers { | ||
for _, ctrportspec := range ctr.Ports { | ||
if ctrportspec.Name == name { | ||
namedport = ctrportspec.ContainerPort |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just return here
pkg/kubectl/util/service_port.go
Outdated
} | ||
} | ||
|
||
if matchfound == false { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unconditional error here
pkg/kubectl/util/service_port.go
Outdated
return int32(svcportspec.TargetPort.IntValue()), nil | ||
} | ||
} else { | ||
ret, err := LookupContainerPortNumberByName(pod, svcportspec.TargetPort.String()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just return LookupContainerPortNumberByName(pod, svcportspec.TargetPort.String())
pkg/kubectl/cmd/portforward.go
Outdated
} | ||
containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum)) | ||
if err != nil { | ||
if containerPort <= 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error in all cases here... if the service didn't declare the port, I don't think it's valid to try to port-forward to it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we went back to the original behavior (using gathered mapping), but it is much more clear how and why we reached the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found another corner case. If ClusterIP: None, the current behavior would return the port as-is, even if the port is not declared by the service. Let me fix that also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ClusterIP=None
, then no proxying occurs today, right? does it make sense to support port-forward on such a service?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to support port-forward even for ClusterIP=None. A common use case for me is for pods with NetworkPolicy that restrict general access, so port-forward (not proxy) is a way for reaching it for testing/verification without changing the NetworkPolicy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, with the change to still require the service to declare the port, that seems reasonable
thanks, go ahead and squash and we'll kick off CI |
@kubernetes/sig-network-pr-reviews |
Cool, thanks for the help. |
What this PR does / why we need it:
Continues on the work in #59705, this PR adds additional support for looking up targetPort for a service, as well as enable using svc/name to select a pod.
Which issue(s) this PR fixes:
Fixes #15180
Fixes #59733
Special notes for your reviewer:
I decided to create pkg/kubectl/util/service_port.go to contain two functions that might be re-usable.
Release note: