Skip to content

Commit 527ce9f

Browse files
Explicitly target a deployment or a statefulset
The most common source of invalid PDBs is the users creating a deployment or a statefulset and a cronjob with the same application and then simply using an application=foo selector. We can add a quick workaround that will explicitly target pods belonging to a deployment or a statefulset to avoid this situation. Signed-off-by: Alexey Ermakov <[email protected]>
1 parent 4c8af52 commit 527ce9f

File tree

2 files changed

+66
-48
lines changed

2 files changed

+66
-48
lines changed

controller.go

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ package main
22

33
import (
44
"fmt"
5+
"reflect"
56
"time"
67

78
log "github.com/sirupsen/logrus"
89
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
910
"k8s.io/apimachinery/pkg/labels"
1011
"k8s.io/apimachinery/pkg/util/intstr"
1112
"k8s.io/client-go/kubernetes"
12-
"k8s.io/client-go/pkg/api/v1"
13+
v1 "k8s.io/client-go/pkg/api/v1"
1314
"k8s.io/client-go/pkg/apis/apps/v1beta1"
1415
pv1beta1 "k8s.io/client-go/pkg/apis/policy/v1beta1"
1516
)
1617

1718
const (
1819
heritageLabel = "heritage"
1920
pdbController = "pdb-controller"
21+
22+
deploymentOwnershipLabel = "pod-template-hash"
23+
statefulsetOwnershipLabel = "statefulset.kubernetes.io/pod-name"
2024
)
2125

