-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add check to enforce that objects with multiple replicas use inter-po…
…d anti affinity (#54)
- Loading branch information
Showing
10 changed files
with
219 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
name: "no-anti-affinity" | ||
description: "Alert on deployments with multiple replicas that don't specify inter pod anti-affinity to ensure that the orchestrator attempts to schedule replicas on different nodes" | ||
remediation: >- | ||
Specify anti-affinity in your pod spec to ensure that the orchestrator attempts to schedule replicas on different nodes. | ||
You can do this by using podAntiAffinity, specifying a labelSelector that matches pods of this deployment, | ||
and setting the topologyKey to kubernetes.io/hostname. | ||
See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for more details. | ||
scope: | ||
objectKinds: | ||
- DeploymentLike | ||
template: "anti-affinity" | ||
params: | ||
minReplicas: 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
internal/templates/antiaffinity/internal/params/gen-params.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package params | ||
|
||
// Params represents the params accepted by this template. | ||
type Params struct { | ||
|
||
// The minimum number of replicas a deployment must have before anti-affinity is enforced on it | ||
MinReplicas int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package antiaffinity | ||
|
||
import ( | ||
"fmt" | ||
|
||
"golang.stackrox.io/kube-linter/internal/check" | ||
"golang.stackrox.io/kube-linter/internal/diagnostic" | ||
"golang.stackrox.io/kube-linter/internal/extract" | ||
"golang.stackrox.io/kube-linter/internal/lintcontext" | ||
"golang.stackrox.io/kube-linter/internal/objectkinds" | ||
"golang.stackrox.io/kube-linter/internal/templates" | ||
"golang.stackrox.io/kube-linter/internal/templates/antiaffinity/internal/params" | ||
coreV1 "k8s.io/api/core/v1" | ||
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/labels" | ||
) | ||
|
||
func init() { | ||
templates.Register(check.Template{ | ||
HumanName: "Anti affinity not specified", | ||
Key: "anti-affinity", | ||
Description: "Flag objects with multiple replicas but inter-pod anti affinity not specified in the pod template spec", | ||
SupportedObjectKinds: check.ObjectKindsDesc{ | ||
ObjectKinds: []string{objectkinds.DeploymentLike}, | ||
}, | ||
Parameters: params.ParamDescs, | ||
ParseAndValidateParams: params.ParseAndValidate, | ||
Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) { | ||
return func(_ *lintcontext.LintContext, object lintcontext.Object) []diagnostic.Diagnostic { | ||
replicas, found := extract.Replicas(object.K8sObject) | ||
if !found { | ||
return nil | ||
} | ||
if int(replicas) < p.MinReplicas { | ||
return nil | ||
} | ||
podTemplateSpec, hasPods := extract.PodTemplateSpec(object.K8sObject) | ||
if !hasPods { | ||
return nil | ||
} | ||
if affinity := podTemplateSpec.Spec.Affinity; affinity != nil && affinity.PodAntiAffinity != nil { | ||
preferredAffinity := affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution | ||
requiredAffinity := affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution | ||
for _, preferred := range preferredAffinity { | ||
if affinityTermMatchesLabelsAgainstNodes(preferred.PodAffinityTerm, podTemplateSpec.Namespace, podTemplateSpec.Labels) { | ||
return nil | ||
} | ||
} | ||
for _, required := range requiredAffinity { | ||
if affinityTermMatchesLabelsAgainstNodes(required, podTemplateSpec.Namespace, podTemplateSpec.Labels) { | ||
return nil | ||
} | ||
} | ||
} | ||
return []diagnostic.Diagnostic{{Message: fmt.Sprintf("object has %d replicas but does not specify inter pod anti-affinity", replicas)}} | ||
}, nil | ||
}), | ||
}) | ||
} | ||
|
||
func affinityTermMatchesLabelsAgainstNodes(affinityTerm coreV1.PodAffinityTerm, podNamespace string, podLabels map[string]string) bool { | ||
// If namespaces is not specified in the affinity term, that means the affinity term implicitly applies to the pod's namespace. | ||
if len(affinityTerm.Namespaces) > 0 { | ||
var matchingNSFound bool | ||
for _, ns := range affinityTerm.Namespaces { | ||
if ns == podNamespace { | ||
matchingNSFound = true | ||
break | ||
} | ||
} | ||
if !matchingNSFound { | ||
return false | ||
} | ||
} | ||
labelSelector, err := metaV1.LabelSelectorAsSelector(affinityTerm.LabelSelector) | ||
if err != nil { | ||
return false | ||
} | ||
if affinityTerm.TopologyKey == "kubernetes.io/hostname" && labelSelector.Matches(labels.Set(podLabels)) { | ||
return true | ||
} | ||
return false | ||
} |