@@ -23,6 +23,7 @@ package deployment
2323import (
2424 "fmt"
2525 "reflect"
26+ "sort"
2627 "time"
2728
2829 "github.com/golang/glog"
@@ -36,6 +37,7 @@ import (
3637 "k8s.io/kubernetes/pkg/controller"
3738 "k8s.io/kubernetes/pkg/controller/deployment/util"
3839 "k8s.io/kubernetes/pkg/controller/framework"
40+ "k8s.io/kubernetes/pkg/labels"
3941 "k8s.io/kubernetes/pkg/runtime"
4042 "k8s.io/kubernetes/pkg/util/metrics"
4143 utilruntime "k8s.io/kubernetes/pkg/util/runtime"
@@ -108,7 +110,7 @@ func NewDeploymentController(client clientset.Interface, resyncPeriod controller
108110 queue : workqueue .NewRateLimitingQueue (workqueue .DefaultControllerRateLimiter ()),
109111 }
110112
111- dc .dStore .Store , dc .dController = framework .NewInformer (
113+ dc .dStore .Indexer , dc .dController = framework .NewIndexerInformer (
112114 & cache.ListWatch {
113115 ListFunc : func (options api.ListOptions ) (runtime.Object , error ) {
114116 return dc .client .Extensions ().Deployments (api .NamespaceAll ).List (options )
@@ -125,6 +127,7 @@ func NewDeploymentController(client clientset.Interface, resyncPeriod controller
125127 // This will enter the sync loop and no-op, because the deployment has been deleted from the store.
126128 DeleteFunc : dc .deleteDeploymentNotification ,
127129 },
130+ cache.Indexers {cache .NamespaceIndex : cache .MetaNamespaceIndexFunc },
128131 )
129132
130133 dc .rsStore .Store , dc .rsController = framework .NewInformer (
@@ -252,7 +255,6 @@ func (dc *DeploymentController) addReplicaSet(obj interface{}) {
252255}
253256
254257// getDeploymentForReplicaSet returns the deployment managing the given ReplicaSet.
255- // TODO: Surface that we are ignoring multiple deployments for a given ReplicaSet.
256258func (dc * DeploymentController ) getDeploymentForReplicaSet (rs * extensions.ReplicaSet ) * extensions.Deployment {
257259 deployments , err := dc .dStore .GetDeploymentsForReplicaSet (rs )
258260 if err != nil || len (deployments ) == 0 {
@@ -262,8 +264,11 @@ func (dc *DeploymentController) getDeploymentForReplicaSet(rs *extensions.Replic
262264 // Because all ReplicaSet's belonging to a deployment should have a unique label key,
263265 // there should never be more than one deployment returned by the above method.
264266 // If that happens we should probably dynamically repair the situation by ultimately
265- // trying to clean up one of the controllers, for now we just return one of the two,
266- // likely randomly.
267+ // trying to clean up one of the controllers, for now we just return the older one
268+ if len (deployments ) > 1 {
269+ sort .Sort (util .BySelectorLastUpdateTime (deployments ))
270+ glog .Errorf ("user error! more than one deployment is selecting replica set %s/%s with labels: %#v, returning %s/%s" , rs .Namespace , rs .Name , rs .Labels , deployments [0 ].Namespace , deployments [0 ].Name )
271+ }
267272 return & deployments [0 ]
268273}
269274
@@ -321,22 +326,20 @@ func (dc *DeploymentController) deleteReplicaSet(obj interface{}) {
321326 }
322327}
323328
324- // getDeploymentForPod returns the deployment managing the ReplicaSet that manages the given Pod.
325- // TODO: Surface that we are ignoring multiple deployments for a given Pod.
329+ // getDeploymentForPod returns the deployment that manages the given Pod.
330+ // If there are multiple deployments for a given Pod, only return the oldest one .
326331func (dc * DeploymentController ) getDeploymentForPod (pod * api.Pod ) * extensions.Deployment {
327- rss , err := dc .rsStore . GetPodReplicaSets (pod )
328- if err != nil {
329- glog .V (4 ).Infof ("Error: %v. No ReplicaSets found for pod %v, deployment controller will avoid syncing." , err , pod .Name )
332+ deployments , err := dc .dStore . GetDeploymentsForPod (pod )
333+ if err != nil || len ( deployments ) == 0 {
334+ glog .V (4 ).Infof ("Error: %v. No deployment found for Pod %v, deployment controller will avoid syncing." , err , pod .Name )
330335 return nil
331336 }
332- for _ , rs := range rss {
333- deployments , err := dc .dStore .GetDeploymentsForReplicaSet (& rs )
334- if err == nil && len (deployments ) > 0 {
335- return & deployments [0 ]
336- }
337+
338+ if len (deployments ) > 1 {
339+ sort .Sort (util .BySelectorLastUpdateTime (deployments ))
340+ glog .Errorf ("user error! more than one deployment is selecting pod %s/%s with labels: %#v, returning %s/%s" , pod .Namespace , pod .Name , pod .Labels , deployments [0 ].Namespace , deployments [0 ].Name )
337341 }
338- glog .V (4 ).Infof ("No deployments found for pod %v, deployment controller will avoid syncing." , pod .Name )
339- return nil
342+ return & deployments [0 ]
340343}
341344
342345// When a pod is created, ensure its controller syncs
@@ -407,15 +410,28 @@ func (dc *DeploymentController) enqueueDeployment(deployment *extensions.Deploym
407410 return
408411 }
409412
410- // TODO: Handle overlapping deployments better. Either disallow them at admission time or
411- // deterministically avoid syncing deployments that fight over ReplicaSet's. Currently, we
412- // only ensure that the same deployment is synced for a given ReplicaSet. When we
413- // periodically relist all deployments there will still be some ReplicaSet instability. One
414- // way to handle this is by querying the store for all deployments that this deployment
415- // overlaps, as well as all deployments that overlap this deployments, and sorting them.
416413 dc .queue .Add (key )
417414}
418415
416+ func (dc * DeploymentController ) markDeploymentOverlap (deployment * extensions.Deployment , withDeployment string ) (* extensions.Deployment , error ) {
417+ if deployment .Annotations [util .OverlapAnnotation ] == withDeployment {
418+ return deployment , nil
419+ }
420+ if deployment .Annotations == nil {
421+ deployment .Annotations = make (map [string ]string )
422+ }
423+ deployment .Annotations [util .OverlapAnnotation ] = withDeployment
424+ return dc .client .Extensions ().Deployments (deployment .Namespace ).Update (deployment )
425+ }
426+
427+ func (dc * DeploymentController ) clearDeploymentOverlap (deployment * extensions.Deployment ) (* extensions.Deployment , error ) {
428+ if len (deployment .Annotations [util .OverlapAnnotation ]) == 0 {
429+ return deployment , nil
430+ }
431+ delete (deployment .Annotations , util .OverlapAnnotation )
432+ return dc .client .Extensions ().Deployments (deployment .Namespace ).Update (deployment )
433+ }
434+
419435// worker runs a worker thread that just dequeues items, processes them, and marks them done.
420436// It enforces that the syncHandler is never invoked concurrently with the same key.
421437func (dc * DeploymentController ) worker () {
@@ -463,7 +479,7 @@ func (dc *DeploymentController) syncDeployment(key string) error {
463479 glog .V (4 ).Infof ("Finished syncing deployment %q (%v)" , key , time .Now ().Sub (startTime ))
464480 }()
465481
466- obj , exists , err := dc .dStore .Store .GetByKey (key )
482+ obj , exists , err := dc .dStore .Indexer .GetByKey (key )
467483 if err != nil {
468484 glog .Infof ("Unable to retrieve deployment %v from store: %v" , key , err )
469485 return err
@@ -491,6 +507,11 @@ func (dc *DeploymentController) syncDeployment(key string) error {
491507 return dc .syncStatusOnly (d )
492508 }
493509
510+ // Handle overlapping deployments by deterministically avoid syncing deployments that fight over ReplicaSets.
511+ if err = dc .handleOverlap (d ); err != nil {
512+ return err
513+ }
514+
494515 if d .Spec .Paused {
495516 return dc .sync (d )
496517 }
@@ -518,3 +539,40 @@ func (dc *DeploymentController) syncDeployment(key string) error {
518539 }
519540 return fmt .Errorf ("unexpected deployment strategy type: %s" , d .Spec .Strategy .Type )
520541}
542+
543+ // handleOverlap relists all deployment in the same namespace for overlaps, and avoid syncing
544+ // the newer overlapping ones (only sync the oldest one). New/old is determined by when the
545+ // deployment's selector is last updated.
546+ func (dc * DeploymentController ) handleOverlap (d * extensions.Deployment ) error {
547+ selector , err := unversioned .LabelSelectorAsSelector (d .Spec .Selector )
548+ if err != nil {
549+ return fmt .Errorf ("deployment %s/%s has invalid label selector: %v" , d .Namespace , d .Name , err )
550+ }
551+ deployments , err := dc .dStore .Deployments (d .Namespace ).List (labels .Everything ())
552+ if err != nil {
553+ return fmt .Errorf ("error listing deployments in namespace %s: %v" , d .Namespace , err )
554+ }
555+ overlapping := false
556+ for i := range deployments {
557+ other := & deployments [i ]
558+ if ! selector .Empty () && selector .Matches (labels .Set (other .Spec .Template .Labels )) && d .UID != other .UID {
559+ overlapping = true
560+ // We don't care if the overlapping annotation update failed or not (we don't make decision on it)
561+ d , _ = dc .markDeploymentOverlap (d , other .Name )
562+ other , _ = dc .markDeploymentOverlap (other , d .Name )
563+ // Skip syncing this one if older overlapping one is found
564+ // TODO: figure out a better way to determine which deployment to skip,
565+ // either with controller reference, or with validation.
566+ // Using oldest active replica set to determine which deployment to skip wouldn't make much difference,
567+ // since new replica set hasn't been created after selector update
568+ if util .SelectorUpdatedBefore (other , d ) {
569+ return fmt .Errorf ("found deployment %s/%s has overlapping selector with an older deployment %s/%s, skip syncing it" , d .Namespace , d .Name , other .Namespace , other .Name )
570+ }
571+ }
572+ }
573+ if ! overlapping {
574+ // We don't care if the overlapping annotation update failed or not (we don't make decision on it)
575+ d , _ = dc .clearDeploymentOverlap (d )
576+ }
577+ return nil
578+ }
0 commit comments