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  }