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 }