github.com/spotahome/redis-operator@v1.2.4/operator/redisfailover/service/generator.go (about) 1 package service 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "text/template" 8 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 policyv1 "k8s.io/api/policy/v1" 12 "k8s.io/apimachinery/pkg/api/resource" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/util/intstr" 15 16 redisfailoverv1 "github.com/spotahome/redis-operator/api/redisfailover/v1" 17 "github.com/spotahome/redis-operator/operator/redisfailover/util" 18 ) 19 20 const ( 21 redisConfigurationVolumeName = "redis-config" 22 // Template used to build the Redis configuration 23 redisConfigTemplate = `slaveof 127.0.0.1 {{.Spec.Redis.Port}} 24 port {{.Spec.Redis.Port}} 25 tcp-keepalive 60 26 save 900 1 27 save 300 10 28 user pinger -@all +ping on >pingpass 29 {{- range .Spec.Redis.CustomCommandRenames}} 30 rename-command "{{.From}}" "{{.To}}" 31 {{- end}} 32 ` 33 34 sentinelConfigTemplate = `sentinel monitor mymaster 127.0.0.1 {{.Spec.Redis.Port}} 2 35 sentinel down-after-milliseconds mymaster 1000 36 sentinel failover-timeout mymaster 3000 37 sentinel parallel-syncs mymaster 2` 38 39 redisShutdownConfigurationVolumeName = "redis-shutdown-config" 40 redisStartupConfigurationVolumeName = "redis-startup-config" 41 redisReadinessVolumeName = "redis-readiness-config" 42 redisStorageVolumeName = "redis-data" 43 sentinelStartupConfigurationVolumeName = "sentinel-startup-config" 44 45 graceTime = 30 46 ) 47 48 func generateSentinelService(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.Service { 49 name := GetSentinelName(rf) 50 namespace := rf.Namespace 51 52 sentinelTargetPort := intstr.FromInt(26379) 53 selectorLabels := generateSelectorLabels(sentinelRoleName, rf.Name) 54 labels = util.MergeLabels(labels, selectorLabels) 55 56 return &corev1.Service{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: name, 59 Namespace: namespace, 60 Labels: labels, 61 OwnerReferences: ownerRefs, 62 Annotations: rf.Spec.Sentinel.ServiceAnnotations, 63 }, 64 Spec: corev1.ServiceSpec{ 65 Selector: selectorLabels, 66 Ports: []corev1.ServicePort{ 67 { 68 Name: "sentinel", 69 Port: 26379, 70 TargetPort: sentinelTargetPort, 71 Protocol: "TCP", 72 }, 73 }, 74 }, 75 } 76 } 77 78 func generateRedisService(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.Service { 79 name := GetRedisName(rf) 80 namespace := rf.Namespace 81 82 selectorLabels := generateSelectorLabels(redisRoleName, rf.Name) 83 labels = util.MergeLabels(labels, selectorLabels) 84 defaultAnnotations := map[string]string{ 85 "prometheus.io/scrape": "true", 86 "prometheus.io/port": "http", 87 "prometheus.io/path": "/metrics", 88 } 89 annotations := util.MergeLabels(defaultAnnotations, rf.Spec.Redis.ServiceAnnotations) 90 91 return &corev1.Service{ 92 ObjectMeta: metav1.ObjectMeta{ 93 Name: name, 94 Namespace: namespace, 95 Labels: labels, 96 OwnerReferences: ownerRefs, 97 Annotations: annotations, 98 }, 99 Spec: corev1.ServiceSpec{ 100 Type: corev1.ServiceTypeClusterIP, 101 ClusterIP: corev1.ClusterIPNone, 102 Ports: []corev1.ServicePort{ 103 { 104 Port: exporterPort, 105 Protocol: corev1.ProtocolTCP, 106 Name: exporterPortName, 107 }, 108 }, 109 Selector: selectorLabels, 110 }, 111 } 112 } 113 114 func generateSentinelConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap { 115 name := GetSentinelName(rf) 116 namespace := rf.Namespace 117 118 labels = util.MergeLabels(labels, generateSelectorLabels(sentinelRoleName, rf.Name)) 119 120 tmpl, err := template.New("sentinel").Parse(sentinelConfigTemplate) 121 if err != nil { 122 panic(err) 123 } 124 125 var tplOutput bytes.Buffer 126 if err := tmpl.Execute(&tplOutput, rf); err != nil { 127 panic(err) 128 } 129 130 sentinelConfigFileContent := tplOutput.String() 131 132 return &corev1.ConfigMap{ 133 ObjectMeta: metav1.ObjectMeta{ 134 Name: name, 135 Namespace: namespace, 136 Labels: labels, 137 OwnerReferences: ownerRefs, 138 }, 139 Data: map[string]string{ 140 sentinelConfigFileName: sentinelConfigFileContent, 141 }, 142 } 143 } 144 145 func generateRedisConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference, password string) *corev1.ConfigMap { 146 name := GetRedisName(rf) 147 labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name)) 148 149 tmpl, err := template.New("redis").Parse(redisConfigTemplate) 150 if err != nil { 151 panic(err) 152 } 153 154 var tplOutput bytes.Buffer 155 if err := tmpl.Execute(&tplOutput, rf); err != nil { 156 panic(err) 157 } 158 159 redisConfigFileContent := tplOutput.String() 160 161 if password != "" { 162 redisConfigFileContent = fmt.Sprintf("%s\nmasterauth %s\nrequirepass %s", redisConfigFileContent, password, password) 163 } 164 165 return &corev1.ConfigMap{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Name: name, 168 Namespace: rf.Namespace, 169 Labels: labels, 170 OwnerReferences: ownerRefs, 171 }, 172 Data: map[string]string{ 173 redisConfigFileName: redisConfigFileContent, 174 }, 175 } 176 } 177 178 func generateRedisShutdownConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap { 179 name := GetRedisShutdownConfigMapName(rf) 180 port := rf.Spec.Redis.Port 181 namespace := rf.Namespace 182 rfName := strings.Replace(strings.ToUpper(rf.Name), "-", "_", -1) 183 184 labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name)) 185 shutdownContent := fmt.Sprintf(`master=$(redis-cli -h ${RFS_%[1]v_SERVICE_HOST} -p ${RFS_%[1]v_SERVICE_PORT_SENTINEL} --csv SENTINEL get-master-addr-by-name mymaster | tr ',' ' ' | tr -d '\"' |cut -d' ' -f1) 186 if [ "$master" = "$(hostname -i)" ]; then 187 redis-cli -h ${RFS_%[1]v_SERVICE_HOST} -p ${RFS_%[1]v_SERVICE_PORT_SENTINEL} SENTINEL failover mymaster 188 sleep 31 189 fi 190 cmd="redis-cli -p %[2]v" 191 if [ ! -z "${REDIS_PASSWORD}" ]; then 192 export REDISCLI_AUTH=${REDIS_PASSWORD} 193 fi 194 save_command="${cmd} save" 195 eval $save_command`, rfName, port) 196 197 return &corev1.ConfigMap{ 198 ObjectMeta: metav1.ObjectMeta{ 199 Name: name, 200 Namespace: namespace, 201 Labels: labels, 202 OwnerReferences: ownerRefs, 203 }, 204 Data: map[string]string{ 205 "shutdown.sh": shutdownContent, 206 }, 207 } 208 } 209 210 func generateRedisReadinessConfigMap(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *corev1.ConfigMap { 211 name := GetRedisReadinessName(rf) 212 port := rf.Spec.Redis.Port 213 namespace := rf.Namespace 214 215 labels = util.MergeLabels(labels, generateSelectorLabels(redisRoleName, rf.Name)) 216 readinessContent := fmt.Sprintf(`ROLE="role" 217 ROLE_MASTER="role:master" 218 ROLE_SLAVE="role:slave" 219 IN_SYNC="master_sync_in_progress:1" 220 NO_MASTER="master_host:127.0.0.1" 221 222 cmd="redis-cli -p %[1]v" 223 if [ ! -z "${REDIS_PASSWORD}" ]; then 224 export REDISCLI_AUTH=${REDIS_PASSWORD} 225 fi 226 227 cmd="${cmd} info replication" 228 229 check_master(){ 230 exit 0 231 } 232 233 check_slave(){ 234 in_sync=$(echo "${cmd} | grep ${IN_SYNC} | tr -d \"\\r\" | tr -d \"\\n\"" | xargs -0 sh -c) 235 no_master=$(echo "${cmd} | grep ${NO_MASTER} | tr -d \"\\r\" | tr -d \"\\n\"" | xargs -0 sh -c) 236 237 if [ -z "$in_sync" ] && [ -z "$no_master" ]; then 238 exit 0 239 fi 240 241 exit 1 242 } 243 244 role=$(echo "${cmd} | grep $ROLE | tr -d \"\\r\" | tr -d \"\\n\"" | xargs -0 sh -c) 245 case $role in 246 $ROLE_MASTER) 247 check_master 248 ;; 249 $ROLE_SLAVE) 250 check_slave 251 ;; 252 *) 253 echo "unespected" 254 exit 1 255 esac`, port) 256 257 return &corev1.ConfigMap{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Name: name, 260 Namespace: namespace, 261 Labels: labels, 262 OwnerReferences: ownerRefs, 263 }, 264 Data: map[string]string{ 265 "ready.sh": readinessContent, 266 }, 267 } 268 } 269 270 func generateRedisStatefulSet(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *appsv1.StatefulSet { 271 name := GetRedisName(rf) 272 namespace := rf.Namespace 273 274 redisCommand := getRedisCommand(rf) 275 selectorLabels := generateSelectorLabels(redisRoleName, rf.Name) 276 labels = util.MergeLabels(labels, selectorLabels) 277 labels = util.MergeLabels(labels, generateRedisDefaultRoleLabel()) 278 279 volumeMounts := getRedisVolumeMounts(rf) 280 volumes := getRedisVolumes(rf) 281 terminationGracePeriodSeconds := getTerminationGracePeriodSeconds(rf) 282 283 ss := &appsv1.StatefulSet{ 284 ObjectMeta: metav1.ObjectMeta{ 285 Annotations: rf.Annotations, 286 Name: name, 287 Namespace: namespace, 288 Labels: labels, 289 OwnerReferences: ownerRefs, 290 }, 291 Spec: appsv1.StatefulSetSpec{ 292 ServiceName: name, 293 Replicas: &rf.Spec.Redis.Replicas, 294 UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ 295 Type: appsv1.OnDeleteStatefulSetStrategyType, 296 }, 297 PodManagementPolicy: appsv1.ParallelPodManagement, 298 Selector: &metav1.LabelSelector{ 299 MatchLabels: selectorLabels, 300 }, 301 Template: corev1.PodTemplateSpec{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Labels: labels, 304 Annotations: rf.Spec.Redis.PodAnnotations, 305 }, 306 Spec: corev1.PodSpec{ 307 Affinity: getAffinity(rf.Spec.Redis.Affinity, labels), 308 Tolerations: rf.Spec.Redis.Tolerations, 309 TopologySpreadConstraints: rf.Spec.Redis.TopologySpreadConstraints, 310 NodeSelector: rf.Spec.Redis.NodeSelector, 311 SecurityContext: getSecurityContext(rf.Spec.Redis.SecurityContext), 312 HostNetwork: rf.Spec.Redis.HostNetwork, 313 DNSPolicy: getDnsPolicy(rf.Spec.Redis.DNSPolicy), 314 ImagePullSecrets: rf.Spec.Redis.ImagePullSecrets, 315 PriorityClassName: rf.Spec.Redis.PriorityClassName, 316 ServiceAccountName: rf.Spec.Redis.ServiceAccountName, 317 TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, 318 Containers: []corev1.Container{ 319 { 320 Name: "redis", 321 Image: rf.Spec.Redis.Image, 322 ImagePullPolicy: pullPolicy(rf.Spec.Redis.ImagePullPolicy), 323 SecurityContext: getContainerSecurityContext(rf.Spec.Redis.ContainerSecurityContext), 324 Ports: []corev1.ContainerPort{ 325 { 326 Name: "redis", 327 ContainerPort: rf.Spec.Redis.Port, 328 Protocol: corev1.ProtocolTCP, 329 }, 330 }, 331 VolumeMounts: volumeMounts, 332 Command: redisCommand, 333 ReadinessProbe: &corev1.Probe{ 334 InitialDelaySeconds: graceTime, 335 TimeoutSeconds: 5, 336 ProbeHandler: corev1.ProbeHandler{ 337 Exec: &corev1.ExecAction{ 338 Command: []string{"/bin/sh", "/redis-readiness/ready.sh"}, 339 }, 340 }, 341 }, 342 LivenessProbe: &corev1.Probe{ 343 InitialDelaySeconds: graceTime, 344 TimeoutSeconds: 5, 345 FailureThreshold: 6, 346 PeriodSeconds: 15, 347 ProbeHandler: corev1.ProbeHandler{ 348 Exec: &corev1.ExecAction{ 349 Command: []string{ 350 "sh", 351 "-c", 352 fmt.Sprintf("redis-cli -h $(hostname) -p %[1]v ping --user pinger --pass pingpass --no-auth-warning", rf.Spec.Redis.Port), 353 }, 354 }, 355 }, 356 }, 357 Resources: rf.Spec.Redis.Resources, 358 Lifecycle: &corev1.Lifecycle{ 359 PreStop: &corev1.LifecycleHandler{ 360 Exec: &corev1.ExecAction{ 361 Command: []string{"/bin/sh", "/redis-shutdown/shutdown.sh"}, 362 }, 363 }, 364 }, 365 }, 366 }, 367 Volumes: volumes, 368 }, 369 }, 370 }, 371 } 372 373 if rf.Spec.Redis.Storage.PersistentVolumeClaim != nil { 374 pvc := corev1.PersistentVolumeClaim{ 375 TypeMeta: metav1.TypeMeta{ 376 APIVersion: "v1", 377 Kind: "PersistentVolumeClaim", 378 }, 379 ObjectMeta: metav1.ObjectMeta{ 380 Name: rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Name, 381 Labels: rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Labels, 382 Annotations: rf.Spec.Redis.Storage.PersistentVolumeClaim.EmbeddedObjectMetadata.Annotations, 383 CreationTimestamp: metav1.Time{}, 384 }, 385 Spec: rf.Spec.Redis.Storage.PersistentVolumeClaim.Spec, 386 Status: rf.Spec.Redis.Storage.PersistentVolumeClaim.Status, 387 } 388 if !rf.Spec.Redis.Storage.KeepAfterDeletion { 389 // Set an owner reference so the persistent volumes are deleted when the RF is 390 pvc.OwnerReferences = ownerRefs 391 } 392 ss.Spec.VolumeClaimTemplates = []corev1.PersistentVolumeClaim{ 393 pvc, 394 } 395 } 396 397 if rf.Spec.Redis.StartupConfigMap != "" { 398 ss.Spec.Template.Spec.Containers[0].StartupProbe = &corev1.Probe{ 399 InitialDelaySeconds: graceTime, 400 TimeoutSeconds: 5, 401 FailureThreshold: 6, 402 PeriodSeconds: 15, 403 ProbeHandler: corev1.ProbeHandler{ 404 Exec: &corev1.ExecAction{ 405 Command: []string{"/bin/sh", "/redis-startup/startup.sh"}, 406 }, 407 }, 408 } 409 } 410 411 if rf.Spec.Redis.Exporter.Enabled { 412 exporter := createRedisExporterContainer(rf) 413 ss.Spec.Template.Spec.Containers = append(ss.Spec.Template.Spec.Containers, exporter) 414 } 415 416 if rf.Spec.Redis.InitContainers != nil { 417 initContainers := getInitContainersWithRedisEnv(rf) 418 ss.Spec.Template.Spec.InitContainers = append(ss.Spec.Template.Spec.InitContainers, initContainers...) 419 } 420 421 if rf.Spec.Redis.ExtraContainers != nil { 422 extraContainers := getExtraContainersWithRedisEnv(rf) 423 ss.Spec.Template.Spec.Containers = append(ss.Spec.Template.Spec.Containers, extraContainers...) 424 } 425 426 redisEnv := getRedisEnv(rf) 427 ss.Spec.Template.Spec.Containers[0].Env = append(ss.Spec.Template.Spec.Containers[0].Env, redisEnv...) 428 429 return ss 430 } 431 432 func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[string]string, ownerRefs []metav1.OwnerReference) *appsv1.Deployment { 433 name := GetSentinelName(rf) 434 configMapName := GetSentinelName(rf) 435 namespace := rf.Namespace 436 437 sentinelCommand := getSentinelCommand(rf) 438 selectorLabels := generateSelectorLabels(sentinelRoleName, rf.Name) 439 labels = util.MergeLabels(labels, selectorLabels) 440 441 volumeMounts := getSentinelVolumeMounts(rf) 442 volumes := getSentinelVolumes(rf, configMapName) 443 444 sd := &appsv1.Deployment{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Name: name, 447 Namespace: namespace, 448 Labels: labels, 449 OwnerReferences: ownerRefs, 450 }, 451 Spec: appsv1.DeploymentSpec{ 452 Replicas: &rf.Spec.Sentinel.Replicas, 453 Selector: &metav1.LabelSelector{ 454 MatchLabels: selectorLabels, 455 }, 456 Template: corev1.PodTemplateSpec{ 457 ObjectMeta: metav1.ObjectMeta{ 458 Labels: labels, 459 Annotations: rf.Spec.Sentinel.PodAnnotations, 460 }, 461 Spec: corev1.PodSpec{ 462 Affinity: getAffinity(rf.Spec.Sentinel.Affinity, labels), 463 Tolerations: rf.Spec.Sentinel.Tolerations, 464 TopologySpreadConstraints: rf.Spec.Sentinel.TopologySpreadConstraints, 465 NodeSelector: rf.Spec.Sentinel.NodeSelector, 466 SecurityContext: getSecurityContext(rf.Spec.Sentinel.SecurityContext), 467 HostNetwork: rf.Spec.Sentinel.HostNetwork, 468 DNSPolicy: getDnsPolicy(rf.Spec.Sentinel.DNSPolicy), 469 ImagePullSecrets: rf.Spec.Sentinel.ImagePullSecrets, 470 PriorityClassName: rf.Spec.Sentinel.PriorityClassName, 471 ServiceAccountName: rf.Spec.Sentinel.ServiceAccountName, 472 InitContainers: []corev1.Container{ 473 { 474 Name: "sentinel-config-copy", 475 Image: rf.Spec.Sentinel.Image, 476 ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy), 477 SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ConfigCopy.ContainerSecurityContext), 478 VolumeMounts: []corev1.VolumeMount{ 479 { 480 Name: "sentinel-config", 481 MountPath: "/redis", 482 }, 483 { 484 Name: "sentinel-config-writable", 485 MountPath: "/redis-writable", 486 }, 487 }, 488 Command: []string{ 489 "cp", 490 fmt.Sprintf("/redis/%s", sentinelConfigFileName), 491 fmt.Sprintf("/redis-writable/%s", sentinelConfigFileName), 492 }, 493 Resources: corev1.ResourceRequirements{ 494 Limits: corev1.ResourceList{ 495 corev1.ResourceCPU: resource.MustParse("10m"), 496 corev1.ResourceMemory: resource.MustParse("32Mi"), 497 }, 498 Requests: corev1.ResourceList{ 499 corev1.ResourceCPU: resource.MustParse("10m"), 500 corev1.ResourceMemory: resource.MustParse("32Mi"), 501 }, 502 }, 503 }, 504 }, 505 Containers: []corev1.Container{ 506 { 507 Name: "sentinel", 508 Image: rf.Spec.Sentinel.Image, 509 ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.ImagePullPolicy), 510 SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.ContainerSecurityContext), 511 Ports: []corev1.ContainerPort{ 512 { 513 Name: "sentinel", 514 ContainerPort: 26379, 515 Protocol: corev1.ProtocolTCP, 516 }, 517 }, 518 VolumeMounts: volumeMounts, 519 Command: sentinelCommand, 520 ReadinessProbe: &corev1.Probe{ 521 InitialDelaySeconds: graceTime, 522 TimeoutSeconds: 5, 523 ProbeHandler: corev1.ProbeHandler{ 524 Exec: &corev1.ExecAction{ 525 Command: []string{ 526 "sh", 527 "-c", 528 "redis-cli -h $(hostname) -p 26379 ping", 529 }, 530 }, 531 }, 532 }, 533 LivenessProbe: &corev1.Probe{ 534 InitialDelaySeconds: graceTime, 535 TimeoutSeconds: 5, 536 ProbeHandler: corev1.ProbeHandler{ 537 Exec: &corev1.ExecAction{ 538 Command: []string{ 539 "sh", 540 "-c", 541 "redis-cli -h $(hostname) -p 26379 ping", 542 }, 543 }, 544 }, 545 }, 546 Resources: rf.Spec.Sentinel.Resources, 547 }, 548 }, 549 Volumes: volumes, 550 }, 551 }, 552 }, 553 } 554 555 if rf.Spec.Sentinel.StartupConfigMap != "" { 556 sd.Spec.Template.Spec.Containers[0].StartupProbe = &corev1.Probe{ 557 InitialDelaySeconds: graceTime, 558 TimeoutSeconds: 5, 559 FailureThreshold: 6, 560 PeriodSeconds: 15, 561 ProbeHandler: corev1.ProbeHandler{ 562 Exec: &corev1.ExecAction{ 563 Command: []string{"/bin/sh", "/sentinel-startup/startup.sh"}, 564 }, 565 }, 566 } 567 } 568 569 if rf.Spec.Sentinel.Exporter.Enabled { 570 exporter := createSentinelExporterContainer(rf) 571 sd.Spec.Template.Spec.Containers = append(sd.Spec.Template.Spec.Containers, exporter) 572 } 573 if rf.Spec.Sentinel.InitContainers != nil { 574 sd.Spec.Template.Spec.InitContainers = append(sd.Spec.Template.Spec.InitContainers, rf.Spec.Sentinel.InitContainers...) 575 } 576 577 if rf.Spec.Sentinel.ExtraContainers != nil { 578 sd.Spec.Template.Spec.Containers = append(sd.Spec.Template.Spec.Containers, rf.Spec.Sentinel.ExtraContainers...) 579 } 580 581 return sd 582 } 583 584 func generatePodDisruptionBudget(name string, namespace string, labels map[string]string, ownerRefs []metav1.OwnerReference, minAvailable intstr.IntOrString) *policyv1.PodDisruptionBudget { 585 return &policyv1.PodDisruptionBudget{ 586 ObjectMeta: metav1.ObjectMeta{ 587 Name: name, 588 Namespace: namespace, 589 Labels: labels, 590 OwnerReferences: ownerRefs, 591 }, 592 Spec: policyv1.PodDisruptionBudgetSpec{ 593 MinAvailable: &minAvailable, 594 Selector: &metav1.LabelSelector{ 595 MatchLabels: labels, 596 }, 597 }, 598 } 599 } 600 601 var exporterDefaultResourceRequirements = corev1.ResourceRequirements{ 602 Limits: corev1.ResourceList{ 603 corev1.ResourceCPU: resource.MustParse(exporterDefaultLimitCPU), 604 corev1.ResourceMemory: resource.MustParse(exporterDefaultLimitMemory), 605 }, 606 Requests: corev1.ResourceList{ 607 corev1.ResourceCPU: resource.MustParse(exporterDefaultRequestCPU), 608 corev1.ResourceMemory: resource.MustParse(exporterDefaultRequestMemory), 609 }, 610 } 611 612 func createRedisExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.Container { 613 resources := exporterDefaultResourceRequirements 614 if rf.Spec.Redis.Exporter.Resources != nil { 615 resources = *rf.Spec.Redis.Exporter.Resources 616 } 617 container := corev1.Container{ 618 Name: exporterContainerName, 619 Image: rf.Spec.Redis.Exporter.Image, 620 ImagePullPolicy: pullPolicy(rf.Spec.Redis.Exporter.ImagePullPolicy), 621 SecurityContext: getContainerSecurityContext(rf.Spec.Redis.Exporter.ContainerSecurityContext), 622 Args: rf.Spec.Redis.Exporter.Args, 623 Env: append(rf.Spec.Redis.Exporter.Env, corev1.EnvVar{ 624 Name: "REDIS_ALIAS", 625 ValueFrom: &corev1.EnvVarSource{ 626 FieldRef: &corev1.ObjectFieldSelector{ 627 FieldPath: "metadata.name", 628 }, 629 }, 630 }, 631 ), 632 Ports: []corev1.ContainerPort{ 633 { 634 Name: "metrics", 635 ContainerPort: exporterPort, 636 Protocol: corev1.ProtocolTCP, 637 }, 638 }, 639 Resources: resources, 640 } 641 642 redisEnv := getRedisEnv(rf) 643 container.Env = append(container.Env, redisEnv...) 644 645 return container 646 } 647 648 func createSentinelExporterContainer(rf *redisfailoverv1.RedisFailover) corev1.Container { 649 resources := exporterDefaultResourceRequirements 650 if rf.Spec.Sentinel.Exporter.Resources != nil { 651 resources = *rf.Spec.Sentinel.Exporter.Resources 652 } 653 container := corev1.Container{ 654 Name: sentinelExporterContainerName, 655 Image: rf.Spec.Sentinel.Exporter.Image, 656 ImagePullPolicy: pullPolicy(rf.Spec.Sentinel.Exporter.ImagePullPolicy), 657 SecurityContext: getContainerSecurityContext(rf.Spec.Sentinel.Exporter.ContainerSecurityContext), 658 Args: rf.Spec.Sentinel.Exporter.Args, 659 Env: append(rf.Spec.Sentinel.Exporter.Env, corev1.EnvVar{ 660 Name: "REDIS_ALIAS", 661 ValueFrom: &corev1.EnvVarSource{ 662 FieldRef: &corev1.ObjectFieldSelector{ 663 FieldPath: "metadata.name", 664 }, 665 }, 666 }, corev1.EnvVar{ 667 Name: "REDIS_EXPORTER_WEB_LISTEN_ADDRESS", 668 Value: fmt.Sprintf("0.0.0.0:%[1]v", sentinelExporterPort), 669 }, corev1.EnvVar{ 670 Name: "REDIS_ADDR", 671 Value: "redis://127.0.0.1:26379", 672 }, 673 ), 674 Ports: []corev1.ContainerPort{ 675 { 676 Name: "metrics", 677 ContainerPort: sentinelExporterPort, 678 Protocol: corev1.ProtocolTCP, 679 }, 680 }, 681 Resources: resources, 682 } 683 684 return container 685 } 686 687 func getAffinity(affinity *corev1.Affinity, labels map[string]string) *corev1.Affinity { 688 if affinity != nil { 689 return affinity 690 } 691 692 // Return a SOFT anti-affinity 693 return &corev1.Affinity{ 694 PodAntiAffinity: &corev1.PodAntiAffinity{ 695 PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{ 696 { 697 Weight: 100, 698 PodAffinityTerm: corev1.PodAffinityTerm{ 699 TopologyKey: hostnameTopologyKey, 700 LabelSelector: &metav1.LabelSelector{ 701 MatchLabels: labels, 702 }, 703 }, 704 }, 705 }, 706 }, 707 } 708 } 709 710 func getSecurityContext(secctx *corev1.PodSecurityContext) *corev1.PodSecurityContext { 711 if secctx != nil { 712 return secctx 713 } 714 715 defaultUserAndGroup := int64(1000) 716 runAsNonRoot := true 717 718 return &corev1.PodSecurityContext{ 719 RunAsUser: &defaultUserAndGroup, 720 RunAsGroup: &defaultUserAndGroup, 721 RunAsNonRoot: &runAsNonRoot, 722 FSGroup: &defaultUserAndGroup, 723 } 724 } 725 726 func getContainerSecurityContext(secctx *corev1.SecurityContext) *corev1.SecurityContext { 727 if secctx != nil { 728 return secctx 729 } 730 731 capabilities := &corev1.Capabilities{ 732 Add: []corev1.Capability{}, 733 Drop: []corev1.Capability{ 734 "ALL", 735 }, 736 } 737 privileged := false 738 defaultUserAndGroup := int64(1000) 739 runAsNonRoot := true 740 allowPrivilegeEscalation := false 741 readOnlyRootFilesystem := true 742 743 return &corev1.SecurityContext{ 744 Capabilities: capabilities, 745 Privileged: &privileged, 746 RunAsUser: &defaultUserAndGroup, 747 RunAsGroup: &defaultUserAndGroup, 748 RunAsNonRoot: &runAsNonRoot, 749 ReadOnlyRootFilesystem: &readOnlyRootFilesystem, 750 AllowPrivilegeEscalation: &allowPrivilegeEscalation, 751 } 752 } 753 754 func getDnsPolicy(dnspolicy corev1.DNSPolicy) corev1.DNSPolicy { 755 if dnspolicy == "" { 756 return corev1.DNSClusterFirst 757 } 758 return dnspolicy 759 } 760 761 func getQuorum(rf *redisfailoverv1.RedisFailover) int32 { 762 return rf.Spec.Sentinel.Replicas/2 + 1 763 } 764 765 func getRedisVolumeMounts(rf *redisfailoverv1.RedisFailover) []corev1.VolumeMount { 766 volumeMounts := []corev1.VolumeMount{ 767 { 768 Name: redisConfigurationVolumeName, 769 MountPath: "/redis", 770 }, 771 { 772 Name: redisShutdownConfigurationVolumeName, 773 MountPath: "/redis-shutdown", 774 }, 775 { 776 Name: redisReadinessVolumeName, 777 MountPath: "/redis-readiness", 778 }, 779 { 780 Name: getRedisDataVolumeName(rf), 781 MountPath: "/data", 782 }, 783 } 784 785 if rf.Spec.Redis.StartupConfigMap != "" { 786 startupVolumeMount := corev1.VolumeMount{ 787 Name: redisStartupConfigurationVolumeName, 788 MountPath: "/redis-startup", 789 } 790 791 volumeMounts = append(volumeMounts, startupVolumeMount) 792 } 793 794 if rf.Spec.Redis.ExtraVolumeMounts != nil { 795 volumeMounts = append(volumeMounts, rf.Spec.Redis.ExtraVolumeMounts...) 796 } 797 798 return volumeMounts 799 } 800 801 func getSentinelVolumeMounts(rf *redisfailoverv1.RedisFailover) []corev1.VolumeMount { 802 volumeMounts := []corev1.VolumeMount{ 803 { 804 Name: "sentinel-config-writable", 805 MountPath: "/redis", 806 }, 807 } 808 809 if rf.Spec.Sentinel.StartupConfigMap != "" { 810 startupVolumeMount := corev1.VolumeMount{ 811 Name: "sentinel-startup-config", 812 MountPath: "/sentinel-startup", 813 } 814 volumeMounts = append(volumeMounts, startupVolumeMount) 815 } 816 if rf.Spec.Sentinel.ExtraVolumeMounts != nil { 817 volumeMounts = append(volumeMounts, rf.Spec.Sentinel.ExtraVolumeMounts...) 818 } 819 820 return volumeMounts 821 } 822 823 func getRedisVolumes(rf *redisfailoverv1.RedisFailover) []corev1.Volume { 824 configMapName := GetRedisName(rf) 825 shutdownConfigMapName := GetRedisShutdownConfigMapName(rf) 826 readinessConfigMapName := GetRedisReadinessName(rf) 827 828 executeMode := int32(0744) 829 volumes := []corev1.Volume{ 830 { 831 Name: redisConfigurationVolumeName, 832 VolumeSource: corev1.VolumeSource{ 833 ConfigMap: &corev1.ConfigMapVolumeSource{ 834 LocalObjectReference: corev1.LocalObjectReference{ 835 Name: configMapName, 836 }, 837 }, 838 }, 839 }, 840 { 841 Name: redisShutdownConfigurationVolumeName, 842 VolumeSource: corev1.VolumeSource{ 843 ConfigMap: &corev1.ConfigMapVolumeSource{ 844 LocalObjectReference: corev1.LocalObjectReference{ 845 Name: shutdownConfigMapName, 846 }, 847 DefaultMode: &executeMode, 848 }, 849 }, 850 }, 851 { 852 Name: redisReadinessVolumeName, 853 VolumeSource: corev1.VolumeSource{ 854 ConfigMap: &corev1.ConfigMapVolumeSource{ 855 LocalObjectReference: corev1.LocalObjectReference{ 856 Name: readinessConfigMapName, 857 }, 858 DefaultMode: &executeMode, 859 }, 860 }, 861 }, 862 } 863 864 if rf.Spec.Redis.StartupConfigMap != "" { 865 startupVolumeName := rf.Spec.Redis.StartupConfigMap 866 startupVolume := corev1.Volume{ 867 Name: redisStartupConfigurationVolumeName, 868 VolumeSource: corev1.VolumeSource{ 869 ConfigMap: &corev1.ConfigMapVolumeSource{ 870 LocalObjectReference: corev1.LocalObjectReference{ 871 Name: startupVolumeName, 872 }, 873 DefaultMode: &executeMode, 874 }, 875 }, 876 } 877 volumes = append(volumes, startupVolume) 878 } 879 880 if rf.Spec.Redis.ExtraVolumes != nil { 881 volumes = append(volumes, rf.Spec.Redis.ExtraVolumes...) 882 } 883 884 dataVolume := getRedisDataVolume(rf) 885 if dataVolume != nil { 886 volumes = append(volumes, *dataVolume) 887 } 888 889 return volumes 890 } 891 892 func getSentinelVolumes(rf *redisfailoverv1.RedisFailover, configMapName string) []corev1.Volume { 893 executeMode := int32(0744) 894 895 volumes := []corev1.Volume{ 896 { 897 Name: "sentinel-config", 898 VolumeSource: corev1.VolumeSource{ 899 ConfigMap: &corev1.ConfigMapVolumeSource{ 900 LocalObjectReference: corev1.LocalObjectReference{ 901 Name: configMapName, 902 }, 903 }, 904 }, 905 }, 906 { 907 Name: "sentinel-config-writable", 908 VolumeSource: corev1.VolumeSource{ 909 EmptyDir: &corev1.EmptyDirVolumeSource{}, 910 }, 911 }, 912 } 913 914 if rf.Spec.Sentinel.StartupConfigMap != "" { 915 startupVolumeName := rf.Spec.Sentinel.StartupConfigMap 916 startupVolume := corev1.Volume{ 917 Name: sentinelStartupConfigurationVolumeName, 918 VolumeSource: corev1.VolumeSource{ 919 ConfigMap: &corev1.ConfigMapVolumeSource{ 920 LocalObjectReference: corev1.LocalObjectReference{ 921 Name: startupVolumeName, 922 }, 923 DefaultMode: &executeMode, 924 }, 925 }, 926 } 927 volumes = append(volumes, startupVolume) 928 } 929 930 if rf.Spec.Sentinel.ExtraVolumes != nil { 931 volumes = append(volumes, rf.Spec.Sentinel.ExtraVolumes...) 932 } 933 934 return volumes 935 } 936 937 func getRedisDataVolume(rf *redisfailoverv1.RedisFailover) *corev1.Volume { 938 // This will find the volumed desired by the user. If no volume defined 939 // an EmptyDir will be used by default 940 switch { 941 case rf.Spec.Redis.Storage.PersistentVolumeClaim != nil: 942 return nil 943 case rf.Spec.Redis.Storage.EmptyDir != nil: 944 return &corev1.Volume{ 945 Name: redisStorageVolumeName, 946 VolumeSource: corev1.VolumeSource{ 947 EmptyDir: rf.Spec.Redis.Storage.EmptyDir, 948 }, 949 } 950 default: 951 return &corev1.Volume{ 952 Name: redisStorageVolumeName, 953 VolumeSource: corev1.VolumeSource{ 954 EmptyDir: &corev1.EmptyDirVolumeSource{}, 955 }, 956 } 957 } 958 } 959 960 func getRedisDataVolumeName(rf *redisfailoverv1.RedisFailover) string { 961 switch { 962 case rf.Spec.Redis.Storage.PersistentVolumeClaim != nil: 963 return rf.Spec.Redis.Storage.PersistentVolumeClaim.Name 964 case rf.Spec.Redis.Storage.EmptyDir != nil: 965 return redisStorageVolumeName 966 default: 967 return redisStorageVolumeName 968 } 969 } 970 971 func getRedisCommand(rf *redisfailoverv1.RedisFailover) []string { 972 if len(rf.Spec.Redis.Command) > 0 { 973 return rf.Spec.Redis.Command 974 } 975 return []string{ 976 "redis-server", 977 fmt.Sprintf("/redis/%s", redisConfigFileName), 978 } 979 } 980 981 func getSentinelCommand(rf *redisfailoverv1.RedisFailover) []string { 982 if len(rf.Spec.Sentinel.Command) > 0 { 983 return rf.Spec.Sentinel.Command 984 } 985 return []string{ 986 "redis-server", 987 fmt.Sprintf("/redis/%s", sentinelConfigFileName), 988 "--sentinel", 989 } 990 } 991 992 func pullPolicy(specPolicy corev1.PullPolicy) corev1.PullPolicy { 993 if specPolicy == "" { 994 return corev1.PullAlways 995 } 996 return specPolicy 997 } 998 999 func getTerminationGracePeriodSeconds(rf *redisfailoverv1.RedisFailover) int64 { 1000 if rf.Spec.Redis.TerminationGracePeriodSeconds > 0 { 1001 return rf.Spec.Redis.TerminationGracePeriodSeconds 1002 } 1003 return 30 1004 } 1005 1006 func getExtraContainersWithRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.Container { 1007 env := getRedisEnv(rf) 1008 extraContainers := getContainersWithRedisEnv(rf.Spec.Redis.ExtraContainers, env) 1009 1010 return extraContainers 1011 } 1012 1013 func getInitContainersWithRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.Container { 1014 env := getRedisEnv(rf) 1015 initContainers := getContainersWithRedisEnv(rf.Spec.Redis.InitContainers, env) 1016 1017 return initContainers 1018 } 1019 1020 func getContainersWithRedisEnv(cs []corev1.Container, e []corev1.EnvVar) []corev1.Container { 1021 var containers []corev1.Container 1022 for _, c := range cs { 1023 c.Env = append(c.Env, e...) 1024 containers = append(containers, c) 1025 } 1026 1027 return containers 1028 } 1029 1030 func getRedisEnv(rf *redisfailoverv1.RedisFailover) []corev1.EnvVar { 1031 var env []corev1.EnvVar 1032 1033 env = append(env, corev1.EnvVar{ 1034 Name: "REDIS_ADDR", 1035 Value: fmt.Sprintf("redis://127.0.0.1:%[1]v", rf.Spec.Redis.Port), 1036 }) 1037 1038 env = append(env, corev1.EnvVar{ 1039 Name: "REDIS_PORT", 1040 Value: fmt.Sprintf("%[1]v", rf.Spec.Redis.Port), 1041 }) 1042 1043 env = append(env, corev1.EnvVar{ 1044 Name: "REDIS_USER", 1045 Value: "default", 1046 }) 1047 1048 if rf.Spec.Auth.SecretPath != "" { 1049 env = append(env, corev1.EnvVar{ 1050 Name: "REDIS_PASSWORD", 1051 ValueFrom: &corev1.EnvVarSource{ 1052 SecretKeyRef: &corev1.SecretKeySelector{ 1053 LocalObjectReference: corev1.LocalObjectReference{ 1054 Name: rf.Spec.Auth.SecretPath, 1055 }, 1056 Key: "password", 1057 }, 1058 }, 1059 }) 1060 } 1061 1062 return env 1063 }