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

Apply service changes fails when load balancer is removed #33766

Closed
jeremywadsack opened this issue Sep 29, 2016 · 62 comments · Fixed by #95196
Closed

Apply service changes fails when load balancer is removed #33766

jeremywadsack opened this issue Sep 29, 2016 · 62 comments · Fixed by #95196
Labels
area/kubectl kind/bug Categorizes issue or PR as related to a bug. lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/network Categorizes an issue or PR as relevant to SIG Network.

Comments

@jeremywadsack
Copy link

What keywords did you search in Kubernetes issues before filing this one? (If you have found any duplicates, you should instead reply there.): 'remove load balancer'


BUG REPORT

Kubernetes version (use kubectl version):

Client Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.7", GitCommit:"a2cba278cba1f6881bb0a7704d9cac6fca6ed435", GitTreeState:"clean", BuildDate:"2016-09-12T23:15:30Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.7", GitCommit:"a2cba278cba1f6881bb0a7704d9cac6fca6ed435", GitTreeState:"clean", BuildDate:"2016-09-12T23:08:43Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}

Environment:

  • Cloud provider or hardware configuration: Google Cloud (GKE)
  • OS (e.g. from /etc/os-release):
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.4.0
PRETTY_NAME="Alpine Linux v3.4"
HOME_URL="http://alpinelinux.org"
BUG_REPORT_URL="http://bugs.alpinelinux.org"
  • Kernel (e.g. uname -a):
Linux ga-redis-cache 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-2 (2016-04-08) x86_64 Linux
  • Install tools: Google Cloud SDK
  • Others:

What happened:

I have a service that I configured with an external load balancer with specific firewall rules.

apiVersion: v1
kind: Service
metadata:
  name: ga-redis-cache
  labels:
    service: redis
    purpose: cache
spec:
  ports:
  - port: 6379
    protocol: TCP
  selector:
    service: redis
    purpose: cache
  type: LoadBalancer
  loadBalancerSourceRanges:
  # IP address changed for security; real IP address is routeable
  - 192.168.1.1/32

We were ready to remove the external access so I changed the configuration to remove the load balancer:

apiVersion: v1
kind: Service
metadata:
  name: ga-redis-cache
  labels:
    service: redis
    purpose: cache
spec:
  ports:
  - port: 6379
    protocol: TCP
  selector:
    service: redis
    purpose: cache

And ran kubectl apply -f and got an error about an invalid port:

proto: no encoder for TypeMeta unversioned.TypeMeta [GetProperties]
proto: tag has too few fields: "-"
proto: no coders for struct *reflect.rtype
proto: no encoder for sec int64 [GetProperties]
proto: no encoder for nsec int32 [GetProperties]
proto: no encoder for loc *time.Location [GetProperties]
proto: no encoder for Time time.Time [GetProperties]
proto: no coders for intstr.Type
proto: no encoder for Type intstr.Type [GetProperties]
The Service "ga-redis-cache" is invalid.
spec.ports[0].nodePort: Invalid value: 30179: may not be used when `type` is 'ClusterIP'

What you expected to happen:
I expected Kubernetes to remove the load balancer from the service and convert back to internal balancing for access from the cluster only.

How to reproduce it (as minimally and precisely as possible):
See configuration files above.

Anything else do we need to know:
I tried this with another service and got repeated result, just a different port:

proto: no encoder for TypeMeta unversioned.TypeMeta [GetProperties]
proto: tag has too few fields: "-"
proto: no coders for struct *reflect.rtype
proto: no encoder for sec int64 [GetProperties]
proto: no encoder for nsec int32 [GetProperties]
proto: no encoder for loc *time.Location [GetProperties]
proto: no encoder for Time time.Time [GetProperties]
proto: no coders for intstr.Type
proto: no encoder for Type intstr.Type [GetProperties]
The Service "keylime-redis-cache" is invalid.
spec.ports[0].nodePort: Invalid value: 32537: may not be used when `type` is 'ClusterIP'
@adohe-zz
Copy link

@jeremywadsack how did you create the original service? using kubectl create or kubectl apply? Please use kubectl apply to create the original resource.

@jeremywadsack
Copy link
Author

Hi @adohe. Yes, I've always used kubectl apply for creating services.

I originally created the service without the load balancer. Then I updated it to add the load balancer and the firewall rules about a month ago.

Let me know if there are more details I can provide to help diagnose this if it's not expected behavior.

@jeremywadsack
Copy link
Author

I just tested this again, completely outside an application and reproduced the issue. My guess is that apply doesn't really support removing the load balancer, but I expected it to.

Again, using GKE

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.7", GitCommit:"a2cba278cba1f6881bb0a7704d9cac6fca6ed435", GitTreeState:"clean", BuildDate:"2016-09-12T23:15:30Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.7", GitCommit:"a2cba278cba1f6881bb0a7704d9cac6fca6ed435", GitTreeState:"clean", BuildDate:"2016-09-12T23:08:43Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}

I created a service description, t1.yml:

apiVersion: v1
kind: Service
metadata:
  name: test-redis-cache
  labels:
    app: test-app
    service: redis
spec:
  ports:
  - port: 6379
    protocol: TCP
  selector:
    app: test-app
    service: redis
  type: LoadBalancer
  loadBalancerSourceRanges:
  - 8.8.8.8/32

I then created the service:

kubectl apply -f t1.yml

Next I created t2.yml as follows (i.e. the same thing without a load balancer):

apiVersion: v1
kind: Service
metadata:
  name: test-redis-cache
  labels:
    app: test-app
    service: redis
spec:
  ports:
  - port: 6379
    protocol: TCP
  selector:
    app: test-app
    service: redis

And once the test service was up and had created the load balancer I tried removing it:

$ kubectl apply -f t2.yml 
proto: no encoder for TypeMeta unversioned.TypeMeta [GetProperties]
proto: tag has too few fields: "-"
proto: no coders for struct *reflect.rtype
proto: no encoder for sec int64 [GetProperties]
proto: no encoder for nsec int32 [GetProperties]
proto: no encoder for loc *time.Location [GetProperties]
proto: no encoder for Time time.Time [GetProperties]
proto: no coders for intstr.Type
proto: no encoder for Type intstr.Type [GetProperties]
The Service "test-redis-cache" is invalid.
spec.ports[0].nodePort: Invalid value: 32452: may not be used when `type` is 'ClusterIP'

@jeremywadsack
Copy link
Author

@adohe I just ran into this again. Any further thoughts?

I'm now running:

Client Version: version.Info{Major:"1", Minor:"4", GitVersion:"v1.4.4", GitCommit:"3b417cc4ccd1b8f38ff9ec96bb50a81ca0ea9d56", GitTreeState:"clean", BuildDate:"2016-10-21T02:48:38Z", GoVersion:"go1.7.1", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"4", GitVersion:"v1.4.5", GitCommit:"5a0a696437ad35c133c0c8493f7e9d22b0f9b81b", GitTreeState:"clean", BuildDate:"2016-10-29T01:32:42Z", GoVersion:"go1.6.3", Compiler:"gc", Platform:"linux/amd64"}

To resolve this I ended up having to delete the server and then apply the new configuration.

@mlushpenko
Copy link

Having the same issue, I use apply command to create service and deployment. Updated service to LoadBalancer type, then wanted to move back to default type and getting such error:

+ /var/jenkins_home/kubectl --token= apply -f gitlab-svc.yml --record
The Service "gitlab" is invalid: 
* spec.ports[0].nodePort: Invalid value: 24121: may not be used when `type` is 'ClusterIP'
* spec.ports[1].nodePort: Invalid value: 19679: may not be used when `type` is 'ClusterIP'
kubectl version
Client Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.5", GitCommit:"b0deb2eb8f4037421077f77cb163dbb4c0a2a9f5", GitTreeState:"clean", BuildDate:"2016-08-11T20:29:08Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.5", GitCommit:"b0deb2eb8f4037421077f77cb163dbb4c0a2a9f5", GitTreeState:"clean", BuildDate:"2016-08-11T20:21:58Z", GoVersion:"go1.6.2", Compiler:"gc", Platform:"linux/amd64"}

@bodepd
Copy link

bodepd commented Apr 17, 2017

FWIW, I ran into this as well.

@adohe-zz
Copy link

@bodepd what's your kubernetes version? kubectl version can give you such info. In the old version I think we did have such problem.

@k8s-github-robot
Copy link

@jeremywadsack There are no sig labels on this issue. Please add a sig label by:
(1) mentioning a sig: @kubernetes/sig-<team-name>-misc
(2) specifying the label manually: /sig <label>

Note: method (1) will trigger a notification to the team. You can find the team list here.

@k8s-github-robot k8s-github-robot added the needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. label May 31, 2017
@jeremywadsack
Copy link
Author

