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

Fix a small regression in Service updates #104601

Merged

Conversation

thockin
Copy link
Member

@thockin thockin commented Aug 26, 2021

Thanks to @liggitt for spotting this one ex post facto.

Prior to 1.22 a user could change NodePort values within a service during an update, and the apiserver would allocate values for any that were not specified.

Consider a YAML like:

apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  type: NodePort
  ports:
  - name: p
    port: 80
  - name: q
    port: 81
  selector:
    app: foo

When this is created, nodeport values will be allocated for each port. Something like:

apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  clusterIP: 10.0.149.11
  type: NodePort
  ports:
  - name: p
    nodePort: 30872
    port: 80
    protocol: TCP
    targetPort: 9376
  - name: q
    nodePort: 31310
    port: 81
    protocol: TCP
    targetPort: 81
  selector:
    app: foo

If the user PUTs (kubectl replace) the original YAML, we would see that .nodePort = 0, and allocate new ports. This was ugly at best.

In 1.22 we fixed this (#103532) to not allocate new values if we still had the old values, but instead re-assign them. Net new ports would still be seen as .nodePort = 0 and so new allocations would be made.

This broke a corner case as follows:

Prior to 1.22, the user could PUT this YAML:

apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  type: NodePort
  ports:
  - name: p
    nodePort: 31310 # note this is the `q` value
    port: 80
  - name: q
    # note this nodePort is not specified
    port: 81
  selector:
    app: foo

The p port would take the q port's value. The q port would be seen as .nodePort = 0 and a new value allocated. In 1.22 this results in an error (duplicate value in p and q).

This is VERY minor but it is an API regression, which we try to avoid, and the fix is not too horrible.

This commit adds more robust testing of this logic.

/kind bug
/kind api-change
/kind regression

Fix a small regression in 1.22 in Service updates. The circumstances are so unlikely that probably nobody would ever hit it.

xref https://github.com/kubernetes/kubernetes/pull/103532/files#r694831725

@thockin thockin added the sig/network Categorizes an issue or PR as relevant to SIG Network. label Aug 26, 2021
@k8s-ci-robot k8s-ci-robot added kind/bug Categorizes issue or PR as related to a bug. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/regression Categorizes issue or PR as related to a regression from a prior release. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Aug 26, 2021
@k8s-ci-robot
Copy link
Contributor

@thockin: This issue is currently awaiting triage.

If a SIG or subproject determines this is a relevant issue, they will accept it by applying the triage/accepted label and provide further guidance.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added needs-priority Indicates a PR lacks a `priority/foo` label and requires one. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Aug 26, 2021
@thockin thockin added priority/backlog Higher priority than priority/awaiting-more-evidence. triage/accepted Indicates an issue or PR is ready to be actively worked on. and removed needs-priority Indicates a PR lacks a `priority/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Aug 26, 2021
@k8s-triage-robot
Copy link

This PR may require API review.

If so, when the changes are ready, complete the pre-review checklist and request an API review.

Status of requested reviews is tracked in the API Review project.


// Build a set of all the ports in oldSvc that are also in newSvc. We know
// we can't patch these values.
used := nodePortsUsed(oldSvc).Intersection(nodePortsUsed(newSvc))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jordan's example considers healthCheckNodePort too https://github.com/kubernetes/kubernetes/pull/103532/files#r694831725

If the user sets a new Nodeport value that matches the current allocated HealthCheckNodePort, should we change the HealthCheckNodePort?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the tests *swap-port-with-hcnp consider that should fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that has always failed, since the node port allocate step happens before the HCNP deallocate. I am fine with that continuing to fail, and honestly, letting nodeports fail isn't SO bad, but it is a breakage in 22 vs 21.

@aojea
Copy link
Member

aojea commented Aug 26, 2021

I have doubts this is a regression https://github.com/kubernetes/kubernetes/pull/103532/files#r696807794 , technically for nodeport it is, but for the user this update never worked.

quoting the comment here:

reproducer as integration test https://github.com/kubernetes/kubernetes/compare/master...aojea:svc_carry_node_ports_test?expand=1

in 1.22

/home/aojea/go/src/k8s.io/kubernetes/test/integration/service/update_test.go:81: Error updating test service: Service "test-123" is invalid: [metadata.resourceVersion: Invalid value: "": must be specified for an update, spec.ports[2].nodePort: Duplicate value: 30553]

in 1.21.2

nodeport_test.go:81: Error updating test service: Service "test-123" is invalid: [metadata.resourceVersion: Invalid value: "": must be specified for an update, spec.clusterIPs[0]: Invalid value: []string(nil): primary clusterIP can not be unset, spec.ipFamilies[0]: Invalid value: []core.IPFamily(nil): primary ipFamily can not be unset]
so, the nodeport behavior changed, no argument about that, however, the error was masked by the clusterIP.

If we can argument that there is no regression for the user (it fails one way or the other), can we go with the port conflict approach and protect users of making weird things (and remove more complex logic in the API) sweat_smile ?

@liggitt
Copy link
Member

liggitt commented Aug 26, 2021

The failure based on the missing cluster IP with my example is orthogonal... a user successfully making use of Replace with services in 1.21 would have had to be setting/preserving the cluster IP somehow. I updated the example in my comment to make that clear.

I agree this is a small issue (the user steps to hit it are unlikely), but I am constantly amazed by the ... creativity of API use people do to make things work, and if we can avoid introducing a new failure, I think we should do so.

@aojea
Copy link
Member

aojea commented Aug 26, 2021

you are right , the patch was already broken but the replace worked as you are describing

$ kubectl apply -f svc_2.yml 
The Service "test" is invalid: spec.ports[2].nodePort: Duplicate value: 31289
$ kubectl replace -f svc_2.yml 
service/test replaced

@aojea
Copy link
Member

aojea commented Aug 26, 2021

LGTM, just the tests questions
defer to @khenidak

@thockin thockin force-pushed the patchAllocatedValues_port_reuse branch from f1099a0 to f8efea2 Compare August 26, 2021 19:35
Copy link
Contributor

@khenidak khenidak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment. One question. But can merge as is.

@@ -622,48 +622,210 @@ func TestServiceRegistryUpdate(t *testing.T) {
}

func TestServiceRegistryUpdateUnspecifiedAllocations(t *testing.T) {
type proof func(t *testing.T, s *api.Service)
prove := func(proofs ...proof) []proof {
return proofs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low pri

on all proves ( don't think we should fix but we should ack it). idx is never tested against len(<the thing we want to test>). This could yield into undesirable error, specially when i think these should be part of make.go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The good news is that they will panic if wrong.

I agree finding a way to re-use these will be nice - as a followup to the mega PR :)

pkg/registry/core/service/storage/rest_test.go Outdated Show resolved Hide resolved
Prior to 1.22 a user could change NodePort values within a service
during an update, and the apiserver would allocate values for any that
were not specified.

Consider a YAML like:

```
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  type: NodePort
  ports:
  - name: p
    port: 80
  - name: q
    port: 81
  selector:
    app: foo
```

When this is created, nodeport values will be allocated for each port.
Something like:

```
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  clusterIP: 10.0.149.11
  type: NodePort
  ports:
  - name: p
    nodePort: 30872
    port: 80
    protocol: TCP
    targetPort: 9376
  - name: q
    nodePort: 31310
    port: 81
    protocol: TCP
    targetPort: 81
  selector:
    app: foo
```

If the user PUTs (kubectl replace) the original YAML, we would see that
`.nodePort = 0`, and allocate new ports.  This was ugly at best.

In 1.22 we fixed this to not allocate new values if we still had the old
values, but instead re-assign them.  Net new ports would still be seen
as `.nodePort = 0` and so new allocations would be made.

This broke a corner case as follows:

Prior to 1.22, the user could PUT this YAML:

```
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  type: NodePort
  ports:
  - name: p
    nodePort: 31310 # note this is the `q` value
    port: 80
  - name: q
    # note this nodePort is not specified
    port: 81
  selector:
    app: foo
```

The `p` port would take the `q` port's value.  The `q` port would be
seen as `.nodePort = 0` and a new value allocated.  In 1.22 this results
in an error (duplicate value in `p` and `q`).

This is VERY minor but it is an API regression, which we try to avoid,
and the fix is not too horrible.

This commit adds more robust testing of this logic.
@thockin thockin force-pushed the patchAllocatedValues_port_reuse branch from f8efea2 to 73503a4 Compare August 30, 2021 19:42
@thockin
Copy link
Member Author

thockin commented Aug 30, 2021

small comment cleanup and rebase

@khenidak
Copy link
Contributor

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 30, 2021
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: khenidak, thockin

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@aojea
Copy link
Member

aojea commented Aug 30, 2021

/retest

@k8s-ci-robot k8s-ci-robot merged commit bb9e89d into kubernetes:master Aug 30, 2021
@k8s-ci-robot k8s-ci-robot added this to the v1.23 milestone Aug 30, 2021
k8s-ci-robot added a commit that referenced this pull request Sep 6, 2021
…532-#104601-upstream-release-1.22

Automated cherry pick of #103532: Service: Fix semantics for Update wrt allocations
#104601: Fix a small regression in Service updates
k8s-ci-robot added a commit that referenced this pull request Sep 10, 2021
…532-#104601-upstream-release-1.20

Automated cherry pick of #103532: Service: Fix semantics for Update wrt allocations
#104601: Fix a small regression in Service updates
k8s-ci-robot added a commit that referenced this pull request Sep 10, 2021
…532-#104601-upstream-release-1.21

Automated cherry pick of #103532: Service: Fix semantics for Update wrt allocations
#104601: Fix a small regression in Service updates
@thockin thockin deleted the patchAllocatedValues_port_reuse branch July 6, 2022 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/bug Categorizes issue or PR as related to a bug. kind/regression Categorizes issue or PR as related to a regression from a prior release. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/backlog Higher priority than priority/awaiting-more-evidence. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/network Categorizes an issue or PR as relevant to SIG Network. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants