github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/component/affinity_utils.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package component
    21  
    22  import (
    23  	"encoding/json"
    24  	"strings"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  
    29  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    30  	"github.com/1aal/kubeblocks/pkg/constant"
    31  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    32  )
    33  
    34  func BuildPodTopologySpreadConstraints(
    35  	cluster *appsv1alpha1.Cluster,
    36  	clusterOrCompAffinity *appsv1alpha1.Affinity,
    37  	component *SynthesizedComponent,
    38  ) []corev1.TopologySpreadConstraint {
    39  	if clusterOrCompAffinity == nil {
    40  		return nil
    41  	}
    42  
    43  	var topologySpreadConstraints []corev1.TopologySpreadConstraint
    44  
    45  	var whenUnsatisfiable corev1.UnsatisfiableConstraintAction
    46  	if clusterOrCompAffinity.PodAntiAffinity == appsv1alpha1.Required {
    47  		whenUnsatisfiable = corev1.DoNotSchedule
    48  	} else {
    49  		whenUnsatisfiable = corev1.ScheduleAnyway
    50  	}
    51  	for _, topologyKey := range clusterOrCompAffinity.TopologyKeys {
    52  		topologySpreadConstraints = append(topologySpreadConstraints, corev1.TopologySpreadConstraint{
    53  			MaxSkew:           1,
    54  			WhenUnsatisfiable: whenUnsatisfiable,
    55  			TopologyKey:       topologyKey,
    56  			LabelSelector: &metav1.LabelSelector{
    57  				MatchLabels: map[string]string{
    58  					constant.AppInstanceLabelKey:    cluster.Name,
    59  					constant.KBAppComponentLabelKey: component.Name,
    60  				},
    61  			},
    62  		})
    63  	}
    64  	return topologySpreadConstraints
    65  }
    66  
    67  func BuildAffinity(cluster *appsv1alpha1.Cluster, clusterCompSpec *appsv1alpha1.ClusterComponentSpec) *appsv1alpha1.Affinity {
    68  	affinityTopoKey := func(policyType appsv1alpha1.AvailabilityPolicyType) string {
    69  		switch policyType {
    70  		case appsv1alpha1.AvailabilityPolicyZone:
    71  			return "topology.kubernetes.io/zone"
    72  		case appsv1alpha1.AvailabilityPolicyNode:
    73  			return "kubernetes.io/hostname"
    74  		}
    75  		return ""
    76  	}
    77  	var affinity *appsv1alpha1.Affinity
    78  	if len(cluster.Spec.Tenancy) > 0 || len(cluster.Spec.AvailabilityPolicy) > 0 {
    79  		affinity = &appsv1alpha1.Affinity{
    80  			PodAntiAffinity: appsv1alpha1.Preferred,
    81  			TopologyKeys:    []string{affinityTopoKey(cluster.Spec.AvailabilityPolicy)},
    82  			Tenancy:         cluster.Spec.Tenancy,
    83  		}
    84  	}
    85  	if cluster.Spec.Affinity != nil {
    86  		affinity = cluster.Spec.Affinity
    87  	}
    88  	if clusterCompSpec.Affinity != nil {
    89  		affinity = clusterCompSpec.Affinity
    90  	}
    91  	return affinity
    92  }
    93  
    94  func BuildPodAffinity(
    95  	cluster *appsv1alpha1.Cluster,
    96  	clusterOrCompAffinity *appsv1alpha1.Affinity,
    97  	component *SynthesizedComponent,
    98  ) (*corev1.Affinity, error) {
    99  	affinity := buildNewAffinity(cluster, clusterOrCompAffinity, component)
   100  
   101  	// read data plane affinity from config and merge it
   102  	dpAffinity := new(corev1.Affinity)
   103  	if val := viper.GetString(constant.CfgKeyDataPlaneAffinity); val != "" {
   104  		if err := json.Unmarshal([]byte(val), &dpAffinity); err != nil {
   105  			return nil, err
   106  		}
   107  	}
   108  	return mergeAffinity(affinity, dpAffinity)
   109  }
   110  
   111  func buildNewAffinity(
   112  	cluster *appsv1alpha1.Cluster,
   113  	clusterOrCompAffinity *appsv1alpha1.Affinity,
   114  	component *SynthesizedComponent,
   115  ) *corev1.Affinity {
   116  	if clusterOrCompAffinity == nil {
   117  		return nil
   118  	}
   119  	affinity := new(corev1.Affinity)
   120  	// Build NodeAffinity
   121  	var matchExpressions []corev1.NodeSelectorRequirement
   122  	for key, value := range clusterOrCompAffinity.NodeLabels {
   123  		values := strings.Split(value, ",")
   124  		matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{
   125  			Key:      key,
   126  			Operator: corev1.NodeSelectorOpIn,
   127  			Values:   values,
   128  		})
   129  	}
   130  	if len(matchExpressions) > 0 {
   131  		nodeSelectorTerm := corev1.NodeSelectorTerm{
   132  			MatchExpressions: matchExpressions,
   133  		}
   134  		affinity.NodeAffinity = &corev1.NodeAffinity{
   135  			RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   136  				NodeSelectorTerms: []corev1.NodeSelectorTerm{nodeSelectorTerm},
   137  			},
   138  		}
   139  	}
   140  	// Build PodAntiAffinity
   141  	var podAntiAffinity *corev1.PodAntiAffinity
   142  	var podAffinityTerms []corev1.PodAffinityTerm
   143  	for _, topologyKey := range clusterOrCompAffinity.TopologyKeys {
   144  		podAffinityTerms = append(podAffinityTerms, corev1.PodAffinityTerm{
   145  			TopologyKey: topologyKey,
   146  			LabelSelector: &metav1.LabelSelector{
   147  				MatchLabels: map[string]string{
   148  					constant.AppInstanceLabelKey:    cluster.Name,
   149  					constant.KBAppComponentLabelKey: component.Name,
   150  				},
   151  			},
   152  		})
   153  	}
   154  	if clusterOrCompAffinity.PodAntiAffinity == appsv1alpha1.Required {
   155  		podAntiAffinity = &corev1.PodAntiAffinity{
   156  			RequiredDuringSchedulingIgnoredDuringExecution: podAffinityTerms,
   157  		}
   158  	} else {
   159  		var weightedPodAffinityTerms []corev1.WeightedPodAffinityTerm
   160  		for _, podAffinityTerm := range podAffinityTerms {
   161  			weightedPodAffinityTerms = append(weightedPodAffinityTerms, corev1.WeightedPodAffinityTerm{
   162  				Weight:          100,
   163  				PodAffinityTerm: podAffinityTerm,
   164  			})
   165  		}
   166  		podAntiAffinity = &corev1.PodAntiAffinity{
   167  			PreferredDuringSchedulingIgnoredDuringExecution: weightedPodAffinityTerms,
   168  		}
   169  	}
   170  	// Add pod PodAffinityTerm for dedicated node
   171  	if clusterOrCompAffinity.Tenancy == appsv1alpha1.DedicatedNode {
   172  		var labelSelectorReqs []metav1.LabelSelectorRequirement
   173  		labelSelectorReqs = append(labelSelectorReqs, metav1.LabelSelectorRequirement{
   174  			Key:      constant.WorkloadTypeLabelKey,
   175  			Operator: metav1.LabelSelectorOpIn,
   176  			Values:   appsv1alpha1.WorkloadTypes,
   177  		})
   178  		podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
   179  			podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, corev1.PodAffinityTerm{
   180  				TopologyKey: corev1.LabelHostname,
   181  				LabelSelector: &metav1.LabelSelector{
   182  					MatchExpressions: labelSelectorReqs,
   183  				},
   184  			})
   185  	}
   186  	affinity.PodAntiAffinity = podAntiAffinity
   187  	return affinity
   188  }
   189  
   190  // mergeAffinity merges affinity from src to dest
   191  func mergeAffinity(dest, src *corev1.Affinity) (*corev1.Affinity, error) {
   192  	if src == nil {
   193  		return dest, nil
   194  	}
   195  
   196  	if dest == nil {
   197  		return src.DeepCopy(), nil
   198  	}
   199  
   200  	rst := dest.DeepCopy()
   201  	skipPodAffinity := src.PodAffinity == nil
   202  	skipPodAntiAffinity := src.PodAntiAffinity == nil
   203  	skipNodeAffinity := src.NodeAffinity == nil
   204  
   205  	if rst.PodAffinity == nil && !skipPodAffinity {
   206  		rst.PodAffinity = src.PodAffinity
   207  		skipPodAffinity = true
   208  	}
   209  	if rst.PodAntiAffinity == nil && !skipPodAntiAffinity {
   210  		rst.PodAntiAffinity = src.PodAntiAffinity
   211  		skipPodAntiAffinity = true
   212  	}
   213  	if rst.NodeAffinity == nil && !skipNodeAffinity {
   214  		rst.NodeAffinity = src.NodeAffinity
   215  		skipNodeAffinity = true
   216  	}
   217  
   218  	// if not skip, both are not nil
   219  	if !skipPodAffinity {
   220  		rst.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
   221  			rst.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
   222  			src.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution...)
   223  
   224  		rst.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
   225  			rst.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
   226  			src.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution...)
   227  	}
   228  	if !skipPodAntiAffinity {
   229  		rst.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
   230  			rst.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
   231  			src.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution...)
   232  
   233  		rst.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
   234  			rst.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
   235  			src.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution...)
   236  	}
   237  	if !skipNodeAffinity {
   238  		rst.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
   239  			rst.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
   240  			src.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution...)
   241  
   242  		skip := src.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil
   243  		if rst.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil && !skip {
   244  			rst.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = src.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution
   245  			skip = true
   246  		}
   247  		if !skip {
   248  			rst.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms = append(
   249  				rst.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms,
   250  				src.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms...)
   251  		}
   252  	}
   253  	return rst, nil
   254  }
   255  
   256  // BuildTolerations builds tolerations from config
   257  func BuildTolerations(cluster *appsv1alpha1.Cluster, clusterCompSpec *appsv1alpha1.ClusterComponentSpec) ([]corev1.Toleration, error) {
   258  	tolerations := cluster.Spec.Tolerations
   259  	if clusterCompSpec != nil && len(clusterCompSpec.Tolerations) != 0 {
   260  		tolerations = clusterCompSpec.Tolerations
   261  	}
   262  
   263  	// build data plane tolerations from config
   264  	var dpTolerations []corev1.Toleration
   265  	if val := viper.GetString(constant.CfgKeyDataPlaneTolerations); val != "" {
   266  		if err := json.Unmarshal([]byte(val), &dpTolerations); err != nil {
   267  			return nil, err
   268  		}
   269  	}
   270  
   271  	return append(tolerations, dpTolerations...), nil
   272  }