This is still an issue with the test case documented above.

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.4", GitCommit:"d6f433224538d4f9ca2f7ae19b252e6fcb66a3ae", GitTreeState:"clean", BuildDate:"2017-05-19T18:44:27Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"6", GitVersion:"v1.6.4", GitCommit:"d6f433224538d4f9ca2f7ae19b252e6fcb66a3ae", GitTreeState:"clean", BuildDate:"2017-05-19T18:33:17Z", GoVersion:"go1.7.5", Compiler:"gc", Platform:"linux/amd64"}

$ kubectl apply -f t2.yml 
The Service "test-service" is invalid: spec.ports[0].nodePort: Invalid value: 32159: may not be used when `type` is 'ClusterIP'

/sig sig-node

@0xmichalis
Copy link
Contributor

/sig network
/sig cli

@k8s-ci-robot k8s-ci-robot added sig/network Categorizes an issue or PR as relevant to SIG Network. sig/cli Categorizes an issue or PR as relevant to SIG CLI. labels Jun 24, 2017
@k8s-github-robot k8s-github-robot removed the needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. label Jun 24, 2017
@thiagoarrais
Copy link

Ran into this as well

@thockin thockin added kind/bug Categorizes issue or PR as related to a bug. and removed team/cluster (deprecated - do not use) labels Dec 13, 2017
@thockin
Copy link
Member

thockin commented Dec 13, 2017

@mengqiy a fun one - guidance on this would be helpful. We may need to change our validation or something?

@azzeddinefaik
Copy link

Same issue in here any fix ? Actually I have a service with Loadbalancer as type wish to change it to ClusterIP, I got that error :

Invalid value: 32699: may not be used when `type` is ‘ClusterIP’

@Inspiravetion
Copy link

Some help on this would be great...even mitigation steps?...I can confirm that this is still happening today

@jeremywadsack
Copy link
Author

jeremywadsack commented Jan 29, 2018 via email

@mengqiy
Copy link
Member

mengqiy commented Jan 29, 2018

The issue is caused by defaulting: API server defaults field spec.ports[0].nodePort. And this field is not managed by apply.
So when you remove type: LoadBalancer, apply doesn't remove spec.ports[0].nodePort. Then the validation will complain about that.

The following is an example after defaulting.

apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"test-app","service":"redis"},"name":"test-redis-cache","namespace":"default"},"spec":{"loadBalancerSourceRanges":["8.8.8.8/32"],"ports":[{"port":6379,"protocol":"TCP"}],"selector":{"app":"test-app","service":"redis"},"type":"LoadBalancer"}}
  creationTimestamp: 2018-01-29T18:20:07Z
  labels:
    app: test-app
    service: redis
  name: test-redis-cache
  namespace: default
  resourceVersion: "5623881"
  selfLink: /api/v1/namespaces/default/services/test-redis-cache
  uid: 08a77b02-0521-11e8-8149-42010a800002
spec:
  clusterIP: 10.0.190.43
  externalTrafficPolicy: Cluster
  loadBalancerSourceRanges:
  - 8.8.8.8/32
  ports:
  - nodePort: 31854 # this got defaulted.
    port: 6379
    protocol: TCP
    targetPort: 6379
  selector:
    app: test-app
    service: redis
  sessionAffinity: None
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 35.188.39.12

You can use kubectl edit to remove type: LoadBalancer, loadBalancerSourceRanges and spec.ports[0].nodePort. It should work.
Or you can do kubectl get-modify-kubectl replace.

@sivaprakash123
Copy link

sivaprakash123 commented Feb 15, 2018

In my case "kubectl apply -f <file.yml> --force" worked.

@stealthybox
Copy link
Member

stealthybox commented Apr 6, 2018

I'm running into this with Helm
Same use case downgrading an existing LoadBalancer Service to a ClusterIP

❯ helm upgrade --install --wait sonar-cicd charts/sonarqube --values charts/sonarqube/custom.values.yaml
Error: UPGRADE FAILED: Service "sonar-cicd-sonarqube" is invalid: spec.ports[0].nodePort: Invalid value: 32057: may not be used when `type` is 'ClusterIP'

Our solution was also to delete the old service before doing the helm upgrade.

@thockin
Copy link
Member

thockin commented Apr 9, 2018

@mengqiy @lavalamp Should we have a pattern for this? The user didn't set this field, but they need to unset it. That seems bad. If I don't force them to unset it, it implies that it is in use, but its not, even though it is allocated.

We could automatically unset it, but that is somewhat surprising if they actually intended to set it (clearing instead of getting an error). This seems like the least wrong option, but in general we do not mutate user requested fields...

@mengqiy
Copy link
Member

