1. 环境说明
源码链接:https://github.com/OT-CONTAINER-KIT/redis-operator
分支/Tag:v0.13.0
Kubernetes
版本:v1.23.0
,该值从工程中的go.mod
文件中得知
Controller-runtime
版本:v0.11.0
,该值从工程中的go.mod
文件中得知
2. 架构
3. 目录结构
在分析源码之前,我们先来看可以看项目结构,如下所示,写过Operator
的小伙伴肯定非常的熟悉,这个项目结构实际上就是通过kube-buidler/operator-sdk
创建出来的标准工程结构,我们从上倒下简单过一遍
api
目录中为CRD
的Spec
定义,譬如这里是redis_types.go
中。这里面定义的都是业务相关的元数据,也是用户使用这个operator
可以提交的yaml
中的字段定义。当执行make install
的时候,会根据这里的定义以及config
目录中的配置生成CRD
以及相关的RBAC
资源清单
config
目录比较简单,这里面是通过kustomize
组织的一系列配置,通过kube-buidler/operator-sdk
创建工程时,会自动创建这个目录,里面的配置大多数都不需要更改,我们只需要改一些自己关心的信息,譬如Operator
的namespace
以及名字
controllers
目录则是我们今天分析的重点,Operator.Reconciler
就是在这个目录中被实现的,一旦Operator
相关的资源清单发生改变,Informer
就会通知Operator
,最终会调用到Reconcile
。显然,Reconcile
中的逻辑就是部署Redis
高可用集群
dashborad
目录并非标注的Operator
目录,大致看了一下文件内容,这个目录下保存就是Grafana
配置
example
目录中则是测试Operator
的资源清单
k8sutils
顾名思义,就是实现Redis-Operator
的一些工具方法,该目录也并非标准kube-buidler/operator-sdk
目录
static
中为Redis-Operator
的Logo
以及架构图
.
├── api
│ └── v1beta1
│ ├── common_types.go
│ ├── groupversion_info.go
│ ├── rediscluster_types.go
│ ├── redis_types.go
│ └── zz_generated.deepcopy.go
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── config
│ ├── certmanager
│ │ ├── certificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── crd
│ │ ├── bases
│ │ ├── kustomization.yaml
│ │ ├── kustomizeconfig.yaml
│ │ └── patches
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ ├── monitor.yaml
│ │ └── redis-servicemonitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── rediscluster_editor_role.yaml
│ │ ├── rediscluster_viewer_role.yaml
│ │ ├── redis_editor_role.yaml
│ │ ├── redis_viewer_role.yaml
│ │ ├── role_binding.yaml
│ │ ├── role.yaml
│ │ └── serviceaccount.yaml
│ ├── samples
│ │ ├── kustomization.yaml
│ │ └── redis_v1beta1_redis.yaml
│ └── scorecard
│ ├── bases
│ ├── kustomization.yaml
│ └── patches
├── CONTRIBUTING.md
├── controllers
│ ├── rediscluster_controller.go
│ ├── redis_controller.go
│ └── suite_test.go
├── dashboards
│ └── redis-operator-cluster.json
├── Dockerfile
├── docs
│ ├── app.yaml
│ ├── assets
│ │ ├── icons
│ │ ├── scss
│ │ └── templates
│ ├── cloudbuild.yaml
│ ├── config.toml
│ ├── content
│ │ └── en
│ ├── credits
│ ├── gen-api-docs.sh
│ ├── go.mod
│ ├── go.sum
│ ├── handler.go
│ ├── handler_test.go
│ ├── htmltest.yaml
│ ├── layouts
│ │ ├── partials
│ │ └── shortcodes
│ ├── main.go
│ ├── netlify.toml
│ ├── package.json
│ ├── static
│ │ ├── css
│ │ ├── diagrams
│ │ ├── favicons
│ │ ├── images
│ │ ├── js
│ │ └── redis-operator.cast
│ ├── themes
│ │ └── docsy
│ └── vanity.yaml
├── example
│ ├── additional_config
│ │ ├── clusterd.yaml
│ │ ├── configmap.yaml
│ │ └── standalone.yaml
│ ├── advance_config
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── affinity
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── disruption_budget
│ │ └── clusterd.yaml
│ ├── eks-cluster.yaml
│ ├── external_service
│ │ ├── clusterd.yaml
│ │ ├── cluster-svc.yaml
│ │ ├── standalone-svc.yaml
│ │ └── standalone.yaml
│ ├── password_protected
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── private_registry
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── probes
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── redis-cluster.yaml
│ ├── redis_monitoring
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ ├── redis-standalone.yaml
│ ├── tls_enabled
│ │ ├── redis-cluster.yaml
│ │ └── redis-standalone.yaml
│ ├── upgrade-strategy
│ │ ├── clusterd.yaml
│ │ └── standalone.yaml
│ └── volume_mount
│ ├── configmap.yaml
│ ├── redis-cluster.yaml
│ └── redis-standalone.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── install-operator.sh
├── k8sutils
│ ├── client.go
│ ├── finalizer.go
│ ├── labels.go
│ ├── poddisruption.go
│ ├── redis-cluster.go
│ ├── redis.go
│ ├── redis-standalone.go
│ ├── redis_test.go
│ ├── secrets.go
│ ├── services.go
│ └── statefulset.go
├── LICENSE
├── main.go
├── Makefile
├── PROJECT
├── README.md
├── SECURITY.md
├── static
│ ├── redis-operator-architecture.png
│ ├── redis-operator-logo.png
│ ├── redis-operator-logo.svg
│ ├── redis-operator-logo-wn.svg
│ └── redis-operator.png
└── USED_BY_ORGANIZATIONS.md
4. 手动搭建Redis集群
手动搭建Redis集群 |
目的:手动搭建Redis
集群是为了能够理解搭建一个高可用的Redis
需要哪些操作,如果使用Operator
来搭建,Operator
必然也需要实现对应的逻辑
5. 源码分析
5.1. KubernetesConfig
我们先来看看KubernetesConfig
是如何定义的,定义如下,结合Redis-Operator
的目标,该定义主要是为了获取Redis
相关的配置,譬如你需要使用的Redis
版本,这里通过Image
来控制,镜像拉去策略,以及将来运行Redis
的Container
的资源限制。
Image
:显然,Image
表示的是Redis
的镜像地址,通过该镜像地址,我们可以控制需要的Redis
版本ImagePullPolicy
:这个字段并不陌生,接触过K8S
一两天的人都知道该字段适用于控制镜像拉去策略Resources
:用于控制Redis.Container
的资源限制ExistingPasswordSecret
:Redis
可以设置密码,如果设置了密码,通过该字段指定密码在哪个K8S
的Secret
的哪个Key
当中ImagePullSecrets
:这里应该是针对私仓的控制UpdateStrategy
:有状态应用的更新策略,从这个字段可以推测,Redis-Operator
最终会使用StatefulSet
来控制Redis
集群
type KubernetesConfig struct {
Image string `json:"image"`
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
ExistingPasswordSecret *ExistingPasswordSecret `json:"redisSecret,omitempty"`
ImagePullSecrets *[]corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
UpdateStrategy appsv1.StatefulSetUpdateStrategy `json:"updateStrategy,omitempty"`
}
5.2. RedisSpec
RedisSpec |
我们继续看看RedsiSpec
中是如何定义的,这里面的数据肯定非常的重要,因为它影响着Operator
的行为。实际上通过阅读RedisController
中的代码可以知道,Redis
资源实际上是一个单节点定义。
RedisSpec
中定义着Redis
期望的状态,我们一起来看看这些字段的含义:
KubernetesConfig
:这个在上面已经解释过了,这里面定义了Redis
的镜像地址,镜像拉去策略,资源限制,密码,以及升级策略RedisExporter
:Redis
早于Prometheus
,因此并没有暴露相关的指标,因此需要一个Exporter
来暴露Redis
的相关指标RedisConfig
:Redis
的扩展配置Storage
:Redis
的存储配置,其实就是一个PVC
NodeSelector
:Node
选择,如果需要把Redis
部署在特定的节点上,可以使用这个标签SecurityContext
:PriorityClassName
:定义Pod
的优先级,这个涉及到kube-scheduler
的抢占式调度Affinity
:亲和性的配置Tolerations
:容忍配置,即是否可以把Redis
部署在打了污点的节点上TLS
:Redis
的TLS
配置ReadinessProbe
:就绪探针LivenessProbe
:存活探针Sidecars
:也就是当前流行的Sidecar
,当用户想要在Redis POD
中部署其它容器时可以使用此配置ServiceAccountName
:用于RBAC
权限控制
// api/v1beta1/redis_types.go
type RedisSpec struct {
KubernetesConfig KubernetesConfig `json:"kubernetesConfig"`
RedisExporter *RedisExporter `json:"redisExporter,omitempty"`
RedisConfig *RedisConfig `json:"redisConfig,omitempty"`
Storage *Storage `json:"storage,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"`
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,11,opt,name=livenessProbe"`
Sidecars *[]Sidecar `json:"sidecars,omitempty"`
ServiceAccountName *string `json:"serviceAccountName,omitempty"`
}
5.3. RedisLeader
RedisLeader |
既然是Redis
集群,那么一定有Leader
和Follower
之分,我们一起来看看RedisLeader
是如何定义的:
Replicas
:用于定义Leader
的数量,显然,根据常用的Raft
选举算法,Leader
的数量一般定义为计数个,且大于三个RedisConfig
:用于定义Leader
的配置Affinity
:用于定义Leader
的亲和性,由于Leader
和Follower
的特性不同,他们可能需要部署在特定的节点上PodDisruptionBuget
:ReadinessProbe
:Leader
就绪探针LivenessProbe
:Leader
的存活探针
// api/v1beta1/rediscluster_types.go
type RedisLeader struct {
Replicas *int32 `json:"replicas,omitempty"`
RedisConfig *RedisConfig `json:"redisConfig,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`
PodDisruptionBudget *RedisPodDisruptionBudget `json:"pdb,omitempty"`
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,11,opt,name=livenessProbe"`
}
5.4. RedisFollower
RedisFollower |
看完RedisLeader
的定义,在看到RedisFollower
的定义,就不难理解了,定时是一摸一样的,只不过抽选的角色为Follower
而已
// api/v1beta1/rediscluster_types.go
type RedisFollower struct {
Replicas *int32 `json:"replicas,omitempty"`
RedisConfig *RedisConfig `json:"redisConfig,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`
PodDisruptionBudget *RedisPodDisruptionBudget `json:"pdb,omitempty"`
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,11,opt,name=livenessProbe"`
}
5.5. RedisClusterSpec
RedisClusterSpec |
我们再来看看RedisClusterSpec
,该数据结构定义了我们期望的Redisg
集群
Size
:集群的大小KubernetesConfig
:这个在上面已经解释过了,这里面定义了Redis
的镜像地址,镜像拉去策略,资源限制,密码,以及升级策略RedisLeader
:用于定义RedisLeader
相关元数据RedisFollower
:定义RedisFollowder
相关元数据RedisExporter
:RedisExporter
指标采集,用于导出给Prometheus
Storage
:存储定义,就是一个PVC
NodeSelector
:Node
标签选择器SecurityContext
:PriorityClassName
:Tolerations
:容忍Resource
:资源限制TLS
:Redis TLS
配置Sicecars
:sidecar
模式ServiceAccountName
:用于RBAC
权限控制PersistenceEnabled
:是否需要持久化数据
// api/v1beta1/rediscluster_types.go
type RedisClusterSpec struct {
Size *int32 `json:"clusterSize"`
KubernetesConfig KubernetesConfig `json:"kubernetesConfig"`
ClusterVersion *string `json:"clusterVersion,omitempty"`
RedisLeader RedisLeader `json:"redisLeader,omitempty"`
RedisFollower RedisFollower `json:"redisFollower,omitempty"`
RedisExporter *RedisExporter `json:"redisExporter,omitempty"`
Storage *Storage `json:"storage,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
SecurityContext *corev1.PodSecurityContext `json:"securityContext,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"`
Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"`
Resources *corev1.ResourceRequirements `json:"resources,omitempty"`
TLS *TLSConfig `json:"TLS,omitempty"`
Sidecars *[]Sidecar `json:"sidecars,omitempty"`
ServiceAccountName *string `json:"serviceAccountName,omitempty"`
PersistenceEnabled *bool `json:"persistenceEnabled,omitempty"`
}
5.6. RedisController
RedisController |
可以看到RedisController
非常的简短,具体逻辑如下:
- 1、通过
ApiServer
获取当前的Redis
资源 - 2、如果发现
Redis
资源打了redis.opstreelabs.in/skip-reconcile
注解,那么就直接退出,由此可以推测出redis.opstreelabs.in/skip-reconcile
注解的用法,如果我们不希望Redis-Operator
管理Redis
资源,就打上这个注解 - 3、如果发现
Redis
资源需要删除,那么先删除Service
, 然后是PVC
,最后是StatefulSet
,删除之后再删除finalizer
- 注意,只有先通过第四步,给
Redis
资源打上了Finallzer
,Redis
的删除流程才会被Redis-Operator
接管,Finalizer
可以认为是Kubernetes
给用户开放的资源清理的Hook
点
- 注意,只有先通过第四步,给
- 4、给
Redis
资源添加Finalizer
- 5、创建
Redis
单节点实例- 5.1、生成
Redis
标签,标签为:app=<cr.Name>, redis_setup_type:standalone, role=standalone
,这个标签会打在StatefulSet
上 - 5.2、生成
Redis
注解,注解为:redis.opstreelabs.in=true, redis.opstreelabs.instance=<cr.Name>
,这个注解将来也会打在StatefulSet
上 - 5.3、根据
Redis
资源清单,生成StatefulSet
配置 - 5.4、生成
OwnerReference
信息 - 5.5、根据
Redis
资源清单,生成Redis Container
配置 - 5.6、通过
apiserver
查询是否StatefulSet
,如果没查询到,说明当前需要创建Redis
单节点实例 - 5.7、如果找到了通过
Update
接口,更新StatefuleSet
,我们来看一下具体的更新过程,看看如果Redis
单实例节点已经创建的情况下,我们需要如何更新。- 5.7.1、通过
k8s-objectmatcher
库计算出current, modified
之间的差别 - 5.7.2、如果
Redis
资源的PVC
容量发生改变,需要更改每个节点的PVC
容量,由于这里RedisController
组建的是单实例节点,因此这里只需要更新一个Node
的PVC
容量- 注意,由于
PVC
的容量只能增加,不能减少,因此这里一定是扩容
- 注意,由于
- 5.7.3、把旧的
Redis
中的Annotation
拷贝给当前新提交的Redis
资源 - 5.7.4、调用
Update
接口更新资源
- 5.7.1、通过
- 5.1、生成
- 6、创建
Service
Service
一共创建了两种,一种是普通的Service
,一种是Headless Service
type RedisReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
// Reconcile is part of the main kubernetes reconciliation loop which aims
func (r *RedisReconciler) 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.Redis{}
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
if _, found := instance.ObjectMeta.GetAnnotations()["redis.opstreelabs.in/skip-reconcile"]; found {
reqLogger.Info("Found annotations redis.opstreelabs.in/skip-reconcile, so skipping reconcile")
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}
if err := k8sutils.HandleRedisFinalizer(instance, r.Client); err != nil {
return ctrl.Result{}, err
}
if err := k8sutils.AddRedisFinalizer(instance, r.Client); err != nil {
return ctrl.Result{}, err
}
err = k8sutils.CreateStandaloneRedis(instance)
if err != nil {
return ctrl.Result{}, err
}
err = k8sutils.CreateStandaloneService(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 *RedisReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&redisv1beta1.Redis{}).
Complete(r)
}
func CreateStandaloneRedis(cr *redisv1beta1.Redis) error {
logger := statefulSetLogger(cr.Namespace, cr.ObjectMeta.Name)
labels := getRedisLabels(cr.ObjectMeta.Name, "standalone", "standalone", cr.ObjectMeta.Labels)
annotations := generateStatefulSetsAnots(cr.ObjectMeta)
objectMetaInfo := generateObjectMetaInformation(cr.ObjectMeta.Name, cr.Namespace, labels, annotations)
err := CreateOrUpdateStateFul(cr.Namespace,
objectMetaInfo,
generateRedisStandaloneParams(cr),
redisAsOwner(cr),
generateRedisStandaloneContainerParams(cr),
cr.Spec.Sidecars,
)
if err != nil {
logger.Error(err, "Cannot create standalone statefulset for Redis")
return err
}
return nil
}
5.7. RedisClusterController
RedisClusterController |
真正组件集群的控制器是通过RedisClusterController
来实现的,RedisController
仅仅是开胃菜,我们来看看RedisClusterController
是如何组件集群的,具体逻辑如下:
- 1、根据
RedisCluster
资源名以及所在名称空间查询RedisCluster
资源,如果没找到,则直接退出- 因为
RedisCluster
中包含了如何创建Redis
集群的信息,因此如果这个资源如果找不到,RedisClusterController
就陷入了巧妇难为无米之炊的困境了。不过,既然Informer
都已经检测到了RedisCluster
资源的变更,一般来说肯定是能够查到的。
- 因为
- 2、如果
RedisCluster
资源标注了rediscluster.opstreelabs.in/skip-reconcile
注解,说明用户不想创建Redis
集群,直接退出 - 3、根据
Redis Leader
的数量以及Redis Follower
的数量计算出总的副本数 - 4、如果发现
RedisCluster
资源被删除了,那么开始执行删除流程,即先删除Service
,然后删除PVC
,最后在删除StaetfulSet
,所有资源删除完成之后,再删除RedisCluster
的Finallzer
资源 - 5、如果发现不是
RedisCluster
资源的删除动作,那么判断该资源是否存在Finallzer
,如果没有,则添加Finallzer
- 6、创建
Redis Cluster
的Leader
,其实就是创建了名为redis-leader
的StatefulSet
,并且只创建了这一个资源,其它资源都没有创建 - 7、创建名为
redis-leader, redis-leader-headless
的Servcie
- 8、创建名为
redis-leader
的PodDisruptionBudget
资源,可以保证Redis
集群更强的高可用性 - 9、如果
Redis Leader
的所有Pod
已经全部启动完成,那么就创建Follower
,和Leader
一样,先创建名为redis-follower
的StatefuleSet
资源,然后创建名为redis-follower, redis-follower-headless
的Service
资源,最后创建名为redis-followder
的PodDisruptionBudget
资源 - 10、判断
Redis
的Leader, Follower
的所有Pod
是否已经全部启动完成,如果没有,那么两分钟之后再进来 - 11、通过执行
cluster nodes
命令,看看当前有几个节点- 11.1、如果查询到的节点数量,不等于总数量,那么执行后续步骤:
- 11.1.1、执行
cluster nodes
命令,看看当前又几个master
节点 - 11.1.2、如果查询到的
master
的数量和预期数量不符,说明master
还未组建好,通过执行-- cluaster create <ip:port> --cluster-yes
命令创建master
- 11.1.3、如果相等,说明
master
已经组建好了,此时可以通过执行cluster add node
命令把Follower
一个一个添加给Leader
- 11.1.1、执行
- 11.2、如果查询到的节点数量,等于总数量,那么之后后续步骤:
- 11.2.1、执行
cluster nodes
命令,拿到当前所有的node
节点 - 11.2.2、如果每个节点的状态没有包含
fail
,说明Redis
集群创建成功,但凡有一个节点出现fail
,说明集群组建失败,此时需要执行cluster reset
命令
- 11.2.1、执行
- 11.1、如果查询到的节点数量,不等于总数量,那么执行后续步骤:
// controllers/rediscluster_controller.go
type RedisClusterReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
// Reconcile is part of the main kubernetes reconciliation loop
func (r *RedisClusterReconciler) 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 Cluster controller")
instance := &redisv1beta1.RedisCluster{}
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
if _, found := instance.ObjectMeta.GetAnnotations()["rediscluster.opstreelabs.in/skip-reconcile"]; found {
reqLogger.Info("Found annotations rediscluster.opstreelabs.in/skip-reconcile, so skipping reconcile")
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}
leaderReplicas := instance.Spec.GetReplicaCounts("leader")
followerReplicas := instance.Spec.GetReplicaCounts("follower")
totalReplicas := leaderReplicas + followerReplicas
if err := k8sutils.HandleRedisClusterFinalizer(instance, r.Client); err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
if err := k8sutils.AddRedisClusterFinalizer(instance, r.Client); err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
err = k8sutils.CreateRedisLeader(instance)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
if leaderReplicas != 0 {
err = k8sutils.CreateRedisLeaderService(instance)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
}
err = k8sutils.ReconcileRedisPodDisruptionBudget(instance, "leader", instance.Spec.RedisLeader.PodDisruptionBudget)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
redisLeaderInfo, err := k8sutils.GetStatefulSet(instance.Namespace, instance.ObjectMeta.Name+"-leader")
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
if int32(redisLeaderInfo.Status.ReadyReplicas) == leaderReplicas {
err = k8sutils.CreateRedisFollower(instance)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
// if we have followers create their service.
if followerReplicas != 0 {
err = k8sutils.CreateRedisFollowerService(instance)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
}
err = k8sutils.ReconcileRedisPodDisruptionBudget(instance, "follower", instance.Spec.RedisFollower.PodDisruptionBudget)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
}
redisFollowerInfo, err := k8sutils.GetStatefulSet(instance.Namespace, instance.ObjectMeta.Name+"-follower")
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 60}, err
}
if leaderReplicas == 0 {
reqLogger.Info("Redis leaders Cannot be 0", "Ready.Replicas", strconv.Itoa(int(redisLeaderInfo.Status.ReadyReplicas)), "Expected.Replicas", leaderReplicas)
return ctrl.Result{RequeueAfter: time.Second * 120}, nil
}
if int32(redisLeaderInfo.Status.ReadyReplicas) != leaderReplicas && int32(redisFollowerInfo.Status.ReadyReplicas) != followerReplicas {
reqLogger.Info("Redis leader and follower nodes are not ready yet", "Ready.Replicas", strconv.Itoa(int(redisLeaderInfo.Status.ReadyReplicas)), "Expected.Replicas", leaderReplicas)
return ctrl.Result{RequeueAfter: time.Second * 120}, nil
}
reqLogger.Info("Creating redis cluster by executing cluster creation commands", "Leaders.Ready", strconv.Itoa(int(redisLeaderInfo.Status.ReadyReplicas)), "Followers.Ready", strconv.Itoa(int(redisFollowerInfo.Status.ReadyReplicas)))
if k8sutils.CheckRedisNodeCount(instance, "") != totalReplicas {
leaderCount := k8sutils.CheckRedisNodeCount(instance, "leader")
if leaderCount != leaderReplicas {
reqLogger.Info("Not all leader are part of the cluster...", "Leaders.Count", leaderCount, "Instance.Size", leaderReplicas)
k8sutils.ExecuteRedisClusterCommand(instance)
} else {
if followerReplicas > 0 {
reqLogger.Info("All leader are part of the cluster, adding follower/replicas", "Leaders.Count", leaderCount, "Instance.Size", leaderReplicas, "Follower.Replicas", followerReplicas)
k8sutils.ExecuteRedisReplicationCommand(instance)
} else {
reqLogger.Info("no follower/replicas configured, skipping replication configuration", "Leaders.Count", leaderCount, "Leader.Size", leaderReplicas, "Follower.Replicas", followerReplicas)
}
}
} else {
reqLogger.Info("Redis leader count is desired")
if k8sutils.CheckRedisClusterState(instance) >= int(totalReplicas)-1 {
reqLogger.Info("Redis leader is not desired, executing failover operation")
err = k8sutils.ExecuteFailoverOperation(instance)
if err != nil {
return ctrl.Result{RequeueAfter: time.Second * 10}, err
}
}
return ctrl.Result{RequeueAfter: time.Second * 120}, nil
}
reqLogger.Info("Will reconcile redis cluster operator in again 10 seconds")
return ctrl.Result{RequeueAfter: time.Second * 10}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *RedisClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&redisv1beta1.RedisCluster{}).
Complete(r)
}