Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/generated/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,9 @@ minReplicas: 3

**Enabled by default**: Yes

**Description**: Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity, to ensure that the orchestrator attempts to schedule replicas on different nodes.
**Description**: Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity or topology spread constraints, to ensure that the orchestrator attempts to schedule replicas on different nodes.

**Remediation**: Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details.
**Remediation**: Specify anti-affinity or topology spread constraints in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Using topologySpreadConstraints, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity and https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ for details.

**Template**: [anti-affinity](templates.md#anti-affinity-not-specified)

Expand Down
9 changes: 6 additions & 3 deletions pkg/builtinchecks/yamls/no-anti-affinity.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: "no-anti-affinity"
description: "Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity, to ensure that the orchestrator attempts to schedule replicas on different nodes."
description: "Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity or topology spread constraints, to ensure that the orchestrator attempts to schedule replicas on different nodes."
remediation: >-
Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes.
Specify anti-affinity or topology spread constraints in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes.
Using podAntiAffinity, specify a labelSelector that matches pods for the deployment,
and set the topologyKey to kubernetes.io/hostname.
Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details.
Using topologySpreadConstraints, specify a labelSelector that matches pods for the deployment,
and set the topologyKey to kubernetes.io/hostname.
Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
and https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ for details.
scope:
objectKinds:
- DeploymentLike
Expand Down
18 changes: 18 additions & 0 deletions pkg/templates/antiaffinity/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func init() {
return nil
}
namespace := object.K8sObject.GetNamespace()
if topologySpreadConstraintsMatchAgainstNodes(podTemplateSpec.Spec.TopologySpreadConstraints,
podTemplateSpec.Labels, topologyKeyMatcher) {
return nil
}
affinity := podTemplateSpec.Spec.Affinity
// Short-circuit if no affinity rule is specified within the pod spec.
if affinity == nil || affinity.PodAntiAffinity == nil {
Expand Down Expand Up @@ -108,6 +112,20 @@ func init() {
})
}

func topologySpreadConstraintsMatchAgainstNodes(topologySpreadConstraints []coreV1.TopologySpreadConstraint,
podLabels map[string]string, topologyKeyMatcher func(string) bool) bool {
for _, constraint := range topologySpreadConstraints {
labelSelector, err := metaV1.LabelSelectorAsSelector(constraint.LabelSelector)
if err != nil {
continue
}
if topologyKeyMatcher(constraint.TopologyKey) && labelSelector.Matches(labels.Set(podLabels)) {
return true
}
}
return false
}
Comment thread
mwozniakk marked this conversation as resolved.

func validateAffinityTermMatchesAgainstNodes(affinityTerm coreV1.PodAffinityTerm, podNamespace string,
podLabels map[string]string, topologyKeyMatcher func(string) bool) error {
// If namespaces is not specified in the affinity term, that means the affinity term implicitly applies to
Expand Down
83 changes: 83 additions & 0 deletions pkg/templates/antiaffinity/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ func (s *AntiAffinityTestSuite) addDeploymentWithAntiAffinity(name string, repli
})
}

func (s *AntiAffinityTestSuite) addDeploymentWithTopologySpreadConstraint(name string, replicas int32, topologyKey string,
labelName string) {
s.addDeploymentWithReplicas(name, replicas)
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
deployment.Spec.Template.Labels = map[string]string{"app": name}
deployment.Spec.Template.Spec.TopologySpreadConstraints = []v1.TopologySpreadConstraint{
{
MaxSkew: 1,
TopologyKey: topologyKey,
WhenUnsatisfiable: v1.DoNotSchedule,
LabelSelector: &metaV1.LabelSelector{
MatchLabels: map[string]string{"app": labelName},
},
},
}
})
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
func (s *AntiAffinityTestSuite) addDeploymentWithEmptyAntiAffinity(name string, replicas int32) {
s.addDeploymentWithReplicas(name, replicas)
s.ctx.ModifyDeployment(s.T(), name, func(deployment *appsV1.Deployment) {
Expand Down Expand Up @@ -223,3 +241,68 @@ func (s *AntiAffinityTestSuite) TestWithAntiAffinity() {
},
})
}

func (s *AntiAffinityTestSuite) TestWithTopologySpreadConstraints() {
const (
kubernetesIOHostnameDepName = "topology-spread-kubernetes-io-hostname"
otherValidKeyDepName = "topology-spread-other-valid-key"
weirdKeyDepName = "topology-spread-weird-key"
nonMatchingLabelSelectors = "topology-spread-non-matching-label-selector"
)
s.addDeploymentWithTopologySpreadConstraint(kubernetesIOHostnameDepName, 2, "kubernetes.io/hostname",
kubernetesIOHostnameDepName)
s.addDeploymentWithTopologySpreadConstraint(otherValidKeyDepName, 3, "other.valid/key", otherValidKeyDepName)
s.addDeploymentWithTopologySpreadConstraint(weirdKeyDepName, 4, "weird/key", weirdKeyDepName)
s.addDeploymentWithTopologySpreadConstraint(nonMatchingLabelSelectors, 4, "kubernetes.io/hostname",
"non-matching")

s.Validate(s.ctx, []templates.TestCase{
{
Param: params.Params{
MinReplicas: 2,
},
Diagnostics: map[string][]diagnostic.Diagnostic{
otherValidKeyDepName: {
{Message: "object has 3 replicas but does not specify inter pod anti-affinity"},
},
weirdKeyDepName: {
{Message: "object has 4 replicas but does not specify inter pod anti-affinity"},
},
nonMatchingLabelSelectors: {
{Message: "object has 4 replicas but does not specify inter pod anti-affinity"},
},
},
ExpectInstantiationError: false,
},
{
Param: params.Params{
MinReplicas: 2,
TopologyKey: "other.valid/key",
},
Diagnostics: map[string][]diagnostic.Diagnostic{
kubernetesIOHostnameDepName: {
{Message: "object has 2 replicas but does not specify inter pod anti-affinity"},
},
weirdKeyDepName: {
{Message: "object has 4 replicas but does not specify inter pod anti-affinity"},
},
nonMatchingLabelSelectors: {
{Message: "object has 4 replicas but does not specify inter pod anti-affinity"},
},
},
ExpectInstantiationError: false,
},
{
Param: params.Params{
MinReplicas: 2,
TopologyKey: ".+",
},
Diagnostics: map[string][]diagnostic.Diagnostic{
nonMatchingLabelSelectors: {
{Message: "object has 4 replicas but does not specify inter pod anti-affinity"},
},
},
ExpectInstantiationError: false,
},
})
}
Loading