github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/cluster_controller.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package apps 21 22 import ( 23 "context" 24 "time" 25 26 appsv1 "k8s.io/api/apps/v1" 27 batchv1 "k8s.io/api/batch/v1" 28 corev1 "k8s.io/api/core/v1" 29 policyv1 "k8s.io/api/policy/v1" 30 rbacv1 "k8s.io/api/rbac/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/client-go/tools/record" 35 ctrl "sigs.k8s.io/controller-runtime" 36 "sigs.k8s.io/controller-runtime/pkg/client" 37 "sigs.k8s.io/controller-runtime/pkg/handler" 38 "sigs.k8s.io/controller-runtime/pkg/log" 39 "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 41 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 42 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 43 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 44 "github.com/1aal/kubeblocks/pkg/constant" 45 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 46 viper "github.com/1aal/kubeblocks/pkg/viperx" 47 ) 48 49 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusters,verbs=get;list;watch;create;update;patch;delete 50 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusters/status,verbs=get;update;patch 51 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusters/finalizers,verbs=update 52 53 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=backuppolicytemplates,verbs=get;list 54 55 // owned K8s core API resources controller-gen RBAC marker 56 // full access on core API resources 57 // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete;deletecollection 58 // +kubebuilder:rbac:groups=core,resources=secrets/finalizers,verbs=update 59 60 // +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete;deletecollection 61 // +kubebuilder:rbac:groups=core,resources=configmaps/finalizers,verbs=update 62 63 // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete;deletecollection 64 // +kubebuilder:rbac:groups=core,resources=services/status,verbs=get 65 // +kubebuilder:rbac:groups=core,resources=services/finalizers,verbs=update 66 67 // +kubebuilder:rbac:groups=core,resources=resourcequotas,verbs=get;list;watch;create;update;patch;delete 68 // +kubebuilder:rbac:groups=core,resources=resourcequotas/status,verbs=get 69 // +kubebuilder:rbac:groups=core,resources=resourcequotas/finalizers,verbs=update 70 71 // +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete 72 // +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/status,verbs=get 73 // +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/finalizers,verbs=update 74 75 // +kubebuilder:rbac:groups=core,resources=persistentvolumes,verbs=get;list;watch;update;patch 76 77 // +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete 78 // +kubebuilder:rbac:groups=apps,resources=replicasets/status,verbs=get 79 // +kubebuilder:rbac:groups=apps,resources=replicasets/finalizers,verbs=update 80 81 // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete;deletecollection 82 // +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get 83 // +kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update 84 85 // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete;deletecollection 86 // +kubebuilder:rbac:groups=apps,resources=statefulsets/status,verbs=get 87 // +kubebuilder:rbac:groups=apps,resources=statefulsets/finalizers,verbs=update 88 89 // +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete;deletecollection 90 // +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets/finalizers,verbs=update 91 92 // read + update access 93 // +kubebuilder:rbac:groups=core,resources=endpoints,verbs=get;list;watch;update;patch 94 // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;update;patch 95 // +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update 96 // +kubebuilder:rbac:groups=core,resources=pods/exec,verbs=create 97 98 // read only + watch access 99 // +kubebuilder:rbac:groups=storage.k8s.io,resources=storageclasses,verbs=get;list;watch 100 101 // dataprotection get list and delete 102 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backuppolicies,verbs=get;list;create;update;patch;delete;deletecollection 103 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backups,verbs=get;list;delete;deletecollection 104 105 // componentresourceconstraint get list 106 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=componentresourceconstraints,verbs=get;list;watch 107 108 // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch 109 // +kubebuilder:rbac:groups=core,resources=serviceaccounts/status,verbs=get 110 111 // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch 112 // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings/status,verbs=get 113 114 // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,verbs=get;list;watch 115 // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings/status,verbs=get 116 117 // ClusterReconciler reconciles a Cluster object 118 type ClusterReconciler struct { 119 client.Client 120 Scheme *runtime.Scheme 121 Recorder record.EventRecorder 122 } 123 124 // Reconcile is part of the main kubernetes reconciliation loop which aims to 125 // move the current state of the cluster closer to the desired state. 126 // 127 // For more details, check Reconcile and its Result here: 128 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile 129 func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 130 reqCtx := intctrlutil.RequestCtx{ 131 Ctx: ctx, 132 Req: req, 133 Log: log.FromContext(ctx).WithValues("cluster", req.NamespacedName), 134 Recorder: r.Recorder, 135 } 136 137 reqCtx.Log.V(1).Info("reconcile", "cluster", req.NamespacedName) 138 139 requeueError := func(err error) (ctrl.Result, error) { 140 if re, ok := err.(intctrlutil.RequeueError); ok { 141 return intctrlutil.RequeueAfter(re.RequeueAfter(), reqCtx.Log, re.Reason()) 142 } 143 if apierrors.IsConflict(err) { 144 return intctrlutil.Requeue(reqCtx.Log, err.Error()) 145 } 146 return intctrlutil.RequeueWithError(err, reqCtx.Log, "") 147 } 148 149 // the cluster reconciliation loop is a 3-stage model: plan Init, plan Build and plan Execute 150 // Init stage 151 planBuilder := NewClusterPlanBuilder(reqCtx, r.Client, req) 152 if err := planBuilder.Init(); err != nil { 153 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 154 } 155 156 // Build stage 157 // what you should do in most cases is writing your transformer. 158 // 159 // here are the how-to tips: 160 // 1. one transformer for one scenario 161 // 2. try not to modify the current transformers, make a new one 162 // 3. transformers are independent with each-other, with some exceptions. 163 // Which means transformers' order is not important in most cases. 164 // If you don't know where to put your transformer, append it to the end and that would be ok. 165 // 4. don't use client.Client for object write, use client.ReadonlyClient for object read. 166 // If you do need to create/update/delete object, make your intent operation a lifecycleVertex and put it into the DAG. 167 // 168 // TODO: transformers are vertices, theirs' dependencies are edges, make plan Build stage a DAG. 169 plan, errBuild := planBuilder. 170 AddTransformer( 171 // handle deletion 172 // handle cluster deletion first 173 &ClusterDeletionTransformer{}, 174 // check is recovering from halted cluster 175 &HaltRecoveryTransformer{}, 176 // assure meta-data info 177 // update finalizer and cd&cv labels 178 &AssureMetaTransformer{}, 179 // validate ref objects 180 // validate cd & cv's existence and availability 181 &ValidateAndLoadRefResourcesTransformer{}, 182 // validate config 183 &ValidateEnableLogsTransformer{}, 184 // create cluster connection credential secret object 185 &ClusterCredentialTransformer{}, 186 // handle restore before ComponentTransformer 187 &RestoreTransformer{Client: r.Client}, 188 // create all components objects 189 &ComponentTransformer{Client: r.Client}, 190 // transform backupPolicyTemplate to backuppolicy.dataprotection.kubeblocks.io 191 // and backupschedule.dataprotection.kubeblocks.io 192 &BackupPolicyTplTransformer{}, 193 // handle rbac for pod 194 &RBACTransformer{}, 195 // add our finalizer to all objects 196 &OwnershipTransformer{}, 197 // make all workload objects depending on credential secret 198 &SecretTransformer{}, 199 // update cluster status 200 &ClusterStatusTransformer{}, 201 // always safe to put your transformer below 202 ). 203 Build() 204 205 // Execute stage 206 // errBuild not nil means build stage partial success or validation error 207 // execute the plan first, delay error handling 208 if errExec := plan.Execute(); errExec != nil { 209 return requeueError(errExec) 210 } 211 if errBuild != nil { 212 return requeueError(errBuild) 213 } 214 return intctrlutil.Reconciled() 215 } 216 217 // SetupWithManager sets up the controller with the Manager. 218 func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { 219 retryDurationMS := viper.GetInt(constant.CfgKeyCtrlrReconcileRetryDurationMS) 220 if retryDurationMS != 0 { 221 requeueDuration = time.Millisecond * time.Duration(retryDurationMS) 222 } 223 // TODO: add filter predicate for core API objects 224 b := ctrl.NewControllerManagedBy(mgr). 225 For(&appsv1alpha1.Cluster{}). 226 Owns(&appsv1.StatefulSet{}). 227 Owns(&appsv1.Deployment{}). 228 Owns(&workloads.ReplicatedStateMachine{}). 229 Owns(&corev1.Service{}). 230 Owns(&corev1.Secret{}). 231 Owns(&corev1.ConfigMap{}). 232 Owns(&corev1.PersistentVolumeClaim{}). 233 Owns(&policyv1.PodDisruptionBudget{}). 234 Owns(&dpv1alpha1.BackupPolicy{}). 235 Owns(&dpv1alpha1.BackupSchedule{}). 236 Owns(&dpv1alpha1.Backup{}). 237 Owns(&dpv1alpha1.Restore{}). 238 Owns(&batchv1.Job{}). 239 Owns(&appsv1alpha1.Configuration{}). 240 Watches(&corev1.Pod{}, handler.EnqueueRequestsFromMapFunc(r.filterClusterResources)) 241 242 if viper.GetBool(constant.EnableRBACManager) { 243 b.Owns(&rbacv1.ClusterRoleBinding{}). 244 Owns(&rbacv1.RoleBinding{}). 245 Owns(&corev1.ServiceAccount{}) 246 } else { 247 b.Watches(&rbacv1.ClusterRoleBinding{}, handler.EnqueueRequestsFromMapFunc(r.filterClusterResources)). 248 Watches(&rbacv1.RoleBinding{}, handler.EnqueueRequestsFromMapFunc(r.filterClusterResources)). 249 Watches(&corev1.ServiceAccount{}, handler.EnqueueRequestsFromMapFunc(r.filterClusterResources)) 250 } 251 252 return b.Complete(r) 253 } 254 255 func (r *ClusterReconciler) filterClusterResources(ctx context.Context, obj client.Object) []reconcile.Request { 256 labels := obj.GetLabels() 257 if v, ok := labels[constant.AppManagedByLabelKey]; !ok || v != constant.AppName { 258 return []reconcile.Request{} 259 } 260 if _, ok := labels[constant.AppInstanceLabelKey]; !ok { 261 return []reconcile.Request{} 262 } 263 return []reconcile.Request{ 264 { 265 NamespacedName: types.NamespacedName{ 266 Namespace: obj.GetNamespace(), 267 Name: labels[constant.AppInstanceLabelKey], 268 }, 269 }, 270 } 271 }