From e86884ead1005484bdb10fb30caf8f8acac2f89b Mon Sep 17 00:00:00 2001 From: Shubham Gupta <69793468+shubham-cmyk@users.noreply.github.com> Date: Mon, 13 Feb 2023 23:45:44 +0530 Subject: [PATCH] [Feature] Add Redis Sentinel Support (#408) * commit * sentinel Major Changes * example addition * example addition * add Env Variable * add Env variable * add Env Variable in sentinel * fix Issue from the int env Variable * name correction * add field for replication * fix : comments, api add : getMaster IP * add : Dynamic Client , fix : getMasterIP function * change Example * fix : bug * fixed : requirement * change : image name --- api/v1beta1/redissentinel_types.go | 78 + api/v1beta1/zz_generated.deepcopy.go | 184 ++ ...s.redis.opstreelabs.in_redissentinels.yaml | 1537 +++++++++++++++++ config/manager/kustomization.yaml | 10 +- controllers/redissentinel_controller.go | 69 + example/redis-sentinel.yaml | 22 + example/redis_sentinel/sentinel.yaml | 22 + k8sutils/client.go | 15 + k8sutils/finalizer.go | 59 + k8sutils/labels.go | 12 + k8sutils/redis-sentinel.go | 266 +++ k8sutils/statefulset.go | 13 +- main.go | 9 + 13 files changed, 2291 insertions(+), 5 deletions(-) create mode 100644 api/v1beta1/redissentinel_types.go create mode 100644 config/crd/bases/redis.redis.opstreelabs.in_redissentinels.yaml create mode 100644 controllers/redissentinel_controller.go create mode 100644 example/redis-sentinel.yaml create mode 100644 example/redis_sentinel/sentinel.yaml create mode 100644 k8sutils/redis-sentinel.go diff --git a/api/v1beta1/redissentinel_types.go b/api/v1beta1/redissentinel_types.go new file mode 100644 index 000000000..e87c8d728 --- /dev/null +++ b/api/v1beta1/redissentinel_types.go @@ -0,0 +1,78 @@ +package v1beta1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type RedisSentinelSpec struct { + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Default=3 + // +kubebuilder:validation:Not=2 + Size *int32 `json:"clusterSize"` + KubernetesConfig KubernetesConfig `json:"kubernetesConfig"` + RedisSentinelConfig *RedisSentinelConfig `json:"redisSentinelConfig,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"` + PriorityClassName string `json:"priorityClassName,omitempty"` + Affinity *corev1.Affinity `json:"affinity,omitempty"` + Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"` + TLS *TLSConfig `json:"TLS,omitempty"` + PodDisruptionBudget *RedisPodDisruptionBudget `json:"pdb,omitempty"` + // +kubebuilder:default:={initialDelaySeconds: 1, timeoutSeconds: 1, periodSeconds: 10, successThreshold: 1, failureThreshold:3} + ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"` + // +kubebuilder:default:={initialDelaySeconds: 1, timeoutSeconds: 1, periodSeconds: 10, successThreshold: 1, failureThreshold:3} + LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,11,opt,name=livenessProbe"` + Sidecars *[]Sidecar `json:"sidecars,omitempty"` + ServiceAccountName *string `json:"serviceAccountName,omitempty"` +} + +func (cr *RedisSentinelSpec) GetSentinelCounts(t string) int32 { + replica := cr.Size + return *replica +} + +type RedisSentinelConfig struct { + AdditionalSentinelConfig *string `json:"additionalSentinelConfig,omitempty"` + RedisReplicationName string `json:"redisReplicationName"` + // +kubebuilder:default:=myMaster + MasterGroupName string `json:"masterGroupName,omitempty"` + // +kubebuilder:default:="6379" + RedisPort string `json:"redisPort,omitempty"` + // +kubebuilder:default:="2" + Quorum string `json:"quorum,omitempty"` + // +kubebuilder:default:="1" + ParallelSyncs string `json:"parallelSyncs,omitempty"` + // +kubebuilder:default:="180000" + FailoverTimeout string `json:"failoverTimeout,omitempty"` + // +kubebuilder:default:="30000" + DownAfterMilliseconds string `json:"downAfterMilliseconds,omitempty"` +} + +type RedisSentinelStatus struct { +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Redis is the Schema for the redis API +type RedisSentinel struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RedisSentinelSpec `json:"spec"` + Status RedisSentinelStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RedisList contains a list of Redis +type RedisSentinelList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RedisSentinel `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RedisSentinel{}, &RedisSentinelList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 9013455e6..bd797c98f 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -713,6 +713,190 @@ func (in *RedisReplicationStatus) DeepCopy() *RedisReplicationStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSentinel) DeepCopyInto(out *RedisSentinel) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSentinel. +func (in *RedisSentinel) DeepCopy() *RedisSentinel { + if in == nil { + return nil + } + out := new(RedisSentinel) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSentinel) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSentinelConfig) DeepCopyInto(out *RedisSentinelConfig) { + *out = *in + if in.AdditionalSentinelConfig != nil { + in, out := &in.AdditionalSentinelConfig, &out.AdditionalSentinelConfig + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSentinelConfig. +func (in *RedisSentinelConfig) DeepCopy() *RedisSentinelConfig { + if in == nil { + return nil + } + out := new(RedisSentinelConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSentinelList) DeepCopyInto(out *RedisSentinelList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RedisSentinel, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSentinelList. +func (in *RedisSentinelList) DeepCopy() *RedisSentinelList { + if in == nil { + return nil + } + out := new(RedisSentinelList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisSentinelList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSentinelSpec) DeepCopyInto(out *RedisSentinelSpec) { + *out = *in + if in.Size != nil { + in, out := &in.Size, &out.Size + *out = new(int32) + **out = **in + } + in.KubernetesConfig.DeepCopyInto(&out.KubernetesConfig) + if in.RedisSentinelConfig != nil { + in, out := &in.RedisSentinelConfig, &out.RedisSentinelConfig + *out = new(RedisSentinelConfig) + (*in).DeepCopyInto(*out) + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(v1.PodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = new([]v1.Toleration) + if **in != nil { + in, out := *in, *out + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TLSConfig) + (*in).DeepCopyInto(*out) + } + if in.PodDisruptionBudget != nil { + in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget + *out = new(RedisPodDisruptionBudget) + (*in).DeepCopyInto(*out) + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(Probe) + **out = **in + } + if in.LivenessProbe != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + *out = new(Probe) + **out = **in + } + if in.Sidecars != nil { + in, out := &in.Sidecars, &out.Sidecars + *out = new([]Sidecar) + if **in != nil { + in, out := *in, *out + *out = make([]Sidecar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } + if in.ServiceAccountName != nil { + in, out := &in.ServiceAccountName, &out.ServiceAccountName + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSentinelSpec. +func (in *RedisSentinelSpec) DeepCopy() *RedisSentinelSpec { + if in == nil { + return nil + } + out := new(RedisSentinelSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisSentinelStatus) DeepCopyInto(out *RedisSentinelStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisSentinelStatus. +func (in *RedisSentinelStatus) DeepCopy() *RedisSentinelStatus { + if in == nil { + return nil + } + out := new(RedisSentinelStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisSpec) DeepCopyInto(out *RedisSpec) { *out = *in diff --git a/config/crd/bases/redis.redis.opstreelabs.in_redissentinels.yaml b/config/crd/bases/redis.redis.opstreelabs.in_redissentinels.yaml new file mode 100644 index 000000000..77b4d6f17 --- /dev/null +++ b/config/crd/bases/redis.redis.opstreelabs.in_redissentinels.yaml @@ -0,0 +1,1537 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: redissentinels.redis.redis.opstreelabs.in +spec: + group: redis.redis.opstreelabs.in + names: + kind: RedisSentinel + listKind: RedisSentinelList + plural: redissentinels + singular: redissentinel + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Redis is the Schema for the redis API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + TLS: + description: TLS Configuration for redis instances + properties: + ca: + type: string + cert: + type: string + key: + type: string + secret: + description: Reference to secret which contains the certificates + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value between + 0000 and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires decimal + values for mode bits. Defaults to 0644. Directories within + the path are not affected by this setting. This might be + in conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in the Data + field of the referenced Secret will be projected into the + volume as a file whose name is the key and content is the + value. If specified, the listed keys will be projected into + the specified paths, and unlisted keys will not be present. + If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' path + or start with '..'. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set permissions + on this file. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. If not specified, the + volume defaultMode will be used. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to map the + key to. May not be an absolute path. May not contain + the path element '..'. May not start with the string + '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys must be + defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace to + use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + required: + - secret + type: object + affinity: + description: Affinity is a group of affinity scheduling rules. + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: An array of string values. If + the operator is In or NotIn, the values + array must be non-empty. If the operator + is Exists or DoesNotExist, the values array + must be empty. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. This field is beta-level + and is only honored when PodAffinityNamespaceSelector + feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + This field is beta-level and is only honored when + PodAffinityNamespaceSelector feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied + to the union of the namespaces selected by this + field and the ones listed in the namespaces field. + null selector and null or empty namespaces list + means "this pod's namespace". An empty selector + ({}) matches all namespaces. This field is beta-level + and is only honored when PodAffinityNamespaceSelector + feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: A label selector requirement + is a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: operator represents a key's + relationship to a set of values. Valid + operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. + If the operator is Exists or DoesNotExist, + the values array must be empty. This + array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is + "In", and the values array contains only "value". + The requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list + of namespace names that the term applies to. The + term is applied to the union of the namespaces + listed in this field and the ones selected by + namespaceSelector. null or empty namespaces list + and null namespaceSelector means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaceSelector: + description: A label query over the set of namespaces + that the term applies to. The term is applied to the + union of the namespaces selected by this field and + the ones listed in the namespaces field. null selector + and null or empty namespaces list means "this pod's + namespace". An empty selector ({}) matches all namespaces. + This field is beta-level and is only honored when + PodAffinityNamespaceSelector feature is enabled. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: A label selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists and DoesNotExist. + type: string + values: + description: values is an array of string + values. If the operator is In or NotIn, + the values array must be non-empty. If the + operator is Exists or DoesNotExist, the + values array must be empty. This array is + replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator is "In", + and the values array contains only "value". The + requirements are ANDed. + type: object + type: object + namespaces: + description: namespaces specifies a static list of namespace + names that the term applies to. The term is applied + to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. null or + empty namespaces list and null namespaceSelector means + "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + clusterSize: + format: int32 + minimum: 1 + type: integer + kubernetesConfig: + description: KubernetesConfig will be the JSON struct for Basic Redis + Config + properties: + image: + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + type: string + imagePullSecrets: + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + type: array + redisSecret: + description: ExistingPasswordSecret is the struct to access the + existing secret + properties: + key: + type: string + name: + type: string + type: object + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + 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. + It includes any additional parameters necessary to perform the + update for the indicated strategy. + properties: + rollingUpdate: + description: RollingUpdate is used to communicate parameters + when Type is RollingUpdateStatefulSetStrategyType. + properties: + partition: + description: Partition indicates the ordinal at which + the StatefulSet should be partitioned. Default value + is 0. + format: int32 + type: integer + type: object + type: + description: Type indicates the type of the StatefulSetUpdateStrategy. + Default is RollingUpdate. + type: string + type: object + required: + - image + type: object + livenessProbe: + default: + failureThreshold: 3 + initialDelaySeconds: 1 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + description: Probe is a interface for ReadinessProbe and LivenessProbe + properties: + failureThreshold: + default: 3 + format: int32 + minimum: 1 + type: integer + initialDelaySeconds: + default: 1 + format: int32 + minimum: 1 + type: integer + periodSeconds: + default: 10 + format: int32 + minimum: 1 + type: integer + successThreshold: + default: 1 + format: int32 + minimum: 1 + type: integer + timeoutSeconds: + default: 1 + format: int32 + minimum: 1 + type: integer + type: object + nodeSelector: + additionalProperties: + type: string + type: object + pdb: + description: RedisPodDisruptionBudget configure a PodDisruptionBudget + on the resource (leader/follower) + properties: + enabled: + type: boolean + maxUnavailable: + format: int32 + type: integer + minAvailable: + format: int32 + type: integer + type: object + priorityClassName: + type: string + readinessProbe: + default: + failureThreshold: 3 + initialDelaySeconds: 1 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + description: Probe is a interface for ReadinessProbe and LivenessProbe + properties: + failureThreshold: + default: 3 + format: int32 + minimum: 1 + type: integer + initialDelaySeconds: + default: 1 + format: int32 + minimum: 1 + type: integer + periodSeconds: + default: 10 + format: int32 + minimum: 1 + type: integer + successThreshold: + default: 1 + format: int32 + minimum: 1 + type: integer + timeoutSeconds: + default: 1 + format: int32 + minimum: 1 + type: integer + type: object + redisSentinelConfig: + properties: + additionalSentinelConfig: + type: string + downAfterMilliseconds: + default: "30000" + type: string + failoverTimeout: + default: "180000" + type: string + masterGroupName: + default: myMaster + type: string + parallelSyncs: + default: "1" + type: string + quorum: + default: "2" + type: string + redisPort: + default: "6379" + type: string + redisReplicationName: + type: string + required: + - redisReplicationName + type: object + securityContext: + description: PodSecurityContext holds pod-level security attributes + and common container settings. Some fields are also present in container.securityContext. Field + values of container.securityContext take precedence over field values + of PodSecurityContext. + properties: + fsGroup: + description: "A special supplemental group that applies to all + containers in a pod. Some volume types allow the Kubelet to + change the ownership of that volume to be owned by the pod: + \n 1. The owning GID will be the FSGroup 2. The setgid bit is + set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- \n If unset, + the Kubelet will not modify the ownership and permissions of + any volume. Note that this field cannot be set when spec.os.name + is windows." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will have + no effect on ephemeral volume types such as: secret, configmaps + and emptydir. Valid values are "OnRootMismatch" and "Always". + If not specified, "Always" is used. Note that this field cannot + be set when spec.os.name is windows.' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container process. + Uses runtime default if unset. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail to start + the container if it does. If unset or false, no such validation + will be performed. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value specified + in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. Note that this field cannot + be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to + the container. + type: string + role: + description: Role is a SELinux role label that applies to + the container. + type: string + type: + description: Type is a SELinux type label that applies to + the container. + type: string + user: + description: User is a SELinux user label that applies to + the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers in this + pod. Note that this field cannot be set when spec.os.name is + windows. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must be + preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a profile + defined in a file on the node should be used. RuntimeDefault + - the container runtime default profile should be used. + Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process run + in each container, in addition to the container's primary GID. If + unspecified, no groups will be added to any container. Note + that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used for + the pod. Pods with unsupported sysctls (by the container runtime) + might fail to launch. Note that this field cannot be set when + spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. Note + that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named by + the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA + credential spec to use. + type: string + hostProcess: + description: HostProcess determines if a container should + be run as a 'Host Process' container. This field is alpha-level + and will only be honored by components that enable the WindowsHostProcessContainers + feature flag. Setting this field without the feature flag + will result in errors when validating the Pod. All of a + Pod's containers must have the same effective HostProcess + value (it is not allowed to have a mix of HostProcess containers + and non-HostProcess containers). In addition, if HostProcess + is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in PodSecurityContext. + If set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: string + type: object + type: object + serviceAccountName: + type: string + sidecars: + items: + description: Sidecar for each Redis pods + properties: + env: + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must be + a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in + the container and any service environment variables. + If a variable cannot be resolved, the reference in the + input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) + syntax: i.e. "$$(VAR_NAME)" will produce the string + literal "$(VAR_NAME)". Escaped references will never + be expanded, regardless of whether the variable exists + or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the + specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the + exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + image: + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + type: string + name: + type: string + resources: + description: ResourceRequirements describes the compute resource + requirements. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. More info: + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/' + type: object + type: object + required: + - image + - name + type: object + type: array + tolerations: + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + required: + - clusterSize + - kubernetesConfig + type: object + status: + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2bcd3eeaa..05c896cef 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,6 +5,12 @@ generatorOptions: disableNameSuffixHash: true configMapGenerator: -- name: manager-config - files: +- files: - controller_manager_config.yaml + name: manager-config +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: shubham192001/redis-operator + newTag: v0.13.0 diff --git a/controllers/redissentinel_controller.go b/controllers/redissentinel_controller.go new file mode 100644 index 000000000..c47dd63ab --- /dev/null +++ b/controllers/redissentinel_controller.go @@ -0,0 +1,69 @@ +package controllers + +import ( + "context" + "redis-operator/k8sutils" + "time" + + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + redisv1beta1 "redis-operator/api/v1beta1" +) + +// RedisSentinelReconciler reconciles a RedisSentinel object +type RedisSentinelReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +// Reconcile is part of the main kubernetes reconciliation loop which aims +func (r *RedisSentinelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + reqLogger := r.Log.WithValues("Request.Namespace", req.Namespace, "Request.Name", req.Name) + reqLogger.Info("Reconciling opstree redis controller") + instance := &redisv1beta1.RedisSentinel{} + + err := r.Client.Get(context.TODO(), req.NamespacedName, instance) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + // Get total Sentinel Replicas + // sentinelReplicas := instance.Spec.GetSentinelCounts("sentinel") + + if err := k8sutils.HandleRedisSentinelFinalizer(instance, r.Client); err != nil { + return ctrl.Result{RequeueAfter: time.Second * 60}, err + } + + if err := k8sutils.AddRedisSentinelFinalizer(instance, r.Client); err != nil { + return ctrl.Result{RequeueAfter: time.Second * 60}, err + } + + // Create Redis Sentinel + err = k8sutils.CreateRedisSentinel(instance) + if err != nil { + return ctrl.Result{}, err + } + + // Create the Service for Redis Sentinel + err = k8sutils.CreateRedisSentinelService(instance) + if err != nil { + return ctrl.Result{}, err + } + + reqLogger.Info("Will reconcile redis operator in again 10 seconds") + return ctrl.Result{RequeueAfter: time.Second * 10}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RedisSentinelReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&redisv1beta1.RedisSentinel{}). + Complete(r) +} diff --git a/example/redis-sentinel.yaml b/example/redis-sentinel.yaml new file mode 100644 index 000000000..4eec84b51 --- /dev/null +++ b/example/redis-sentinel.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta1 +kind: RedisSentinel +metadata: + name: redis-sentinel +spec: + clusterSize: 3 + securityContext: + runAsUser: 1000 + fsGroup: 1000 + redisSentinelConfig: + redisReplicationName : redis-replication + kubernetesConfig: + image: quay.io/opstree/redis-sentinel:v7.0.7 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi diff --git a/example/redis_sentinel/sentinel.yaml b/example/redis_sentinel/sentinel.yaml new file mode 100644 index 000000000..4eec84b51 --- /dev/null +++ b/example/redis_sentinel/sentinel.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: redis.redis.opstreelabs.in/v1beta1 +kind: RedisSentinel +metadata: + name: redis-sentinel +spec: + clusterSize: 3 + securityContext: + runAsUser: 1000 + fsGroup: 1000 + redisSentinelConfig: + redisReplicationName : redis-replication + kubernetesConfig: + image: quay.io/opstree/redis-sentinel:v7.0.7 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi diff --git a/k8sutils/client.go b/k8sutils/client.go index 1c5ce2a26..54078ab7e 100644 --- a/k8sutils/client.go +++ b/k8sutils/client.go @@ -1,6 +1,8 @@ package k8sutils import ( + // custom "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -19,6 +21,19 @@ func generateK8sClient() *kubernetes.Clientset { return clientset } +// generateK8sClient create Dynamic client for kubernetes +func generateK8sDynamicClient() dynamic.Interface { + config, err := generateK8sConfig() + if err != nil { + panic(err.Error()) + } + dynamicClientset, err := dynamic.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + return dynamicClientset +} + // generateK8sConfig will load the kube config file func generateK8sConfig() (*rest.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() diff --git a/k8sutils/finalizer.go b/k8sutils/finalizer.go index 58d6d5dcf..f26669de1 100644 --- a/k8sutils/finalizer.go +++ b/k8sutils/finalizer.go @@ -16,6 +16,7 @@ const ( RedisFinalizer string = "redisFinalizer" RedisClusterFinalizer string = "redisClusterFinalizer" RedisReplicationFinalizer string = "redisReplicationFinalizer" + RedisSentinelFinalizer string = "redisSentinelFinalizer" ) // finalizeLogger will generate logging interface @@ -96,6 +97,30 @@ func HandleRedisReplicationFinalizer(cr *redisv1beta1.RedisReplication, cl clien return nil } +// HandleRedisSentinelFinalizer finalize resource if instance is marked to be deleted +func HandleRedisSentinelFinalizer(cr *redisv1beta1.RedisSentinel, cl client.Client) error { + logger := finalizerLogger(cr.Namespace, RedisSentinelFinalizer) + if cr.GetDeletionTimestamp() != nil { + if controllerutil.ContainsFinalizer(cr, RedisSentinelFinalizer) { + if err := finalizeRedisSentinelServices(cr); err != nil { + return err + } + if err := finalizeRedisSentinelPVC(cr); err != nil { + return err + } + if err := finalizeRedisSentinelStatefulSets(cr); err != nil { + return err + } + controllerutil.RemoveFinalizer(cr, RedisSentinelFinalizer) + if err := cl.Update(context.TODO(), cr); err != nil { + logger.Error(err, "Could not remove finalizer "+RedisSentinelFinalizer) + return err + } + } + } + return nil +} + // AddRedisFinalizer add finalizer for graceful deletion func AddRedisFinalizer(cr *redisv1beta1.Redis, cl client.Client) error { if !controllerutil.ContainsFinalizer(cr, RedisFinalizer) { @@ -123,6 +148,15 @@ func AddRedisReplicationFinalizer(cr *redisv1beta1.RedisReplication, cl client.C return nil } +// AddRedisSentinelFinalizer add finalizer for graceful deletion +func AddRedisSentinelFinalizer(cr *redisv1beta1.RedisSentinel, cl client.Client) error { + if !controllerutil.ContainsFinalizer(cr, RedisSentinelFinalizer) { + controllerutil.AddFinalizer(cr, RedisSentinelFinalizer) + return cl.Update(context.TODO(), cr) + } + return nil +} + // finalizeRedisServices delete Services func finalizeRedisServices(cr *redisv1beta1.Redis) error { logger := finalizerLogger(cr.Namespace, RedisFinalizer) @@ -165,6 +199,21 @@ func finalizeRedisReplicationServices(cr *redisv1beta1.RedisReplication) error { return nil } +// finalizeRedisSentinelServices delete Services +func finalizeRedisSentinelServices(cr *redisv1beta1.RedisSentinel) error { + logger := finalizerLogger(cr.Namespace, RedisSentinelFinalizer) + serviceName, headlessServiceName := cr.Name, cr.Name+"-headless" + for _, svc := range []string{serviceName, headlessServiceName} { + err := generateK8sClient().CoreV1().Services(cr.Namespace).Delete(context.TODO(), svc, metav1.DeleteOptions{}) + if err != nil && !errors.IsNotFound(err) { + logger.Error(err, "Could not delete service "+svc) + return err + } + } + return nil + +} + // finalizeRedisPVC delete PVC func finalizeRedisPVC(cr *redisv1beta1.Redis) error { logger := finalizerLogger(cr.Namespace, RedisFinalizer) @@ -208,6 +257,11 @@ func finalizeRedisReplicationPVC(cr *redisv1beta1.RedisReplication) error { return nil } +func finalizeRedisSentinelPVC(cr *redisv1beta1.RedisSentinel) error { + + return nil +} + // finalizeRedisStatefulSet delete statefulset for Redis func finalizeRedisStatefulSet(cr *redisv1beta1.Redis) error { logger := finalizerLogger(cr.Namespace, RedisFinalizer) @@ -242,3 +296,8 @@ func finalizeRedisReplicationStatefulSets(cr *redisv1beta1.RedisReplication) err } return nil } + +func finalizeRedisSentinelStatefulSets(cr *redisv1beta1.RedisSentinel) error { + + return nil +} diff --git a/k8sutils/labels.go b/k8sutils/labels.go index fb006269a..9b19a87e6 100644 --- a/k8sutils/labels.go +++ b/k8sutils/labels.go @@ -65,6 +65,18 @@ func redisReplicationAsOwner(cr *redisv1beta1.RedisReplication) metav1.OwnerRefe } } +// RedisSentinelAsOwner generates and returns object refernece +func redisSentinelAsOwner(cr *redisv1beta1.RedisSentinel) metav1.OwnerReference { + trueVar := true + return metav1.OwnerReference{ + APIVersion: cr.APIVersion, + Kind: cr.Kind, + Name: cr.Name, + UID: cr.UID, + Controller: &trueVar, + } +} + // generateStatefulSetsAnots generates and returns statefulsets annotations func generateStatefulSetsAnots(stsMeta metav1.ObjectMeta) map[string]string { anots := map[string]string{ diff --git a/k8sutils/redis-sentinel.go b/k8sutils/redis-sentinel.go new file mode 100644 index 000000000..e52abfb95 --- /dev/null +++ b/k8sutils/redis-sentinel.go @@ -0,0 +1,266 @@ +package k8sutils + +import ( + "context" + "encoding/json" + redisv1beta1 "redis-operator/api/v1beta1" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// RedisSentinelSTS is a interface to call Redis Statefulset function +type RedisSentinelSTS struct { + RedisStateFulType string + ExternalConfig *string + Affinity *corev1.Affinity `json:"affinity,omitempty"` + ReadinessProbe *redisv1beta1.Probe + LivenessProbe *redisv1beta1.Probe +} + +// RedisSentinelService is a interface to call Redis Service function +type RedisSentinelService struct { + RedisServiceRole string +} + +type RedisReplicationObject struct { + RedisReplication *redisv1beta1.RedisReplication +} + +// Redis Sentinel Create the Redis Sentinel Setup +func CreateRedisSentinel(cr *redisv1beta1.RedisSentinel) error { + prop := RedisSentinelSTS{ + RedisStateFulType: "sentinel", + Affinity: cr.Spec.Affinity, + ReadinessProbe: cr.Spec.ReadinessProbe, + LivenessProbe: cr.Spec.LivenessProbe, + } + + if cr.Spec.RedisSentinelConfig.AdditionalSentinelConfig != nil { + prop.ExternalConfig = cr.Spec.RedisSentinelConfig.AdditionalSentinelConfig + } + + return prop.CreateRedisSentinelSetup(cr) + +} + +// Create RedisSentinel Service +func CreateRedisSentinelService(cr *redisv1beta1.RedisSentinel) error { + + prop := RedisSentinelService{ + RedisServiceRole: "sentinel", + } + return prop.CreateRedisSentinelService(cr) +} + +// Create Redis Sentinel Cluster Setup +func (service RedisSentinelSTS) CreateRedisSentinelSetup(cr *redisv1beta1.RedisSentinel) error { + + stateFulName := cr.ObjectMeta.Name + logger := statefulSetLogger(cr.Namespace, stateFulName) + labels := getRedisLabels(stateFulName, "cluster", service.RedisStateFulType, cr.ObjectMeta.Labels) + annotations := generateStatefulSetsAnots(cr.ObjectMeta) + objectMetaInfo := generateObjectMetaInformation(stateFulName, cr.Namespace, labels, annotations) + err := CreateOrUpdateStateFul( + cr.Namespace, + objectMetaInfo, + generateRedisSentinelParams(cr, service.getSentinelCount(cr), service.ExternalConfig, service.Affinity), + redisSentinelAsOwner(cr), + generateRedisSentinelContainerParams(cr, service.ReadinessProbe, service.LivenessProbe), + cr.Spec.Sidecars, + ) + + if err != nil { + logger.Error(err, "Cannot create Sentinel statefulset for Redis") + return err + } + return nil +} + +// Create Redis Sentile Params for the statefulset +func generateRedisSentinelParams(cr *redisv1beta1.RedisSentinel, replicas int32, externalConfig *string, affinity *corev1.Affinity) statefulSetParameters { + + res := statefulSetParameters{ + Metadata: cr.ObjectMeta, + Replicas: &replicas, + NodeSelector: cr.Spec.NodeSelector, + SecurityContext: cr.Spec.SecurityContext, + PriorityClassName: cr.Spec.PriorityClassName, + Affinity: affinity, + Tolerations: cr.Spec.Tolerations, + ServiceAccountName: cr.Spec.ServiceAccountName, + UpdateStrategy: cr.Spec.KubernetesConfig.UpdateStrategy, + } + + if cr.Spec.KubernetesConfig.ImagePullSecrets != nil { + res.ImagePullSecrets = cr.Spec.KubernetesConfig.ImagePullSecrets + } + if externalConfig != nil { + res.ExternalConfig = externalConfig + } + + return res +} + +// Create Redis Sentinel Statefulset Container Params +func generateRedisSentinelContainerParams(cr *redisv1beta1.RedisSentinel, readinessProbeDef *redisv1beta1.Probe, livenessProbeDef *redisv1beta1.Probe) containerParameters { + + trueProperty := true + falseProperty := false + containerProp := containerParameters{ + Role: "sentinel", + Image: cr.Spec.KubernetesConfig.Image, + ImagePullPolicy: cr.Spec.KubernetesConfig.ImagePullPolicy, + Resources: cr.Spec.KubernetesConfig.Resources, + AdditionalEnvVariable: getSentinelEnvVariable(cr), + } + if cr.Spec.KubernetesConfig.ExistingPasswordSecret != nil { + containerProp.EnabledPassword = &trueProperty + containerProp.SecretName = cr.Spec.KubernetesConfig.ExistingPasswordSecret.Name + containerProp.SecretKey = cr.Spec.KubernetesConfig.ExistingPasswordSecret.Key + } else { + containerProp.EnabledPassword = &falseProperty + } + if readinessProbeDef != nil { + containerProp.ReadinessProbe = readinessProbeDef + } + if livenessProbeDef != nil { + containerProp.LivenessProbe = livenessProbeDef + } + if cr.Spec.TLS != nil { + containerProp.TLSConfig = cr.Spec.TLS + } + + return containerProp + +} + +// Get the Count of the Sentinel +func (service RedisSentinelSTS) getSentinelCount(cr *redisv1beta1.RedisSentinel) int32 { + return cr.Spec.GetSentinelCounts(service.RedisStateFulType) +} + +// Create the Service for redis sentinel +func (service RedisSentinelService) CreateRedisSentinelService(cr *redisv1beta1.RedisSentinel) error { + serviceName := cr.ObjectMeta.Name + "-" + service.RedisServiceRole + logger := serviceLogger(cr.Namespace, serviceName) + labels := getRedisLabels(serviceName, "cluster", service.RedisServiceRole, cr.ObjectMeta.Labels) + annotations := generateServiceAnots(cr.ObjectMeta, nil) + + additionalServiceAnnotations := map[string]string{} + if cr.Spec.KubernetesConfig.Service != nil { + additionalServiceAnnotations = cr.Spec.KubernetesConfig.Service.ServiceAnnotations + } + + objectMetaInfo := generateObjectMetaInformation(serviceName, cr.Namespace, labels, annotations) + headlessObjectMetaInfo := generateObjectMetaInformation(serviceName+"-headless", cr.Namespace, labels, annotations) + additionalObjectMetaInfo := generateObjectMetaInformation(serviceName+"-additional", cr.Namespace, labels, generateServiceAnots(cr.ObjectMeta, additionalServiceAnnotations)) + + err := CreateOrUpdateService(cr.Namespace, headlessObjectMetaInfo, redisSentinelAsOwner(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, redisSentinelAsOwner(cr), enableMetrics, false, "ClusterIP") + if err != nil { + logger.Error(err, "Cannot create service for Redis", "Setup.Type", service.RedisServiceRole) + return err + } + + additionalServiceType := "ClusterIP" + if cr.Spec.KubernetesConfig.Service != nil { + additionalServiceType = cr.Spec.KubernetesConfig.Service.ServiceType + } + err = CreateOrUpdateService(cr.Namespace, additionalObjectMetaInfo, redisSentinelAsOwner(cr), false, false, additionalServiceType) + if err != nil { + logger.Error(err, "Cannot create additional service for Redis", "Setup.Type", service.RedisServiceRole) + return err + } + return nil + +} + +func getSentinelEnvVariable(cr *redisv1beta1.RedisSentinel) *[]corev1.EnvVar { + + envVar := &[]corev1.EnvVar{ + { + Name: "MASTER_GROUP_NAME", + Value: cr.Spec.RedisSentinelConfig.MasterGroupName, + }, + { + Name: "IP", + Value: getRedisReplicationMasterIP(cr), + }, + { + Name: "PORT", + Value: cr.Spec.RedisSentinelConfig.RedisPort, + }, + { + Name: "QUORUM", + Value: cr.Spec.RedisSentinelConfig.Quorum, + }, + { + Name: "DOWN_AFTER_MILLISECONDS", + Value: cr.Spec.RedisSentinelConfig.DownAfterMilliseconds, + }, + { + Name: "PARALLEL_SYNCS", + Value: cr.Spec.RedisSentinelConfig.ParallelSyncs, + }, + { + Name: "FAILOVER_TIMEOUT", + Value: cr.Spec.RedisSentinelConfig.FailoverTimeout, + }, + } + + return envVar + +} + +func getRedisReplicationMasterIP(cr *redisv1beta1.RedisSentinel) string { + logger := generateRedisManagerLogger(cr.Namespace, cr.ObjectMeta.Name) + + replicationName := cr.Spec.RedisSentinelConfig.RedisReplicationName + replicationNamespace := cr.Namespace + + var replicationInstance redisv1beta1.RedisReplication + + // Get Request on Dynamic Client + customObject, err := generateK8sDynamicClient().Resource(schema.GroupVersionResource{ + Group: "redis.redis.opstreelabs.in", + Version: "v1beta1", + Resource: "redisreplications", + }).Namespace(replicationNamespace).Get(context.TODO(), replicationName, v1.GetOptions{}) + + if err != nil { + logger.Error(err, "Failed to Execute Get Request", "replication name", replicationName, "namespace", replicationNamespace) + return "" + } else { + logger.Info("Successfully Execute the Get Request", "replication name", replicationName, "namespace", replicationNamespace) + } + + // Marshal CustomObject to JSON + replicationJSON, err := customObject.MarshalJSON() + if err != nil { + logger.Error(err, "Failed To Load JSON") + return "" + } + + // Unmarshal The JSON on Object + if err := json.Unmarshal(replicationJSON, &replicationInstance); err != nil { + logger.Error(err, "Failed To Unmarshal JSON over the Object") + return "" + } + + masterPods := GetRedisNodesByRole(&replicationInstance, "master") + realMasterPod := checkAttachedSlave(&replicationInstance, masterPods) + realMasterInfo := RedisDetails{ + PodName: realMasterPod, + Namespace: replicationNamespace, + } + + realMasterPodIP := getRedisServerIP(realMasterInfo) + return realMasterPodIP + +} diff --git a/k8sutils/statefulset.go b/k8sutils/statefulset.go index 0cdfa8c70..280ef3c82 100644 --- a/k8sutils/statefulset.go +++ b/k8sutils/statefulset.go @@ -59,6 +59,7 @@ type containerParameters struct { TLSConfig *redisv1beta1.TLSConfig ReadinessProbe *redisv1beta1.Probe LivenessProbe *redisv1beta1.Probe + AdditionalEnvVariable *[]corev1.EnvVar AdditionalVolume []corev1.Volume AdditionalMountPath []corev1.VolumeMount } @@ -326,6 +327,12 @@ func generateContainerDef(name string, containerParams containerParameters, enab } containerDefinition = append(containerDefinition, container) } + + if containerParams.AdditionalEnvVariable != nil { + containerDefinition[0].Env = append(containerDefinition[0].Env, *containerParams.AdditionalEnvVariable...) + + } + return containerDefinition } @@ -449,7 +456,7 @@ func getProbeInfo(probe *redisv1beta1.Probe) *corev1.Probe { } // getEnvironmentVariables returns all the required Environment Variables -func getEnvironmentVariables(role string, enabledMetric bool, enabledPassword *bool, secretName *string, secretKey *string, persistenceEnabled *bool, extraEnv *[]corev1.EnvVar, tlsConfig *redisv1beta1.TLSConfig) []corev1.EnvVar { +func getEnvironmentVariables(role string, enabledMetric bool, enabledPassword *bool, secretName *string, secretKey *string, persistenceEnabled *bool, exporterEnvVar *[]corev1.EnvVar, tlsConfig *redisv1beta1.TLSConfig) []corev1.EnvVar { envVars := []corev1.EnvVar{ {Name: "SERVER_MODE", Value: role}, {Name: "SETUP_MODE", Value: role}, @@ -501,8 +508,8 @@ func getEnvironmentVariables(role string, enabledMetric bool, enabledPassword *b envVars = append(envVars, corev1.EnvVar{Name: "PERSISTENCE_ENABLED", Value: "true"}) } - if extraEnv != nil { - envVars = append(envVars, *extraEnv...) + if exporterEnvVar != nil { + envVars = append(envVars, *exporterEnvVar...) } sort.SliceStable(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name diff --git a/main.go b/main.go index 5b6557538..44c9b5bc8 100644 --- a/main.go +++ b/main.go @@ -108,6 +108,15 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "RedisReplication") os.Exit(1) } + + if err = (&controllers.RedisSentinelReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("RedisSentinel"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RedisSentinel") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil {