github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/factory/builder.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 factory 21 22 import ( 23 "encoding/base64" 24 "encoding/hex" 25 "encoding/json" 26 "fmt" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 32 "github.com/google/uuid" 33 snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" 34 appsv1 "k8s.io/api/apps/v1" 35 batchv1 "k8s.io/api/batch/v1" 36 corev1 "k8s.io/api/core/v1" 37 policyv1 "k8s.io/api/policy/v1" 38 rbacv1 "k8s.io/api/rbac/v1" 39 "k8s.io/apimachinery/pkg/types" 40 "k8s.io/apimachinery/pkg/util/rand" 41 42 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 43 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 44 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 45 "github.com/1aal/kubeblocks/pkg/common" 46 cfgcm "github.com/1aal/kubeblocks/pkg/configuration/config_manager" 47 "github.com/1aal/kubeblocks/pkg/constant" 48 "github.com/1aal/kubeblocks/pkg/controller/builder" 49 "github.com/1aal/kubeblocks/pkg/controller/component" 50 "github.com/1aal/kubeblocks/pkg/controller/rsm" 51 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 52 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 53 viper "github.com/1aal/kubeblocks/pkg/viperx" 54 ) 55 56 const ( 57 VolumeName = "tls" 58 CAName = "ca.crt" 59 CertName = "tls.crt" 60 KeyName = "tls.key" 61 MountPath = "/etc/pki/tls" 62 ) 63 64 func processContainersInjection(reqCtx intctrlutil.RequestCtx, 65 cluster *appsv1alpha1.Cluster, 66 component *component.SynthesizedComponent, 67 envConfigName string, 68 podSpec *corev1.PodSpec) error { 69 for _, cc := range []*[]corev1.Container{ 70 &podSpec.Containers, 71 &podSpec.InitContainers, 72 } { 73 for i := range *cc { 74 if err := injectEnvs(cluster, component, envConfigName, &(*cc)[i]); err != nil { 75 return err 76 } 77 intctrlutil.InjectZeroResourcesLimitsIfEmpty(&(*cc)[i]) 78 } 79 } 80 return nil 81 } 82 83 func injectEnvs(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent, envConfigName string, c *corev1.Container) error { 84 // can not use map, it is unordered 85 envFieldPathSlice := []struct { 86 name string 87 fieldPath string 88 }{ 89 {name: constant.KBEnvPodName, fieldPath: "metadata.name"}, 90 {name: constant.KBEnvPodUID, fieldPath: "metadata.uid"}, 91 {name: constant.KBEnvNamespace, fieldPath: "metadata.namespace"}, 92 {name: "KB_SA_NAME", fieldPath: "spec.serviceAccountName"}, 93 {name: constant.KBEnvNodeName, fieldPath: "spec.nodeName"}, 94 {name: constant.KBEnvHostIP, fieldPath: "status.hostIP"}, 95 {name: "KB_POD_IP", fieldPath: "status.podIP"}, 96 {name: "KB_POD_IPS", fieldPath: "status.podIPs"}, 97 // TODO: need to deprecate following 98 {name: "KB_HOSTIP", fieldPath: "status.hostIP"}, 99 {name: "KB_PODIP", fieldPath: "status.podIP"}, 100 {name: "KB_PODIPS", fieldPath: "status.podIPs"}, 101 } 102 103 toInjectEnvs := make([]corev1.EnvVar, 0, len(envFieldPathSlice)+len(c.Env)) 104 for _, v := range envFieldPathSlice { 105 toInjectEnvs = append(toInjectEnvs, corev1.EnvVar{ 106 Name: v.name, 107 ValueFrom: &corev1.EnvVarSource{ 108 FieldRef: &corev1.ObjectFieldSelector{ 109 APIVersion: "v1", 110 FieldPath: v.fieldPath, 111 }, 112 }, 113 }) 114 } 115 116 var kbClusterPostfix8 string 117 if len(cluster.UID) > 8 { 118 kbClusterPostfix8 = string(cluster.UID)[len(cluster.UID)-8:] 119 } else { 120 kbClusterPostfix8 = string(cluster.UID) 121 } 122 toInjectEnvs = append(toInjectEnvs, []corev1.EnvVar{ 123 {Name: "KB_CLUSTER_NAME", Value: cluster.Name}, 124 {Name: "KB_COMP_NAME", Value: component.Name}, 125 {Name: "KB_CLUSTER_COMP_NAME", Value: cluster.Name + "-" + component.Name}, 126 {Name: "KB_CLUSTER_UID_POSTFIX_8", Value: kbClusterPostfix8}, 127 {Name: "KB_POD_FQDN", Value: fmt.Sprintf("%s.%s-headless.%s.svc", "$(KB_POD_NAME)", 128 "$(KB_CLUSTER_COMP_NAME)", "$(KB_NAMESPACE)")}, 129 }...) 130 131 if component.TLS { 132 toInjectEnvs = append(toInjectEnvs, []corev1.EnvVar{ 133 {Name: "KB_TLS_CERT_PATH", Value: MountPath}, 134 {Name: "KB_TLS_CA_FILE", Value: CAName}, 135 {Name: "KB_TLS_CERT_FILE", Value: CertName}, 136 {Name: "KB_TLS_KEY_FILE", Value: KeyName}, 137 }...) 138 } 139 140 if udeValue, ok := cluster.Annotations[constant.ExtraEnvAnnotationKey]; ok { 141 udeMap := make(map[string]string) 142 if err := json.Unmarshal([]byte(udeValue), &udeMap); err != nil { 143 return err 144 } 145 keys := make([]string, 0) 146 for k := range udeMap { 147 if k == "" || udeMap[k] == "" { 148 continue 149 } 150 keys = append(keys, k) 151 } 152 sort.Strings(keys) 153 for _, k := range keys { 154 toInjectEnvs = append(toInjectEnvs, corev1.EnvVar{ 155 Name: k, 156 Value: udeMap[k], 157 }) 158 } 159 } 160 161 // have injected variables placed at the front of the slice 162 if len(c.Env) == 0 { 163 c.Env = toInjectEnvs 164 } else { 165 c.Env = append(toInjectEnvs, c.Env...) 166 } 167 if envConfigName == "" { 168 return nil 169 } 170 c.EnvFrom = append(c.EnvFrom, corev1.EnvFromSource{ 171 ConfigMapRef: &corev1.ConfigMapEnvSource{ 172 LocalObjectReference: corev1.LocalObjectReference{ 173 Name: envConfigName, 174 }, 175 }, 176 }) 177 return nil 178 } 179 180 // BuildPersistentVolumeClaimLabels builds a pvc name label, and synchronize the labels from sts to pvc. 181 func BuildPersistentVolumeClaimLabels(component *component.SynthesizedComponent, pvc *corev1.PersistentVolumeClaim, 182 pvcTplName string) { 183 // strict args checking. 184 if pvc == nil || component == nil { 185 return 186 } 187 if pvc.Labels == nil { 188 pvc.Labels = make(map[string]string) 189 } 190 pvc.Labels[constant.VolumeClaimTemplateNameLabelKey] = pvcTplName 191 192 if component.VolumeTypes != nil { 193 for _, t := range component.VolumeTypes { 194 if t.Name == pvcTplName { 195 pvc.Labels[constant.VolumeTypeLabelKey] = string(t.Type) 196 break 197 } 198 } 199 } 200 } 201 202 func BuildCommonLabels(cluster *appsv1alpha1.Cluster, 203 component *component.SynthesizedComponent) map[string]string { 204 return map[string]string{ 205 constant.AppManagedByLabelKey: constant.AppName, 206 constant.AppNameLabelKey: component.ClusterDefName, 207 constant.AppInstanceLabelKey: cluster.Name, 208 constant.KBAppComponentLabelKey: component.Name, 209 } 210 } 211 212 func vctToPVC(vct corev1.PersistentVolumeClaimTemplate) corev1.PersistentVolumeClaim { 213 return corev1.PersistentVolumeClaim{ 214 ObjectMeta: vct.ObjectMeta, 215 Spec: vct.Spec, 216 } 217 } 218 219 func BuildSts(reqCtx intctrlutil.RequestCtx, cluster *appsv1alpha1.Cluster, 220 component *component.SynthesizedComponent, envConfigName string) (*appsv1.StatefulSet, error) { 221 commonLabels := BuildCommonLabels(cluster, component) 222 podBuilder := builder.NewPodBuilder("", ""). 223 AddLabelsInMap(commonLabels). 224 AddLabels(constant.AppComponentLabelKey, component.CompDefName). 225 AddLabels(constant.WorkloadTypeLabelKey, string(component.WorkloadType)) 226 if len(cluster.Spec.ClusterVersionRef) > 0 { 227 podBuilder.AddLabels(constant.AppVersionLabelKey, cluster.Spec.ClusterVersionRef) 228 } 229 template := corev1.PodTemplateSpec{ 230 ObjectMeta: podBuilder.GetObject().ObjectMeta, 231 Spec: *component.PodSpec, 232 } 233 stsBuilder := builder.NewStatefulSetBuilder(cluster.Namespace, cluster.Name+"-"+component.Name). 234 AddLabelsInMap(commonLabels). 235 AddLabels(constant.AppComponentLabelKey, component.CompDefName). 236 AddMatchLabelsInMap(commonLabels). 237 SetServiceName(cluster.Name + "-" + component.Name + "-headless"). 238 SetReplicas(component.Replicas). 239 SetTemplate(template) 240 241 var vcts []corev1.PersistentVolumeClaim 242 for _, vct := range component.VolumeClaimTemplates { 243 vcts = append(vcts, vctToPVC(vct)) 244 } 245 stsBuilder.SetVolumeClaimTemplates(vcts...) 246 247 if component.StatefulSetWorkload != nil { 248 podManagementPolicy, updateStrategy := component.StatefulSetWorkload.FinalStsUpdateStrategy() 249 stsBuilder.SetPodManagementPolicy(podManagementPolicy).SetUpdateStrategy(updateStrategy) 250 } 251 252 sts := stsBuilder.GetObject() 253 254 // update sts.spec.volumeClaimTemplates[].metadata.labels 255 if len(sts.Spec.VolumeClaimTemplates) > 0 && len(sts.GetLabels()) > 0 { 256 for index, vct := range sts.Spec.VolumeClaimTemplates { 257 BuildPersistentVolumeClaimLabels(component, &vct, vct.Name) 258 sts.Spec.VolumeClaimTemplates[index] = vct 259 } 260 } 261 262 if err := processContainersInjection(reqCtx, cluster, component, envConfigName, &sts.Spec.Template.Spec); err != nil { 263 return nil, err 264 } 265 return sts, nil 266 } 267 268 func buildWellKnownLabels(clusterDefName, clusterName, componentName string) map[string]string { 269 return map[string]string{ 270 constant.AppManagedByLabelKey: constant.AppName, 271 constant.AppNameLabelKey: clusterDefName, 272 constant.AppInstanceLabelKey: clusterName, 273 constant.KBAppComponentLabelKey: componentName, 274 } 275 } 276 277 func BuildRSM(reqCtx intctrlutil.RequestCtx, cluster *appsv1alpha1.Cluster, 278 component *component.SynthesizedComponent, envConfigName string) (*workloads.ReplicatedStateMachine, error) { 279 commonLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) 280 addCommonLabels := func(service *corev1.Service) { 281 if service == nil { 282 return 283 } 284 labels := service.Labels 285 if labels == nil { 286 labels = make(map[string]string, 0) 287 } 288 for k, v := range commonLabels { 289 labels[k] = v 290 } 291 labels[constant.AppComponentLabelKey] = component.CompDefName 292 service.Labels = labels 293 } 294 295 podBuilder := builder.NewPodBuilder("", ""). 296 AddLabelsInMap(commonLabels). 297 AddLabels(constant.AppComponentLabelKey, component.CompDefName). 298 AddLabels(constant.WorkloadTypeLabelKey, string(component.WorkloadType)) 299 if len(cluster.Spec.ClusterVersionRef) > 0 { 300 podBuilder.AddLabels(constant.AppVersionLabelKey, cluster.Spec.ClusterVersionRef) 301 } 302 template := corev1.PodTemplateSpec{ 303 ObjectMeta: podBuilder.GetObject().ObjectMeta, 304 Spec: *component.PodSpec, 305 } 306 307 monitorAnnotations := func() map[string]string { 308 annotations := make(map[string]string, 0) 309 falseStr := "false" 310 trueStr := "true" 311 switch { 312 case !component.Monitor.Enable: 313 annotations["monitor.kubeblocks.io/scrape"] = falseStr 314 annotations["monitor.kubeblocks.io/agamotto"] = falseStr 315 case component.Monitor.BuiltIn: 316 annotations["monitor.kubeblocks.io/scrape"] = falseStr 317 annotations["monitor.kubeblocks.io/agamotto"] = trueStr 318 default: 319 annotations["monitor.kubeblocks.io/scrape"] = trueStr 320 annotations["monitor.kubeblocks.io/path"] = component.Monitor.ScrapePath 321 annotations["monitor.kubeblocks.io/port"] = strconv.Itoa(int(component.Monitor.ScrapePort)) 322 annotations["monitor.kubeblocks.io/scheme"] = "http" 323 annotations["monitor.kubeblocks.io/agamotto"] = falseStr 324 } 325 return rsm.AddAnnotationScope(rsm.HeadlessServiceScope, annotations) 326 }() 327 rsmName := fmt.Sprintf("%s-%s", cluster.Name, component.Name) 328 rsmBuilder := builder.NewReplicatedStateMachineBuilder(cluster.Namespace, rsmName). 329 AddAnnotations(constant.KubeBlocksGenerationKey, strconv.FormatInt(cluster.Generation, 10)). 330 AddAnnotationsInMap(monitorAnnotations). 331 AddLabelsInMap(commonLabels). 332 AddLabels(constant.AppComponentLabelKey, component.CompDefName). 333 AddMatchLabelsInMap(commonLabels). 334 SetServiceName(rsmName + "-headless"). 335 SetReplicas(component.Replicas). 336 SetTemplate(template) 337 338 var vcts []corev1.PersistentVolumeClaim 339 for _, vct := range component.VolumeClaimTemplates { 340 vcts = append(vcts, vctToPVC(vct)) 341 } 342 rsmBuilder.SetVolumeClaimTemplates(vcts...) 343 344 if component.StatefulSetWorkload != nil { 345 podManagementPolicy, updateStrategy := component.StatefulSetWorkload.FinalStsUpdateStrategy() 346 rsmBuilder.SetPodManagementPolicy(podManagementPolicy).SetUpdateStrategy(updateStrategy) 347 } 348 349 service, alternativeServices := separateServices(component.Services) 350 addCommonLabels(service) 351 for i := range alternativeServices { 352 addCommonLabels(&alternativeServices[i]) 353 } 354 if service != nil { 355 rsmBuilder.SetService(service) 356 } 357 if len(alternativeServices) == 0 { 358 alternativeServices = nil 359 } 360 alternativeServices = fixService(cluster.Namespace, rsmName, component, alternativeServices...) 361 rsmBuilder.SetAlternativeServices(alternativeServices) 362 363 secretName := fmt.Sprintf("%s-conn-credential", cluster.Name) 364 credential := workloads.Credential{ 365 Username: workloads.CredentialVar{ 366 ValueFrom: &corev1.EnvVarSource{ 367 SecretKeyRef: &corev1.SecretKeySelector{ 368 LocalObjectReference: corev1.LocalObjectReference{ 369 Name: secretName, 370 }, 371 Key: constant.AccountNameForSecret, 372 }, 373 }, 374 }, 375 Password: workloads.CredentialVar{ 376 ValueFrom: &corev1.EnvVarSource{ 377 SecretKeyRef: &corev1.SecretKeySelector{ 378 LocalObjectReference: corev1.LocalObjectReference{ 379 Name: secretName, 380 }, 381 Key: constant.AccountPasswdForSecret, 382 }, 383 }, 384 }, 385 } 386 rsmBuilder.SetCredential(credential) 387 388 roles, roleProbe, membershipReconfiguration, memberUpdateStrategy := buildRoleInfo(component) 389 rsm := rsmBuilder.SetRoles(roles). 390 SetRoleProbe(roleProbe). 391 SetMembershipReconfiguration(membershipReconfiguration). 392 SetMemberUpdateStrategy(memberUpdateStrategy). 393 GetObject() 394 395 // update sts.spec.volumeClaimTemplates[].metadata.labels 396 if len(rsm.Spec.VolumeClaimTemplates) > 0 && len(rsm.GetLabels()) > 0 { 397 for index, vct := range rsm.Spec.VolumeClaimTemplates { 398 BuildPersistentVolumeClaimLabels(component, &vct, vct.Name) 399 rsm.Spec.VolumeClaimTemplates[index] = vct 400 } 401 } 402 403 if err := processContainersInjection(reqCtx, cluster, component, envConfigName, &rsm.Spec.Template.Spec); err != nil { 404 return nil, err 405 } 406 return rsm, nil 407 } 408 409 func fixService(namespace, prefix string, component *component.SynthesizedComponent, alternativeServices ...corev1.Service) []corev1.Service { 410 leaderName := getLeaderName(component) 411 for i := range alternativeServices { 412 if len(alternativeServices[i].Name) > 0 { 413 alternativeServices[i].Name = prefix + "-" + alternativeServices[i].Name 414 } 415 if len(alternativeServices[i].Namespace) == 0 { 416 alternativeServices[i].Namespace = namespace 417 } 418 if alternativeServices[i].Spec.Type == corev1.ServiceTypeLoadBalancer { 419 alternativeServices[i].Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyTypeLocal 420 } 421 if len(leaderName) > 0 { 422 selector := alternativeServices[i].Spec.Selector 423 if selector == nil { 424 selector = make(map[string]string, 0) 425 } 426 selector[constant.RoleLabelKey] = leaderName 427 alternativeServices[i].Spec.Selector = selector 428 } 429 } 430 return alternativeServices 431 } 432 433 func getLeaderName(component *component.SynthesizedComponent) string { 434 if component == nil { 435 return "" 436 } 437 switch component.WorkloadType { 438 case appsv1alpha1.Consensus: 439 if component.ConsensusSpec != nil { 440 return component.ConsensusSpec.Leader.Name 441 } 442 case appsv1alpha1.Replication: 443 return constant.Primary 444 } 445 return "" 446 } 447 448 // separateServices separates 'services' to a main service from cd and alternative services from cluster 449 func separateServices(services []corev1.Service) (*corev1.Service, []corev1.Service) { 450 if len(services) == 0 { 451 return nil, nil 452 } 453 // from component.buildComponent (which contains component.Services' building process), the first item should be the main service 454 // TODO(free6om): make two fields in component(i.e. Service and AlternativeServices) after RSM passes all testes. 455 return &services[0], services[1:] 456 } 457 458 func buildRoleInfo(component *component.SynthesizedComponent) ([]workloads.ReplicaRole, *workloads.RoleProbe, *workloads.MembershipReconfiguration, *workloads.MemberUpdateStrategy) { 459 if component.RSMSpec != nil { 460 return buildRoleInfo2(component) 461 } 462 463 var ( 464 roles []workloads.ReplicaRole 465 probe *workloads.RoleProbe 466 reconfiguration *workloads.MembershipReconfiguration 467 strategy *workloads.MemberUpdateStrategy 468 ) 469 470 handler := convertCharacterTypeToHandler(component.CharacterType, component.WorkloadType == appsv1alpha1.Consensus) 471 472 if handler != nil && component.Probes != nil && component.Probes.RoleProbe != nil { 473 probe = &workloads.RoleProbe{} 474 probe.BuiltinHandler = (*string)(handler) 475 roleProbe := component.Probes.RoleProbe 476 probe.PeriodSeconds = roleProbe.PeriodSeconds 477 probe.TimeoutSeconds = roleProbe.TimeoutSeconds 478 probe.FailureThreshold = roleProbe.FailureThreshold 479 // set to default value 480 probe.SuccessThreshold = 1 481 probe.RoleUpdateMechanism = workloads.DirectAPIServerEventUpdate 482 } 483 484 reconfiguration = nil 485 486 switch component.WorkloadType { 487 case appsv1alpha1.Consensus: 488 roles, strategy = buildRoleInfoFromConsensus(component.ConsensusSpec) 489 case appsv1alpha1.Replication: 490 roles = buildRoleInfoFromReplication() 491 reconfiguration = nil 492 strgy := workloads.SerialUpdateStrategy 493 strategy = &strgy 494 } 495 496 return roles, probe, reconfiguration, strategy 497 } 498 499 func buildRoleInfo2(component *component.SynthesizedComponent) ([]workloads.ReplicaRole, *workloads.RoleProbe, *workloads.MembershipReconfiguration, *workloads.MemberUpdateStrategy) { 500 rsmSpec := component.RSMSpec 501 return rsmSpec.Roles, rsmSpec.RoleProbe, rsmSpec.MembershipReconfiguration, rsmSpec.MemberUpdateStrategy 502 } 503 504 func buildRoleInfoFromReplication() []workloads.ReplicaRole { 505 return []workloads.ReplicaRole{ 506 { 507 Name: constant.Primary, 508 IsLeader: true, 509 CanVote: true, 510 AccessMode: workloads.ReadWriteMode, 511 }, 512 { 513 Name: constant.Secondary, 514 IsLeader: false, 515 CanVote: true, 516 AccessMode: workloads.ReadonlyMode, 517 }, 518 } 519 } 520 521 func buildRoleInfoFromConsensus(consensusSpec *appsv1alpha1.ConsensusSetSpec) ([]workloads.ReplicaRole, *workloads.MemberUpdateStrategy) { 522 if consensusSpec == nil { 523 return nil, nil 524 } 525 526 var ( 527 roles []workloads.ReplicaRole 528 strategy *workloads.MemberUpdateStrategy 529 ) 530 531 roles = append(roles, workloads.ReplicaRole{ 532 Name: consensusSpec.Leader.Name, 533 IsLeader: true, 534 CanVote: true, 535 AccessMode: workloads.AccessMode(consensusSpec.Leader.AccessMode), 536 }) 537 for _, follower := range consensusSpec.Followers { 538 roles = append(roles, workloads.ReplicaRole{ 539 Name: follower.Name, 540 IsLeader: false, 541 CanVote: true, 542 AccessMode: workloads.AccessMode(follower.AccessMode), 543 }) 544 } 545 if consensusSpec.Learner != nil { 546 roles = append(roles, workloads.ReplicaRole{ 547 Name: consensusSpec.Learner.Name, 548 IsLeader: false, 549 CanVote: false, 550 AccessMode: workloads.AccessMode(consensusSpec.Learner.AccessMode), 551 }) 552 } 553 554 strgy := workloads.MemberUpdateStrategy(consensusSpec.UpdateStrategy) 555 strategy = &strgy 556 557 return roles, strategy 558 } 559 560 func convertCharacterTypeToHandler(characterType string, isConsensus bool) *common.BuiltinHandler { 561 var handler common.BuiltinHandler 562 kind := strings.ToLower(characterType) 563 switch kind { 564 case "mysql": //nolint:goconst 565 if isConsensus { 566 handler = common.WeSQLHandler 567 } else { 568 handler = common.MySQLHandler 569 } 570 case "postgres", "postgresql": 571 handler = common.PostgresHandler 572 case "mongodb": 573 574 handler = common.MongoDBHandler 575 case "etcd": 576 handler = common.ETCDHandler 577 case "redis": 578 handler = common.RedisHandler 579 case "kafka": 580 handler = common.KafkaHandler 581 } 582 if handler != "" { 583 return &handler 584 } 585 return nil 586 } 587 588 func randomString(length int) string { 589 return rand.String(length) 590 } 591 592 func BuildConnCredential(clusterDefinition *appsv1alpha1.ClusterDefinition, cluster *appsv1alpha1.Cluster, 593 component *component.SynthesizedComponent) *corev1.Secret { 594 wellKnownLabels := buildWellKnownLabels(clusterDefinition.Name, cluster.Name, "") 595 delete(wellKnownLabels, constant.KBAppComponentLabelKey) 596 credentialBuilder := builder.NewSecretBuilder(cluster.Namespace, fmt.Sprintf("%s-conn-credential", cluster.Name)). 597 AddLabelsInMap(wellKnownLabels). 598 SetStringData(clusterDefinition.Spec.ConnectionCredential) 599 if len(clusterDefinition.Spec.Type) > 0 { 600 credentialBuilder.AddLabels("apps.kubeblocks.io/cluster-type", clusterDefinition.Spec.Type) 601 } 602 connCredential := credentialBuilder.GetObject() 603 604 if len(connCredential.StringData) == 0 { 605 return connCredential 606 } 607 608 replaceVarObjects := func(k, v *string, i int, origValue string, varObjectsMap map[string]string) { 609 toReplace := origValue 610 for j, r := range varObjectsMap { 611 replaced := strings.ReplaceAll(toReplace, j, r) 612 if replaced == toReplace { 613 continue 614 } 615 toReplace = replaced 616 // replace key 617 if i == 0 { 618 delete(connCredential.StringData, origValue) 619 *k = replaced 620 } else { 621 *v = replaced 622 } 623 } 624 } 625 626 // REVIEW: perhaps handles value replacement at `func mergeComponents` 627 replaceData := func(varObjectsMap map[string]string) { 628 copyStringData := connCredential.DeepCopy().StringData 629 for k, v := range copyStringData { 630 for i, vv := range []string{k, v} { 631 if !strings.Contains(vv, "$(") { 632 continue 633 } 634 replaceVarObjects(&k, &v, i, vv, varObjectsMap) 635 } 636 connCredential.StringData[k] = v 637 } 638 } 639 640 // get restore password if exists during recovery. 641 getRestorePassword := func() string { 642 valueString := cluster.Annotations[constant.RestoreFromBackupAnnotationKey] 643 if len(valueString) == 0 { 644 return "" 645 } 646 backupMap := map[string]map[string]string{} 647 err := json.Unmarshal([]byte(valueString), &backupMap) 648 if err != nil { 649 return "" 650 } 651 backupSource, ok := backupMap[component.Name] 652 if !ok { 653 return "" 654 } 655 password, ok := backupSource[constant.ConnectionPassword] 656 if !ok { 657 return "" 658 } 659 e := intctrlutil.NewEncryptor(viper.GetString(constant.CfgKeyDPEncryptionKey)) 660 password, _ = e.Decrypt([]byte(password)) 661 return password 662 } 663 664 // TODO: do JIT value generation for lower CPU resources 665 // 1st pass replace variables 666 uuidVal := uuid.New() 667 uuidBytes := uuidVal[:] 668 uuidStr := uuidVal.String() 669 uuidB64 := base64.RawStdEncoding.EncodeToString(uuidBytes) 670 uuidStrB64 := base64.RawStdEncoding.EncodeToString([]byte(strings.ReplaceAll(uuidStr, "-", ""))) 671 uuidHex := hex.EncodeToString(uuidBytes) 672 randomPassword := randomString(8) 673 restorePassword := getRestorePassword() 674 // check if a connection password is specified during recovery. 675 // if exists, replace the random password 676 if restorePassword != "" { 677 randomPassword = restorePassword 678 } 679 m := map[string]string{ 680 "$(RANDOM_PASSWD)": randomPassword, 681 "$(UUID)": uuidStr, 682 "$(UUID_B64)": uuidB64, 683 "$(UUID_STR_B64)": uuidStrB64, 684 "$(UUID_HEX)": uuidHex, 685 "$(SVC_FQDN)": fmt.Sprintf("%s-%s.%s.svc", cluster.Name, component.Name, cluster.Namespace), 686 "$(KB_CLUSTER_COMP_NAME)": cluster.Name + "-" + component.Name, 687 "$(HEADLESS_SVC_FQDN)": fmt.Sprintf("%s-%s-headless.%s.svc", cluster.Name, component.Name, cluster.Namespace), 688 } 689 if len(component.Services) > 0 { 690 for _, p := range component.Services[0].Spec.Ports { 691 m[fmt.Sprintf("$(SVC_PORT_%s)", p.Name)] = strconv.Itoa(int(p.Port)) 692 } 693 } 694 replaceData(m) 695 696 // 2nd pass replace $(CONN_CREDENTIAL) variables 697 m = map[string]string{} 698 for k, v := range connCredential.StringData { 699 m[fmt.Sprintf("$(CONN_CREDENTIAL).%s", k)] = v 700 } 701 replaceData(m) 702 return connCredential 703 } 704 705 func BuildPDB(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) *policyv1.PodDisruptionBudget { 706 wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) 707 return builder.NewPDBBuilder(cluster.Namespace, fmt.Sprintf("%s-%s", cluster.Name, component.Name)). 708 AddLabelsInMap(wellKnownLabels). 709 AddLabels(constant.AppComponentLabelKey, component.CompDefName). 710 AddSelectorsInMap(wellKnownLabels). 711 GetObject() 712 } 713 714 func BuildPVC(cluster *appsv1alpha1.Cluster, 715 component *component.SynthesizedComponent, 716 vct *corev1.PersistentVolumeClaimTemplate, 717 pvcKey types.NamespacedName, 718 snapshotName string) *corev1.PersistentVolumeClaim { 719 wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) 720 pvcBuilder := builder.NewPVCBuilder(pvcKey.Namespace, pvcKey.Name). 721 AddLabelsInMap(wellKnownLabels). 722 AddLabels(constant.VolumeClaimTemplateNameLabelKey, vct.Name). 723 SetAccessModes(vct.Spec.AccessModes). 724 SetResources(vct.Spec.Resources) 725 if vct.Spec.StorageClassName != nil { 726 pvcBuilder.SetStorageClass(*vct.Spec.StorageClassName) 727 } 728 if len(snapshotName) > 0 { 729 apiGroup := "snapshot.storage.k8s.io" 730 pvcBuilder.SetDataSource(corev1.TypedLocalObjectReference{ 731 APIGroup: &apiGroup, 732 Kind: "VolumeSnapshot", 733 Name: snapshotName, 734 }) 735 } 736 pvc := pvcBuilder.GetObject() 737 BuildPersistentVolumeClaimLabels(component, pvc, vct.Name) 738 return pvc 739 } 740 741 // BuildEnvConfig builds cluster component context ConfigMap object, which is to be used in workload container's 742 // envFrom.configMapRef with name of "$(cluster.metadata.name)-$(component.name)-env" pattern. 743 func BuildEnvConfig(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) *corev1.ConfigMap { 744 envData := map[string]string{} 745 // add component envs 746 if component.ComponentRefEnvs != nil { 747 for _, env := range component.ComponentRefEnvs { 748 envData[env.Name] = env.Value 749 } 750 } 751 752 wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) 753 wellKnownLabels[constant.AppComponentLabelKey] = component.CompDefName 754 return builder.NewConfigMapBuilder(cluster.Namespace, fmt.Sprintf("%s-%s-env", cluster.Name, component.Name)). 755 AddLabelsInMap(wellKnownLabels). 756 AddLabels(constant.AppConfigTypeLabelKey, "kubeblocks-env"). 757 SetData(envData). 758 GetObject() 759 } 760 761 func BuildBackup(cluster *appsv1alpha1.Cluster, 762 component *component.SynthesizedComponent, 763 backupPolicyName string, 764 backupKey types.NamespacedName, 765 backupMethod string) *dpv1alpha1.Backup { 766 return builder.NewBackupBuilder(backupKey.Namespace, backupKey.Name). 767 AddLabels(dptypes.BackupMethodLabelKey, backupMethod). 768 AddLabels(dptypes.BackupPolicyLabelKey, backupPolicyName). 769 AddLabels(constant.KBManagedByKey, "cluster"). 770 AddLabels(constant.AppNameLabelKey, component.ClusterDefName). 771 AddLabels(constant.AppInstanceLabelKey, cluster.Name). 772 AddLabels(constant.AppManagedByLabelKey, constant.AppName). 773 AddLabels(constant.KBAppComponentLabelKey, component.Name). 774 SetBackupPolicyName(backupPolicyName). 775 SetBackupMethod(backupMethod). 776 GetObject() 777 } 778 779 func BuildConfigMapWithTemplate(cluster *appsv1alpha1.Cluster, 780 component *component.SynthesizedComponent, 781 configs map[string]string, 782 cmName string, 783 configTemplateSpec appsv1alpha1.ComponentTemplateSpec) *corev1.ConfigMap { 784 wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name) 785 wellKnownLabels[constant.AppComponentLabelKey] = component.CompDefName 786 return builder.NewConfigMapBuilder(cluster.Namespace, cmName). 787 AddLabelsInMap(wellKnownLabels). 788 AddLabels(constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType). 789 AddLabels(constant.CMTemplateNameLabelKey, configTemplateSpec.TemplateRef). 790 AddAnnotations(constant.DisableUpgradeInsConfigurationAnnotationKey, strconv.FormatBool(false)). 791 SetData(configs). 792 GetObject() 793 } 794 795 func BuildCfgManagerContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, component *component.SynthesizedComponent) (*corev1.Container, error) { 796 var env []corev1.EnvVar 797 env = append(env, corev1.EnvVar{ 798 Name: "CONFIG_MANAGER_POD_IP", 799 ValueFrom: &corev1.EnvVarSource{ 800 FieldRef: &corev1.ObjectFieldSelector{ 801 APIVersion: "v1", 802 FieldPath: "status.podIP", 803 }, 804 }, 805 }) 806 if len(sidecarRenderedParam.CharacterType) > 0 { 807 env = append(env, corev1.EnvVar{ 808 Name: "DB_TYPE", 809 Value: sidecarRenderedParam.CharacterType, 810 }) 811 } 812 if sidecarRenderedParam.CharacterType == "mysql" { 813 env = append(env, corev1.EnvVar{ 814 Name: "MYSQL_USER", 815 ValueFrom: &corev1.EnvVarSource{ 816 SecretKeyRef: &corev1.SecretKeySelector{ 817 Key: "username", 818 LocalObjectReference: corev1.LocalObjectReference{Name: sidecarRenderedParam.SecreteName}, 819 }, 820 }, 821 }, 822 corev1.EnvVar{ 823 Name: "MYSQL_PASSWORD", 824 ValueFrom: &corev1.EnvVarSource{ 825 SecretKeyRef: &corev1.SecretKeySelector{ 826 Key: "password", 827 LocalObjectReference: corev1.LocalObjectReference{Name: sidecarRenderedParam.SecreteName}, 828 }, 829 }, 830 }, 831 corev1.EnvVar{ 832 Name: "DATA_SOURCE_NAME", 833 Value: "$(MYSQL_USER):$(MYSQL_PASSWORD)@(localhost:3306)/", 834 }, 835 ) 836 } 837 containerBuilder := builder.NewContainerBuilder(sidecarRenderedParam.ManagerName). 838 AddCommands("env"). 839 AddArgs("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$(TOOLS_PATH)"). 840 AddArgs("/bin/reloader"). 841 AddArgs(sidecarRenderedParam.Args...). 842 AddEnv(env...). 843 SetImage(sidecarRenderedParam.Image). 844 SetImagePullPolicy(corev1.PullIfNotPresent). 845 AddVolumeMounts(sidecarRenderedParam.Volumes...) 846 if sidecarRenderedParam.ShareProcessNamespace { 847 user := int64(0) 848 containerBuilder.SetSecurityContext(corev1.SecurityContext{ 849 RunAsUser: &user, 850 }) 851 } 852 container := containerBuilder.GetObject() 853 854 if err := injectEnvs(sidecarRenderedParam.Cluster, component, sidecarRenderedParam.EnvConfigName, container); err != nil { 855 return nil, err 856 } 857 intctrlutil.InjectZeroResourcesLimitsIfEmpty(container) 858 return container, nil 859 } 860 861 func BuildRestoreJob(cluster *appsv1alpha1.Cluster, synthesizedComponent *component.SynthesizedComponent, name, image string, command []string, 862 volumes []corev1.Volume, volumeMounts []corev1.VolumeMount, env []corev1.EnvVar, resources *corev1.ResourceRequirements) (*batchv1.Job, error) { 863 containerBuilder := builder.NewContainerBuilder("restore"). 864 SetImage(image). 865 SetImagePullPolicy(corev1.PullIfNotPresent). 866 AddCommands(command...). 867 AddVolumeMounts(volumeMounts...). 868 AddEnv(env...) 869 if resources != nil { 870 containerBuilder.SetResources(*resources) 871 } 872 container := containerBuilder.GetObject() 873 874 ctx := corev1.PodSecurityContext{} 875 user := int64(0) 876 ctx.RunAsUser = &user 877 pod := builder.NewPodBuilder(cluster.Namespace, ""). 878 AddContainer(*container). 879 AddVolumes(volumes...). 880 SetRestartPolicy(corev1.RestartPolicyOnFailure). 881 SetSecurityContext(ctx). 882 GetObject() 883 template := corev1.PodTemplateSpec{ 884 Spec: pod.Spec, 885 } 886 887 job := builder.NewJobBuilder(cluster.Namespace, name). 888 AddLabels(constant.AppManagedByLabelKey, constant.AppName). 889 SetPodTemplateSpec(template). 890 GetObject() 891 containers := job.Spec.Template.Spec.Containers 892 if len(containers) > 0 { 893 if err := injectEnvs(cluster, synthesizedComponent, "", &containers[0]); err != nil { 894 return nil, err 895 } 896 intctrlutil.InjectZeroResourcesLimitsIfEmpty(&containers[0]) 897 } 898 tolerations, err := component.BuildTolerations(cluster, cluster.Spec.GetComponentByName(synthesizedComponent.Name)) 899 if err != nil { 900 return nil, err 901 } 902 job.Spec.Template.Spec.Tolerations = tolerations 903 return job, nil 904 } 905 906 func BuildCfgManagerToolsContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, component *component.SynthesizedComponent, toolsMetas []appsv1alpha1.ToolConfig, toolsMap map[string]cfgcm.ConfigSpecMeta) ([]corev1.Container, error) { 907 toolContainers := make([]corev1.Container, 0, len(toolsMetas)) 908 for _, toolConfig := range toolsMetas { 909 toolContainerBuilder := builder.NewContainerBuilder(toolConfig.Name). 910 AddCommands(toolConfig.Command...). 911 SetImagePullPolicy(corev1.PullIfNotPresent). 912 AddVolumeMounts(sidecarRenderedParam.Volumes...) 913 if len(toolConfig.Image) > 0 { 914 toolContainerBuilder.SetImage(toolConfig.Image) 915 } 916 toolContainers = append(toolContainers, *toolContainerBuilder.GetObject()) 917 } 918 for i := range toolContainers { 919 container := &toolContainers[i] 920 if err := injectEnvs(sidecarRenderedParam.Cluster, component, sidecarRenderedParam.EnvConfigName, container); err != nil { 921 return nil, err 922 } 923 intctrlutil.InjectZeroResourcesLimitsIfEmpty(container) 924 if meta, ok := toolsMap[container.Name]; ok { 925 setToolsScriptsPath(container, meta) 926 } 927 } 928 return toolContainers, nil 929 } 930 931 func setToolsScriptsPath(container *corev1.Container, meta cfgcm.ConfigSpecMeta) { 932 container.Env = append(container.Env, corev1.EnvVar{ 933 Name: cfgcm.KBTOOLSScriptsPathEnv, 934 Value: filepath.Join(cfgcm.KBScriptVolumePath, meta.ConfigSpec.Name), 935 }) 936 } 937 938 func BuildVolumeSnapshotClass(name string, driver string) *snapshotv1.VolumeSnapshotClass { 939 return builder.NewVolumeSnapshotClassBuilder("", name). 940 AddLabels(constant.AppManagedByLabelKey, constant.AppName). 941 SetDriver(driver). 942 SetDeletionPolicy(snapshotv1.VolumeSnapshotContentDelete). 943 GetObject() 944 } 945 946 func BuildServiceAccount(cluster *appsv1alpha1.Cluster) *corev1.ServiceAccount { 947 wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "") 948 delete(wellKnownLabels, constant.KBAppComponentLabelKey) 949 return builder.NewServiceAccountBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)). 950 AddLabelsInMap(wellKnownLabels). 951 GetObject() 952 } 953 954 func BuildRoleBinding(cluster *appsv1alpha1.Cluster) *rbacv1.RoleBinding { 955 wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "") 956 delete(wellKnownLabels, constant.KBAppComponentLabelKey) 957 return builder.NewRoleBindingBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)). 958 AddLabelsInMap(wellKnownLabels). 959 SetRoleRef(rbacv1.RoleRef{ 960 APIGroup: rbacv1.GroupName, 961 Kind: "ClusterRole", 962 Name: constant.RBACRoleName, 963 }). 964 AddSubjects(rbacv1.Subject{ 965 Kind: rbacv1.ServiceAccountKind, 966 Namespace: cluster.Namespace, 967 Name: fmt.Sprintf("kb-%s", cluster.Name), 968 }). 969 GetObject() 970 } 971 972 func BuildClusterRoleBinding(cluster *appsv1alpha1.Cluster) *rbacv1.ClusterRoleBinding { 973 wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "") 974 delete(wellKnownLabels, constant.KBAppComponentLabelKey) 975 return builder.NewClusterRoleBindingBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)). 976 AddLabelsInMap(wellKnownLabels). 977 SetRoleRef(rbacv1.RoleRef{ 978 APIGroup: rbacv1.GroupName, 979 Kind: "ClusterRole", 980 Name: constant.RBACClusterRoleName, 981 }). 982 AddSubjects(rbacv1.Subject{ 983 Kind: rbacv1.ServiceAccountKind, 984 Namespace: cluster.Namespace, 985 Name: fmt.Sprintf("kb-%s", cluster.Name), 986 }). 987 GetObject() 988 }