diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index f4a2d7ae6..fdee1aea9 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -29,6 +29,14 @@ type KubernetesConfig struct { ExistingPasswordSecret *ExistingPasswordSecret `json:"redisSecret,omitempty"` ImagePullSecrets *[]corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` UpdateStrategy appsv1.StatefulSetUpdateStrategy `json:"updateStrategy,omitempty"` + Service *ServiceConfig `json:"service,omitempty"` +} + +// ServiceConfig define the type of service to be created and its annotations +type ServiceConfig struct { + // +kubebuilder:validation:Enum=LoadBalancer;NodePort;ClusterIP + ServiceType string `json:"serviceType,omitempty"` + ServiceAnnotations map[string]string `json:"annotations,omitempty"` } // RedisConfig defines the external configuration of Redis diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 9f30cdc7c..d263faab4 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -74,6 +74,11 @@ func (in *KubernetesConfig) DeepCopyInto(out *KubernetesConfig) { } } in.UpdateStrategy.DeepCopyInto(&out.UpdateStrategy) + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(ServiceConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesConfig. @@ -597,6 +602,28 @@ func (in *RedisStatus) DeepCopy() *RedisStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceConfig) DeepCopyInto(out *ServiceConfig) { + *out = *in + if in.ServiceAnnotations != nil { + in, out := &in.ServiceAnnotations, &out.ServiceAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceConfig. +func (in *ServiceConfig) DeepCopy() *ServiceConfig { + if in == nil { + return nil + } + out := new(ServiceConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Sidecar) DeepCopyInto(out *Sidecar) { *out = *in diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml index 9da37e5bb..4fcdb8b16 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redis.yaml @@ -992,6 +992,21 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + service: + description: ServiceConfig define the type of service to be created + and its annotations + properties: + annotations: + additionalProperties: + type: string + type: object + serviceType: + enum: + - LoadBalancer + - NodePort + - ClusterIP + type: string + type: object updateStrategy: description: StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml index b96602473..5210cc3d2 100644 --- a/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml +++ b/config/crd/bases/redis.redis.opstreelabs.in_redisclusters.yaml @@ -190,6 +190,21 @@ spec: to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' type: object type: object + service: + description: ServiceConfig define the type of service to be created + and its annotations + properties: + annotations: + additionalProperties: + type: string + type: object + serviceType: + enum: + - LoadBalancer + - NodePort + - ClusterIP + type: string + type: object updateStrategy: description: StatefulSetUpdateStrategy indicates the strategy that the StatefulSet controller will use to perform updates. diff --git a/k8sutils/labels.go b/k8sutils/labels.go index e0316df12..2edf8f7fa 100644 --- a/k8sutils/labels.go +++ b/k8sutils/labels.go @@ -74,7 +74,7 @@ func filterAnnotations(anots map[string]string) map[string]string { } // generateServiceAnots generates and returns service annotations -func generateServiceAnots(stsMeta metav1.ObjectMeta) map[string]string { +func generateServiceAnots(stsMeta metav1.ObjectMeta, additionalSvcAnnotations map[string]string) map[string]string { anots := map[string]string{ "redis.opstreelabs.in": "true", "redis.opstreelabs.instance": stsMeta.GetName(), @@ -84,6 +84,10 @@ func generateServiceAnots(stsMeta metav1.ObjectMeta) map[string]string { for k, v := range stsMeta.GetAnnotations() { anots[k] = v } + for k := range additionalSvcAnnotations { + anots[k] = additionalSvcAnnotations[k] + } + return filterAnnotations(anots) } diff --git a/k8sutils/redis-cluster.go b/k8sutils/redis-cluster.go index 1bd6e906e..71740a578 100644 --- a/k8sutils/redis-cluster.go +++ b/k8sutils/redis-cluster.go @@ -171,21 +171,27 @@ func (service RedisClusterService) CreateRedisClusterService(cr *redisv1beta1.Re serviceName := cr.ObjectMeta.Name + "-" + service.RedisServiceRole logger := serviceLogger(cr.Namespace, serviceName) labels := getRedisLabels(serviceName, "cluster", service.RedisServiceRole, cr.ObjectMeta.Labels) - annotations := generateServiceAnots(cr.ObjectMeta) + annotations := generateServiceAnots(cr.ObjectMeta, nil) if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { enableMetrics = true } objectMetaInfo := generateObjectMetaInformation(serviceName, cr.Namespace, labels, annotations) headlessObjectMetaInfo := generateObjectMetaInformation(serviceName+"-headless", cr.Namespace, labels, annotations) - err := CreateOrUpdateService(cr.Namespace, headlessObjectMetaInfo, redisClusterAsOwner(cr), false, true) + additionalObjectMetaInfo := generateObjectMetaInformation(serviceName+"-additional", cr.Namespace, labels, generateServiceAnots(cr.ObjectMeta, cr.Spec.KubernetesConfig.Service.ServiceAnnotations)) + err := CreateOrUpdateService(cr.Namespace, headlessObjectMetaInfo, redisClusterAsOwner(cr), false, true, "ClusterIP") if err != nil { logger.Error(err, "Cannot create headless service for Redis", "Setup.Type", service.RedisServiceRole) return err } - err = CreateOrUpdateService(cr.Namespace, objectMetaInfo, redisClusterAsOwner(cr), enableMetrics, false) + err = CreateOrUpdateService(cr.Namespace, objectMetaInfo, redisClusterAsOwner(cr), enableMetrics, false, "ClusterIP") if err != nil { logger.Error(err, "Cannot create service for Redis", "Setup.Type", service.RedisServiceRole) return err } + err = CreateOrUpdateService(cr.Namespace, additionalObjectMetaInfo, redisClusterAsOwner(cr), false, false, cr.Spec.KubernetesConfig.Service.ServiceType) + if err != nil { + logger.Error(err, "Cannot create additional service for Redis", "Setup.Type", service.RedisServiceRole) + return err + } return nil } diff --git a/k8sutils/redis-standalone.go b/k8sutils/redis-standalone.go index 088d0cf38..676b01d44 100644 --- a/k8sutils/redis-standalone.go +++ b/k8sutils/redis-standalone.go @@ -12,22 +12,28 @@ var ( func CreateStandaloneService(cr *redisv1beta1.Redis) error { logger := serviceLogger(cr.Namespace, cr.ObjectMeta.Name) labels := getRedisLabels(cr.ObjectMeta.Name, "standalone", "standalone", cr.ObjectMeta.Labels) - annotations := generateServiceAnots(cr.ObjectMeta) + annotations := generateServiceAnots(cr.ObjectMeta, nil) if cr.Spec.RedisExporter != nil && cr.Spec.RedisExporter.Enabled { enableMetrics = true } objectMetaInfo := generateObjectMetaInformation(cr.ObjectMeta.Name, cr.Namespace, labels, annotations) headlessObjectMetaInfo := generateObjectMetaInformation(cr.ObjectMeta.Name+"-headless", cr.Namespace, labels, annotations) - err := CreateOrUpdateService(cr.Namespace, headlessObjectMetaInfo, redisAsOwner(cr), false, true) + additionalObjectMetaInfo := generateObjectMetaInformation(cr.ObjectMeta.Name+"-additional", cr.Namespace, labels, generateServiceAnots(cr.ObjectMeta, cr.Spec.KubernetesConfig.Service.ServiceAnnotations)) + err := CreateOrUpdateService(cr.Namespace, headlessObjectMetaInfo, redisAsOwner(cr), false, true, "ClusterIP") if err != nil { logger.Error(err, "Cannot create standalone headless service for Redis") return err } - err = CreateOrUpdateService(cr.Namespace, objectMetaInfo, redisAsOwner(cr), enableMetrics, false) + err = CreateOrUpdateService(cr.Namespace, objectMetaInfo, redisAsOwner(cr), enableMetrics, false, "ClusterIP") if err != nil { logger.Error(err, "Cannot create standalone service for Redis") return err } + err = CreateOrUpdateService(cr.Namespace, additionalObjectMetaInfo, redisAsOwner(cr), false, false, cr.Spec.KubernetesConfig.Service.ServiceType) + if err != nil { + logger.Error(err, "Cannot create additional service for Redis") + return err + } return nil } diff --git a/k8sutils/services.go b/k8sutils/services.go index 9c1ede090..8e414a2a9 100644 --- a/k8sutils/services.go +++ b/k8sutils/services.go @@ -21,12 +21,12 @@ var ( ) // generateServiceDef generates service definition for Redis -func generateServiceDef(serviceMeta metav1.ObjectMeta, enableMetrics bool, ownerDef metav1.OwnerReference, headless bool) *corev1.Service { +func generateServiceDef(serviceMeta metav1.ObjectMeta, enableMetrics bool, ownerDef metav1.OwnerReference, headless bool, serviceType string) *corev1.Service { service := &corev1.Service{ TypeMeta: generateMetaInformation("Service", "v1"), ObjectMeta: serviceMeta, Spec: corev1.ServiceSpec{ - Type: generateServiceType("ClusterIP"), + Type: generateServiceType(serviceType), ClusterIP: "", Selector: serviceMeta.GetLabels(), Ports: []corev1.ServicePort{ @@ -120,9 +120,9 @@ func serviceLogger(namespace string, name string) logr.Logger { } // CreateOrUpdateService method will create or update Redis service -func CreateOrUpdateService(namespace string, serviceMeta metav1.ObjectMeta, ownerDef metav1.OwnerReference, enableMetrics, headless bool) error { +func CreateOrUpdateService(namespace string, serviceMeta metav1.ObjectMeta, ownerDef metav1.OwnerReference, enableMetrics, headless bool, serviceType string) error { logger := serviceLogger(namespace, serviceMeta.Name) - serviceDef := generateServiceDef(serviceMeta, enableMetrics, ownerDef, headless) + serviceDef := generateServiceDef(serviceMeta, enableMetrics, ownerDef, headless, serviceType) storedService, err := getService(namespace, serviceMeta.Name) if err != nil { if errors.IsNotFound(err) {