Code change:
return rs.releaseClusterIPs(toRelease) } <ins> // tests if two preferred dual-stack service have matching ClusterIPFields // assumption: old service is a valid, default service (e.g., loaded from store) func isMatchingPreferDualStackClusterIPFields(oldService, service *api.Service) bool { if oldService == nil { return false } if service.Spec.IPFamilyPolicy == nil { return false } // if type mutated then it is an update // that needs to run through the entire process. if oldService.Spec.Type != service.Spec.Type { return false } // both must be type that gets an IP assigned if service.Spec.Type != api.ServiceTypeClusterIP && service.Spec.Type != api.ServiceTypeNodePort && service.Spec.Type != api.ServiceTypeLoadBalancer { return false } if oldService.Spec.Type != api.ServiceTypeClusterIP && oldService.Spec.Type != api.ServiceTypeNodePort && oldService.Spec.Type != api.ServiceTypeLoadBalancer { return false } // both must be of IPFamilyPolicy==PreferDualStack if service.Spec.IPFamilyPolicy != nil && *(service.Spec.IPFamilyPolicy) != api.IPFamilyPolicyPreferDualStack { return false } if oldService.Spec.IPFamilyPolicy != nil && *(oldService.Spec.IPFamilyPolicy) != api.IPFamilyPolicyPreferDualStack { return false } // compare ClusterIPs lengths. // due to validation. if len(service.Spec.ClusterIPs) != len(oldService.Spec.ClusterIPs) { return false } for i, ip := range service.Spec.ClusterIPs { if oldService.Spec.ClusterIPs[i] != ip { return false } } // compare IPFamilies if len(service.Spec.IPFamilies) != len(oldService.Spec.IPFamilies) { return false } for i, family := range service.Spec.IPFamilies { if oldService.Spec.IPFamilies[i] != family { return false } } // they match on // Policy: preferDualStack // ClusterIPs // IPFamilies return true } </ins> // attempts to default service ip families according to cluster configuration // while ensuring that provided families are configured on cluster. <del> func (rs *REST) tryDefaultValidateServiceClusterIPFields(service *api.Service) error { </del> <ins> func (rs *REST) tryDefaultValidateServiceClusterIPFields(oldService, service *api.Service) error { </ins> // can not do anything here if service.Spec.Type == api.ServiceTypeExternalName { return nil

Related discussion:
text: 1) start with a single stack cluster 2) create a PreferDualStack service 3) configure API server with a 2nd service CIDR 4) Confirm the above service is not dual-stack 5) Add a label to the above service 6) Observe that the service becomes dual-stack I think the correct answer is to have a "repair loop" that upgrades (and downgrades!) PreferDualStack Services to match the config. I think this is a GA blocker.
/triage accepted The current thought is: of type with are special new breed of api objects that I don't think we have any similar precedent in the apis, i could be wrong. Their behavior is tied to how the cluster is configured. Thus their behavior must change as the cluster behavior change. a way the user is stating . If it is cluster then i am good with two ClusterIPs. If it is single stack cluster then I am also good with a single ClusterIP. One can assume that if the user was ok was with getting a additional IP when possible then they must be ok with taking it away when it is no longer possible. Based on the above I believe best course of action is create a new repair loop or piggyback on any of the existing (or better: fix it in rest stack during list and get calls). The new logic takes care of matching to the .
reproducer and regression test if somebody takes this, feel free to add it
ack - Scheduled to tackle 3rd week of May. Will definitely include that patch.