2226
var (
@@ -99,7 +103,7 @@ func (n *PDBController) addPDBs(namespace *v1.Namespace) error {
99103
return err
100104
}
101105

102-
addPDB := make([]interface{}, 0, len(deployments.Items)+len(statefulSets.Items))
106+
addPDB := make([]metav1.Object, 0, len(deployments.Items)+len(statefulSets.Items))
103107
removePDB := make([]pv1beta1.PodDisruptionBudget, 0, len(deployments.Items)+len(statefulSets.Items))
104108

105109
nonReadTTL := time.Now().UTC().Add(-n.nonReadyTTL)
@@ -111,7 +115,8 @@ func (n *PDBController) addPDBs(namespace *v1.Namespace) error {
111115
// ready, add one
112116
if deployment.Status.ReadyReplicas == *deployment.Spec.Replicas {
113117
if len(matchedPDBs) == 0 && *deployment.Spec.Replicas > 1 {
114-
addPDB = append(addPDB, deployment)
118+
obj := &deployment
119+
addPDB = append(addPDB, obj)
115120
}
116121
}
117122

@@ -168,7 +173,8 @@ func (n *PDBController) addPDBs(namespace *v1.Namespace) error {
168173
// ready, add one
169174
if statefulSet.Status.ReadyReplicas == *statefulSet.Spec.Replicas {
170175
if len(matchedPDBs) == 0 && *statefulSet.Spec.Replicas > 1 {
171-
addPDB = append(addPDB, statefulSet)
176+
obj := &statefulSet
177+
addPDB = append(addPDB, obj)
172178
}
173179
}
174180

@@ -215,50 +221,15 @@ func (n *PDBController) addPDBs(namespace *v1.Namespace) error {
215221

216222
// add missing PDBs
217223
for _, resource := range addPDB {
218-
maxUnavailable := intstr.FromInt(1)
219-
pdb := &pv1beta1.PodDisruptionBudget{
220-
Spec: pv1beta1.PodDisruptionBudgetSpec{
221-
MaxUnavailable: &maxUnavailable,
222-
},
223-
}
224+
var pdb *pv1beta1.PodDisruptionBudget
224225

225226
switch r := resource.(type) {
226-
case v1beta1.Deployment:
227-
if r.Labels == nil {
228-
r.Labels = make(map[string]string)
229-
}
230-
labels := r.Labels
231-
labels[heritageLabel] = pdbController
232-
pdb.Name = r.Name
233-
pdb.Namespace = r.Namespace
234-
pdb.OwnerReferences = []metav1.OwnerReference{
235-
{
236-
APIVersion: "apps/v1",
237-
Kind: "Deployment",
238-
Name: r.Name,
239-
UID: r.UID,
240-
},
241-
}
242-
pdb.Labels = labels
243-
pdb.Spec.Selector = r.Spec.Selector
244-
case v1beta1.StatefulSet:
245-
if r.Labels == nil {
246-
r.Labels = make(map[string]string)
247-
}
248-
labels := r.Labels
249-
labels[heritageLabel] = pdbController
250-
pdb.Name = r.Name
251-
pdb.Namespace = r.Namespace
252-
pdb.OwnerReferences = []metav1.OwnerReference{
253-
{
254-
APIVersion: "apps/v1",
255-
Kind: "StatefulSet",
256-
Name: r.Name,
257-
UID: r.UID,
258-
},
259-
}
260-
pdb.Labels = labels
261-
pdb.Spec.Selector = r.Spec.Selector
227+
case *v1beta1.Deployment:
228+
pdb = generatePDB(r.APIVersion, r.Kind, r, r.Spec.Selector, deploymentOwnershipLabel)
229+
case *v1beta1.StatefulSet:
230+
pdb = generatePDB(r.APIVersion, r.Kind, r, r.Spec.Selector, statefulsetOwnershipLabel)
231+
default:
232+
return fmt.Errorf("unknown type for %s/%s: %s", resource.GetNamespace(), resource.GetName(), reflect.TypeOf(r))
262233
}
263234

264235
if n.pdbNameSuffix != "" {
@@ -298,6 +269,41 @@ func (n *PDBController) addPDBs(namespace *v1.Namespace) error {
298269
return nil
299270
}
300271

272+
func generatePDB(apiVersion, kind string, object metav1.Object, selector *metav1.LabelSelector, ownershipLabel string) *pv1beta1.PodDisruptionBudget {
273+
maxUnavailable := intstr.FromInt(1)
274+
pdb := &pv1beta1.PodDisruptionBudget{
275+
Spec: pv1beta1.PodDisruptionBudgetSpec{
276+
MaxUnavailable: &maxUnavailable,
277+
},
278+
}
279+
280+
pdb.Labels = object.GetLabels()
281+
if pdb.Labels == nil {
282+
pdb.Labels = make(map[string]string)
283+
}
284+
pdb.Labels[heritageLabel] = pdbController
285+
286+
pdb.Name = object.GetName()
287+
pdb.Namespace = object.GetNamespace()
288+
pdb.OwnerReferences = []metav1.OwnerReference{
289+
{
290+
APIVersion: apiVersion,
291+
Kind: kind,
292+
Name: object.GetName(),
293+
UID: object.GetUID(),
294+
},
295+
}
296+
pdb.Spec.Selector = selector
297+
if !hasOwnershipMatchExpression(pdb.Spec.Selector) {
298+
pdb.Spec.Selector.MatchExpressions = append(pdb.Spec.Selector.MatchExpressions, metav1.LabelSelectorRequirement{
299+
Key: ownershipLabel,
300+
Operator: metav1.LabelSelectorOpExists,
301+
})
302+
}
303+
304+
return pdb
305+
}
306+
301307
// getPodsLastTransitionTime returns the latest transition time for the pod not
302308
// ready condition of all pods matched by the selector.
303309
func (n *PDBController) getPodsLastTransitionTime(namespace string, selector map[string]string) (time.Time, error) {
@@ -331,9 +337,21 @@ func (n *PDBController) getPodsLastTransitionTime(namespace string, selector map
331337
return lastTransitionTime, nil
332338
}
333339

340+
func hasOwnershipMatchExpression(selector *metav1.LabelSelector) bool {
341+
for _, expr := range selector.MatchExpressions {
342+
if expr.Operator == metav1.LabelSelectorOpExists {
343+
switch expr.Key {
344+
case deploymentOwnershipLabel, statefulsetOwnershipLabel:
345+
return true
346+
}
347+
}
348+
}
349+
return false
350+
}
351+
334352
// pdbSpecValid returns true if the PDB spec is up-to-date
335353
func pdbSpecValid(pdb pv1beta1.PodDisruptionBudget) bool {
336-
return pdb.Spec.MinAvailable == nil
354+
return pdb.Spec.MinAvailable == nil && hasOwnershipMatchExpression(pdb.Spec.Selector)
337355
}
338356

339357
// getPDBs gets matching PodDisruptionBudgets.

controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"k8s.io/apimachinery/pkg/util/intstr"
99
"k8s.io/client-go/kubernetes"
1010
"k8s.io/client-go/kubernetes/fake"
11-
"k8s.io/client-go/pkg/api/v1"
11+
v1 "k8s.io/client-go/pkg/api/v1"
1212
"k8s.io/client-go/pkg/apis/apps/v1beta1"
1313
pv1beta1 "k8s.io/client-go/pkg/apis/policy/v1beta1"
1414
)

0 commit comments

Comments
 (0)