mengqiy commented Apr 10, 2018

cc: @apelisse @seans3

@apelisse
Copy link
Member

Agreed, it's bad. I think we could fix this by having a proper union semantic somehow (though the API might need several changes). It's always good to have new examples of how things can break. We're working on trying to fix this. Thank you!

@fejta-bot
Copy link

Issues go stale after 90d of inactivity.
Mark the issue as fresh with /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with /close.

Send feedback to sig-testing, kubernetes/test-infra and/or fejta.
/lifecycle stale

@apelisse
Copy link
Member

"Unions" is kind of stalled for now, but we've made a lot of progress on the necessary work to make this happen. I'll keep this issue posted when we make progress, thanks for your patience!

@rangapv
Copy link

rangapv commented Jun 21, 2020

Hi,

Any fix on this. I am having the same issue when i delete the LoadBalancer and re-
apply the service as ClusterIP it gives me the error ...

Error Message:
The Service "web2-service" is invalid: spec.ports[0].nodePort: Forbidden: may not be used when type is 'ClusterIP'

Thanks
Ranga

@thockin
Copy link
Member

thockin commented Jun 22, 2020 via email

@rangapv
Copy link

rangapv commented Jun 22, 2020

I did a kubectl apply -f ./web20service.yaml
However when I add a --force flag as mentioned by @sivaprakash123 ; it seems to work, I don't know if that is advisable to do .
Regards
Ranga

@lavalamp
Copy link
Member

lavalamp commented Jun 22, 2020 via email

@rangapv
Copy link

rangapv commented Jun 22, 2020

Yes , I did change the service type ... is it forbidden ...
Regards
Ranga

@lavalamp
Copy link
Member

lavalamp commented Jun 22, 2020 via email

@rangapv
Copy link

rangapv commented Jun 22, 2020

Will do! so basically follow Mengqi solution for now....
which is ..

use kubectl edit to remove type: LoadBalancer, loadBalancerSourceRanges and spec.ports[0].nodePort. It should work.
Or you can do kubectl get-modify-kubectl replace.

Regards
Ranga

@mokua
Copy link

mokua commented Sep 19, 2020

How are you applying this change?

On Sun, Jun 21, 2020, 4:04 AM rangapv @.***> wrote: Hi, Any fix on this. I am having the same issue when i delete the LoadBalancer and re- apply the service as ClusterIP it gives me the error ... Error Message: The Service "web2-service" is invalid: spec.ports[0].nodePort: Forbidden: may not be used when type is 'ClusterIP' Thanks Ranga — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#33766 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABKWAVC624EX7UZCCBAWCJDRXXSNBANCNFSM4CRI5TNQ .

This is also a problem when using terraform.

Removed the type = LoadBalancer , then applied the change and got:

Service "xxx" is invalid: spec.ports[0].nodePort: Forbidden: may not be used when type is 'ClusterIP'

@lavalamp
Copy link
Member

TF is probably triggering the same bug; you'll have to figure out how to get it to clear the nodePort fields when you make that change.

@thockin
Copy link
Member

thockin commented Sep 22, 2020 via email

@apelisse
Copy link
Member

I think the long-term plan is to use a union, and the short-term workaround is to send empty/null nodePort?

@thockin
Copy link
Member

thockin commented Sep 22, 2020 via email

@apelisse
Copy link
Member

Yeah, that's why it's long term. My understanding is that some fields only make sense for specific "discriminator" values, they really look like they should be part of a union, but that'd need unions to actually work and probably the whole service fields to be re-arranged differently. It's very very long term ;-)

Are we unhappy with the null approach for node port? Does that fail specifically because of how terraform would handle null value?

@thockin
Copy link
Member

thockin commented Sep 22, 2020 via email

@thockin
Copy link
Member

thockin commented Sep 22, 2020 via email

@apelisse
Copy link
Member

For some reason, your comment doesn't format properly, reproduced here:

