-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Allow user-defined ports on Configuration #2642
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,15 +77,134 @@ func TestContainerValidation(t *testing.T) { | |
| }, | ||
| want: nil, | ||
| }, { | ||
| name: "has ports", | ||
| name: "has no container ports set", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{}, | ||
| }, | ||
| want: nil, | ||
| }, { | ||
| name: "has valid user port http1", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Name: "http", | ||
| Name: "http1", | ||
| ContainerPort: 8081, | ||
| }}, | ||
| }, | ||
| want: nil, | ||
| }, { | ||
| name: "has valid user port h2c", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Name: "h2c", | ||
| ContainerPort: 8081, | ||
| }}, | ||
| }, | ||
| want: nil, | ||
| }, { | ||
| name: "has more than one ports with valid names", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Name: "h2c", | ||
| ContainerPort: 8080, | ||
| }, { | ||
| Name: "http1", | ||
| ContainerPort: 8181, | ||
| }}, | ||
| }, | ||
| want: apis.ErrDisallowedFields("ports"), | ||
| want: &apis.FieldError{ | ||
| Message: "More than one container port is set", | ||
| Paths: []string{"ports"}, | ||
| Details: "Only a single port is allowed", | ||
| }, | ||
| }, { | ||
| name: "has container port value too large", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| ContainerPort: 65536, | ||
| }}, | ||
| }, | ||
| want: apis.ErrOutOfBoundsValue("65536", "1", "65535", "ports.ContainerPort"), | ||
| }, { | ||
| name: "has an empty port set", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{}}, | ||
| }, | ||
| want: apis.ErrOutOfBoundsValue("0", "1", "65535", "ports.ContainerPort"), | ||
| }, { | ||
| name: "has more than one unnamed port", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| ContainerPort: 8080, | ||
| }, { | ||
| ContainerPort: 8181, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if this second ContainerPort is empty? I think it's still an error to have: Ports: []corev1.ContainerPort{{}, {}}
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have improved validation and cleaned up the tests including a test to cover empty ports in the array. I currently have it as an error, but I could also be convinced that a ContainerPorts object with a missing ContainerPort value results in the default 8080.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a ContainerPorts object with a single missing value results in the default, but two empty ContainerPorts elements suggests selecting two ports, which should still be an error. |
||
| }}, | ||
| }, | ||
| want: &apis.FieldError{ | ||
| Message: "More than one container port is set", | ||
| Paths: []string{"ports"}, | ||
| Details: "Only a single port is allowed", | ||
| }, | ||
| }, { | ||
| name: "has tcp protocol", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Protocol: corev1.ProtocolTCP, | ||
| ContainerPort: 8080, | ||
| }}, | ||
| }, | ||
| want: nil, | ||
| }, { | ||
| name: "has invalid protocol", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Protocol: "tdp", | ||
| ContainerPort: 8080, | ||
| }}, | ||
| }, | ||
| want: apis.ErrInvalidValue("tdp", "ports.Protocol"), | ||
| }, { | ||
| name: "has host port", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| HostPort: 80, | ||
| ContainerPort: 8080, | ||
| }}, | ||
| }, | ||
| want: apis.ErrDisallowedFields("ports.HostPort"), | ||
| }, { | ||
| name: "has host ip", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| HostIP: "127.0.0.1", | ||
| ContainerPort: 8080, | ||
| }}, | ||
| }, | ||
| want: apis.ErrDisallowedFields("ports.HostIP"), | ||
| }, { | ||
| name: "has invalid port name", | ||
| c: corev1.Container{ | ||
| Image: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Name: "foobar", | ||
| ContainerPort: 8080, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a test that it's fine to omit We should also raise an error if
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since I was already making changes I added errors for those fields. |
||
| }}, | ||
| }, | ||
| want: &apis.FieldError{ | ||
| Message: fmt.Sprintf("Port name %v is not allowed", "foobar"), | ||
| Paths: []string{"ports"}, | ||
| Details: "Name must be empty, or one of: 'h2c', 'http1'", | ||
| }, | ||
| }, { | ||
| name: "has volumeMounts", | ||
| c: corev1.Container{ | ||
|
|
@@ -152,17 +271,13 @@ func TestContainerValidation(t *testing.T) { | |
| name: "has numerous problems", | ||
| c: corev1.Container{ | ||
| Name: "foo", | ||
| Ports: []corev1.ContainerPort{{ | ||
| Name: "http", | ||
| ContainerPort: 8080, | ||
| }}, | ||
| VolumeMounts: []corev1.VolumeMount{{ | ||
| MountPath: "mount/path", | ||
| Name: "name", | ||
| }}, | ||
| Lifecycle: &corev1.Lifecycle{}, | ||
| }, | ||
| want: apis.ErrDisallowedFields("name", "ports", "volumeMounts", "lifecycle").Also( | ||
| want: apis.ErrDisallowedFields("name", "volumeMounts", "lifecycle").Also( | ||
| &apis.FieldError{ | ||
| Message: "Failed to parse image reference", | ||
| Paths: []string{"image"}, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,17 +48,6 @@ var ( | |
| MountPath: "/var/log", | ||
| } | ||
|
|
||
| userPorts = []corev1.ContainerPort{{ | ||
| Name: userPortName, | ||
| ContainerPort: int32(userPort), | ||
| }} | ||
|
|
||
| // Expose containerPort as env PORT. | ||
| userEnv = corev1.EnvVar{ | ||
| Name: userPortEnvName, | ||
| Value: strconv.Itoa(userPort), | ||
| } | ||
|
|
||
| userResources = corev1.ResourceRequirements{ | ||
| Requests: corev1.ResourceList{ | ||
| corev1.ResourceCPU: userContainerCPU, | ||
|
|
@@ -79,7 +68,7 @@ var ( | |
| } | ||
| ) | ||
|
|
||
| func rewriteUserProbe(p *corev1.Probe) { | ||
| func rewriteUserProbe(p *corev1.Probe, userPort int) { | ||
| if p == nil { | ||
| return | ||
| } | ||
|
|
@@ -123,19 +112,24 @@ func makePodSpec(rev *v1alpha1.Revision, loggingConfig *logging.Config, observab | |
| // If client provides for some resources, override default values | ||
| applyDefaultResources(userResources, &userContainer.Resources) | ||
|
|
||
| userContainer.Ports = userPorts | ||
| userContainer.VolumeMounts = append(userContainer.VolumeMounts, varLogVolumeMount) | ||
| userContainer.Lifecycle = userLifecycle | ||
| userContainer.Env = append(userContainer.Env, userEnv) | ||
| userPort := getUserPort(rev) | ||
| userPortInt := int(userPort) | ||
| userPortStr := strconv.Itoa(userPortInt) | ||
| // Replacement is safe as only up to a single port is allowed on the Revision | ||
| userContainer.Ports = buildContainerPorts(userPort) | ||
| userContainer.Env = append(userContainer.Env, buildUserPortEnv(userPortStr)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to remove any previous env var entries for PORT here?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And/or prefer the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This currently mimics existing behavior, and I think I am okay with the behavior. Both a user specified PORT and the auto-populated port will be available from the API server when querying the pod or deployment, but the PORT we set should take effect inside the container due to environment variable ordering. I can't think of a case where you want the environment variable in the container to not be the port where the queue-proxy is sending traffic.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My question was what happens if I have the following in my spec: spec:
...
containerPort:
name: http1
env:
- name: PORT
value: 8888I'm worried that the Deployment pod will look like: spec:
...
containerPort:
name: http1
port: 8080
env:
- name: PORT
value: 8888
- name: PORT
value: 8080
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the above example you cannot have a name without a containerPort which should prevent some confusion. However, the bottom part of the example is still possible to have duplicate ports if the user specified an environment variable named PORT. Whatever we do I would like to have consistent behavior with all environment variables set by Knative ( https://github.com/knative/serving/blob/master/docs/runtime-contract.md#process ). Lets chat about the behavior we want, and I will open up a new issue to change if needed. |
||
| userContainer.Env = append(userContainer.Env, getKnativeEnvVar(rev)...) | ||
|
|
||
| // Prefer imageDigest from revision if available | ||
| if rev.Status.ImageDigest != "" { | ||
| userContainer.Image = rev.Status.ImageDigest | ||
| } | ||
|
|
||
| // If the client provides probes, we should fill in the port for them. | ||
| rewriteUserProbe(userContainer.ReadinessProbe) | ||
| rewriteUserProbe(userContainer.LivenessProbe) | ||
| rewriteUserProbe(userContainer.ReadinessProbe, userPortInt) | ||
| rewriteUserProbe(userContainer.LivenessProbe, userPortInt) | ||
|
|
||
| revisionTimeout := int64(rev.Spec.TimeoutSeconds.Duration.Seconds()) | ||
|
|
||
|
|
@@ -158,6 +152,30 @@ func makePodSpec(rev *v1alpha1.Revision, loggingConfig *logging.Config, observab | |
| return podSpec | ||
| } | ||
|
|
||
| func getUserPort(rev *v1alpha1.Revision) int32 { | ||
|
dgerd marked this conversation as resolved.
|
||
| if len(rev.Spec.Container.Ports) == 1 { | ||
| return rev.Spec.Container.Ports[0].ContainerPort | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a TODO to extract EXPOSE information from the container?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
| //TODO(#2258): Use container EXPOSE metadata from image before falling back to default value | ||
|
|
||
| return v1alpha1.DefaultUserPort | ||
| } | ||
|
|
||
| func buildContainerPorts(userPort int32) []corev1.ContainerPort { | ||
|
dgerd marked this conversation as resolved.
|
||
| return []corev1.ContainerPort{{ | ||
| Name: v1alpha1.UserPortName, | ||
| ContainerPort: userPort, | ||
| }} | ||
| } | ||
|
|
||
| func buildUserPortEnv(userPort string) corev1.EnvVar { | ||
| return corev1.EnvVar{ | ||
| Name: userPortEnvName, | ||
| Value: userPort, | ||
| } | ||
| } | ||
|
|
||
| func MakeDeployment(rev *v1alpha1.Revision, | ||
| loggingConfig *logging.Config, networkConfig *config.Network, observabilityConfig *config.Observability, | ||
| autoscalerConfig *autoscaler.Config, controllerConfig *config.Controller) *appsv1.Deployment { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.