diff --git a/pkg/registry/core/service/strategy.go
b/pkg/registry/core/service/strategy.go
index 4e160b3cf046..403ffc106822 100644
--- a/pkg/registry/core/service/strategy.go
+++ b/pkg/registry/core/service/strategy.go
@@ -138,11 +138,26 @@ func (svcStrategy) AllowCreateOnUpdate() bool {
 }

 func (strategy svcStrategy) ValidateUpdate(ctx context.Context, obj,
old runtime.Object) field.ErrorList {
-       allErrs :=
validation.ValidateServiceUpdate(obj.(*api.Service),
old.(*api.Service))
-       allErrs = append(allErrs,
validation.ValidateConditionalService(obj.(*api.Service),
old.(*api.Service), strategy.ipFamilies)...)
+       before := old.(*api.Service)
+       after := obj.(*api.Service)
+       if needsNodePort(before) && !needsNodePort(after) {
+               for i := range after.Spec.Ports {
+                       after.Spec.Ports[i].NodePort = 0
+               }
+       }
+       // FIXME: repeat for ClusterIP
+       allErrs := validation.ValidateServiceUpdate(after, before)
+       allErrs = append(allErrs,
validation.ValidateConditionalService(after, before,
strategy.ipFamilies)...)
        return allErrs
 }

+func needsNodePort(svc *api.Service) bool {
+       if svc.Spec.Type == api.ServiceTypeNodePort || svc.Spec.Type
== api.ServiceTypeLoadBalancer {
+               return true
+       }
+       return false
+}
+
 func (svcStrategy) AllowUnconditionalUpdate() bool {
        return true
 }

@apelisse
Copy link
Member

If I read this properly, you're modifying the object in the validation function, is that OK?

@thockin
Copy link
Member

thockin commented Sep 23, 2020 via email

@apelisse
Copy link
Member

In general we don't mutate a user-provided spec

Every instance of doing that is an apply bug waiting to happen :-)
In that specific case, it's not because people haven't applied nodePort.
Are people ever supposed to apply this field or is it strictly filled in by the system?

@thockin
Copy link
Member

thockin commented Sep 25, 2020

NodePort can be set by the user. It's rarely useful but I know people do use it.

So TL;DR:

a) we clear ports[].nodePort whenever they are not needed (even if the user set them)
b) we clear ports[
].nodePort on any update to a state when they are no longer needed (need a good hook)
c) we force users to do it
d) we leave the values allocated but don't use them (confusing!)

@khenidak because I know he loves this

@khenidak
Copy link
Contributor

khenidak commented Sep 25, 2020

It is a ux issue really. If NodePorts are to be treated like ClusterIP e.g. allocated by system or set by user then we must expect the same user to clear them as they change services to types that do not require NodePorts. a la setting None for headless or clearing ClusterIP when using ExternalName. What i am trying to say is same argument can be made for ClusterIP. But argument is not made due to the fact that ClusterIP has more visibility to end user.

I have always thought of this as clearer user intent meaning. As a user i fully understand that i am releasing critical resources that i may not get back because the system may allocate them immediately else where. The validation error protects against situations where the user expects resources to remain allocated, even when types has changed or the type change is a mistake.

fyi the above also applies on changing to require NodePort service types to ExternalName.

mutability is hard...

@thockin
Copy link
Member

thockin commented Sep 26, 2020 via email

@apelisse
Copy link
Member

What we really want to express is the
idea that we can auto-clear fields IF and ONLY IF we set them in the first
place. In other words, what is we tracked which fields were auto-assigned
and do our best to auto-clear them.

I don't know if I agree with that. I understand that this problem
is real, but I don't know if we need to generalize a category of
problem from this one specifically. Here, if we had a proper
union mechanism and this type was designed to use it, then I
don't think we would even have that discussion: when you change
the type (discriminator), all the fields that don't apply anymore
would be cleared (unless they are set by the user, in which case
it would fail validation). Wouldn't that solve this bug?

Now, the problem is real, and my solution is not so I'm happy to
keeping looking for a better workaround than "remove that value
by setting it to null even if you've never asked for it".

@yalegria
Copy link

yalegria commented Dec 1, 2020

@piotr-napadlek @jeremywadsack I had the same issue and the workaround of setting nodePort to empty works for me as well. Note that this works on a plain service yml file that I can directly edit.

However, I am using Kustomize for my orchestration setup and it seems that setting nodePort to empty, Kustomize just ignores that entry and does not include it on the build. The nodePort is missing from the generated yml file and therefore fail to get applied.

Anyone experience this as well?

@solarmosaic-kflorence
Copy link

@yalegria I have successfully worked around this issue using nodePort: null with Kustomize

@thockin
Copy link
Member

thockin commented Apr 27, 2021

FTR: This is fixed is v1.20.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/kubectl kind/bug Categorizes issue or PR as related to a bug. lifecycle/frozen Indicates that an issue or PR should not be auto-closed due to staleness. sig/cli Categorizes an issue or PR as relevant to SIG CLI. sig/network Categorizes an issue or PR as relevant to SIG Network.
Projects
None yet
Development

Successfully merging a pull request may close this issue.