diff --git a/cmd/kvcache-watcher/main.go b/cmd/kvcache-watcher/main.go index 267ce5260..7879282c1 100644 --- a/cmd/kvcache-watcher/main.go +++ b/cmd/kvcache-watcher/main.go @@ -56,7 +56,7 @@ import ( const KVCacheLabelKeyIdentifier = "kvcache.orchestration.aibrix.ai/name" const KVCacheLabelKeyRole = "kvcache.orchestration.aibrix.ai/role" const KVCacheLabelValueRoleCache = "cache" -const HPKVRedisNodeMemberKey = "hpkv_nodes" +const HPKVRedisNodeMemberKey = "hpkv_cluster_metadata" const InfiniStoreRedisNodeMemberKey = "kvcache_nodes" const networkStatusAnnotation = "k8s.volcengine.com/network-status" diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 2b9b54af3..c0add5157 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -67,6 +67,10 @@ metadata: control-plane: controller-manager app.kubernetes.io/name: aibrix app.kubernetes.io/managed-by: kustomize + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/metrics" name: controller-manager-metrics-service namespace: system spec: diff --git a/config/rbac/controller-manager/role.yaml b/config/rbac/controller-manager/role.yaml index 74deede0d..a74b4e7f9 100644 --- a/config/rbac/controller-manager/role.yaml +++ b/config/rbac/controller-manager/role.yaml @@ -12,10 +12,37 @@ rules: - create - patch - update +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + - validatingwebhookconfigurations + verbs: + - get + - list + - update + - watch +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list - apiGroups: - apps resources: - deployments + - statefulsets verbs: - create - delete @@ -28,30 +55,11 @@ rules: - apps resources: - deployments/status + - statefulsets/status verbs: - get - patch - update -- apiGroups: - - apps - resources: - - statefulsets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - statefulsets/status - verbs: - - get - - patch - - update - apiGroups: - autoscaling resources: @@ -106,7 +114,9 @@ rules: - apiGroups: - "" resources: + - pods/exec - pods/status + - services verbs: - create - delete @@ -118,14 +128,12 @@ rules: - apiGroups: - "" resources: - - services + - serviceaccounts verbs: - create - delete - get - list - - patch - - update - watch - apiGroups: - "" @@ -159,17 +167,6 @@ rules: - gateway.networking.k8s.io resources: - httproutes - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - gateway.networking.k8s.io - resources: - referencegrants verbs: - create @@ -209,57 +206,7 @@ rules: - orchestration.aibrix.ai resources: - kvcaches - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - orchestration.aibrix.ai - resources: - - kvcaches/finalizers - verbs: - - update -- apiGroups: - - orchestration.aibrix.ai - resources: - - kvcaches/status - verbs: - - get - - patch - - update -- apiGroups: - - orchestration.aibrix.ai - resources: - rayclusterfleets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - orchestration.aibrix.ai - resources: - - rayclusterfleets/finalizers - verbs: - - update -- apiGroups: - - orchestration.aibrix.ai - resources: - - rayclusterfleets/status - verbs: - - get - - patch - - update -- apiGroups: - - orchestration.aibrix.ai - resources: - rayclusterreplicasets verbs: - create @@ -272,12 +219,16 @@ rules: - apiGroups: - orchestration.aibrix.ai resources: + - kvcaches/finalizers + - rayclusterfleets/finalizers - rayclusterreplicasets/finalizers verbs: - update - apiGroups: - orchestration.aibrix.ai resources: + - kvcaches/status + - rayclusterfleets/status - rayclusterreplicasets/status verbs: - get @@ -310,28 +261,23 @@ rules: - patch - update - apiGroups: - - "" + - rbac.authorization.k8s.io resources: - - secrets + - rolebindings verbs: + - create + - delete - get - list - - update - watch - apiGroups: - - admissionregistration.k8s.io + - rbac.authorization.k8s.io resources: - - mutatingwebhookconfigurations - - validatingwebhookconfigurations + - roles verbs: + - create + - delete - get - list - update - watch -- apiGroups: - - apiextensions.k8s.io - resources: - - customresourcedefinitions - verbs: - - get - - list \ No newline at end of file diff --git a/config/standalone/kv-cache-controller/patch.yaml b/config/standalone/kv-cache-controller/patch.yaml index 30a716739..bb46a55ff 100644 --- a/config/standalone/kv-cache-controller/patch.yaml +++ b/config/standalone/kv-cache-controller/patch.yaml @@ -12,6 +12,6 @@ spec: - --leader-elect - --leader-election-id=aibrix-kv-cache-controller - --health-probe-bind-address=:8081 - - --metrics-bind-address=0 + - --metrics-bind-address=:8080 - --controllers=kv-cache-controller - --disable-webhook diff --git a/pkg/controller/kvcache/backends/common.go b/pkg/controller/kvcache/backends/common.go index 8c7986e91..a49c88b97 100644 --- a/pkg/controller/kvcache/backends/common.go +++ b/pkg/controller/kvcache/backends/common.go @@ -22,6 +22,7 @@ import ( orchestrationv1alpha1 "github.com/vllm-project/aibrix/api/orchestration/v1alpha1" "github.com/vllm-project/aibrix/pkg/constants" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -113,3 +114,75 @@ func buildRedisService(kvCache *orchestrationv1alpha1.KVCache) *corev1.Service { return svc } + +// buildServiceAccount creates a new ServiceAccount for Distributed kv cache solution. +func buildServiceAccount(kvCache *orchestrationv1alpha1.KVCache) *corev1.ServiceAccount { + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvCache.Name, + Namespace: kvCache.Namespace, + Labels: map[string]string{ + constants.KVCacheLabelKeyIdentifier: kvCache.Name, + constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleCache, + }, + }, + } + + return sa +} + +// buildRole creates a new Role for a KVCache resource. +func buildRole(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.Role { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvCache.Name, + Namespace: kvCache.Namespace, + Labels: map[string]string{ + constants.KVCacheLabelKeyIdentifier: kvCache.Name, + constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleCache, + }, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods/exec"}, + Verbs: []string{"create"}, + }, + }, + } + + return role +} + +// buildRoleBinding creates rolebinding for a kvCache object +func buildRoleBinding(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.RoleBinding { + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: kvCache.Name, + Namespace: kvCache.Namespace, + Labels: map[string]string{ + constants.KVCacheLabelKeyIdentifier: kvCache.Name, + constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleCache, + }, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.ServiceAccountKind, + Name: kvCache.Name, + Namespace: kvCache.Namespace, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: kvCache.Name, + }, + } + + return rb +} diff --git a/pkg/controller/kvcache/backends/distributed.go b/pkg/controller/kvcache/backends/distributed.go index b773baa7f..fd5e1b9d9 100644 --- a/pkg/controller/kvcache/backends/distributed.go +++ b/pkg/controller/kvcache/backends/distributed.go @@ -60,6 +60,18 @@ func (r *DistributedReconciler) Reconcile(ctx context.Context, kvCache *orchestr return reconcile.Result{}, err } + if err := r.reconcileWatcherPodServiceAccount(ctx, r.Backend.BuildWatcherPodServiceAccount(kvCache)); err != nil { + return reconcile.Result{}, err + } + + if err := r.reconcileWatcherPodRole(ctx, r.Backend.BuildWatcherPodRole(kvCache)); err != nil { + return reconcile.Result{}, err + } + + if err := r.reconcileWatcherPodRoleBinding(ctx, r.Backend.BuildWatcherPodRoleBinding(kvCache)); err != nil { + return reconcile.Result{}, err + } + // Handle infinistore kvCache Deployment if err := r.ReconcileStatefulsetObject(ctx, r.Backend.BuildCacheStatefulSet(kvCache)); err != nil { return ctrl.Result{}, err diff --git a/pkg/controller/kvcache/backends/distributed_test.go b/pkg/controller/kvcache/backends/distributed_test.go index d20dccf09..de2d22380 100644 --- a/pkg/controller/kvcache/backends/distributed_test.go +++ b/pkg/controller/kvcache/backends/distributed_test.go @@ -22,6 +22,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" "github.com/stretchr/testify/assert" "github.com/vllm-project/aibrix/api/orchestration/v1alpha1" @@ -181,6 +182,9 @@ type mockBackend struct { watcher *corev1.Pod svc *corev1.Service sts *appsv1.StatefulSet + sa *corev1.ServiceAccount + role *rbacv1.Role + rb *rbacv1.RoleBinding } func (m mockBackend) Name() string { @@ -199,6 +203,18 @@ func (m mockBackend) BuildMetadataService(*v1alpha1.KVCache) *corev1.Service { return m.svc } +func (m mockBackend) BuildWatcherPodServiceAccount(*v1alpha1.KVCache) *corev1.ServiceAccount { + return m.sa +} + +func (m mockBackend) BuildWatcherPodRole(*v1alpha1.KVCache) *rbacv1.Role { + return m.role +} + +func (m mockBackend) BuildWatcherPodRoleBinding(*v1alpha1.KVCache) *rbacv1.RoleBinding { + return m.rb +} + func (m mockBackend) BuildWatcherPod(*v1alpha1.KVCache) *corev1.Pod { return m.watcher } diff --git a/pkg/controller/kvcache/backends/hpkv.go b/pkg/controller/kvcache/backends/hpkv.go index aa1fd9e18..04a66da23 100644 --- a/pkg/controller/kvcache/backends/hpkv.go +++ b/pkg/controller/kvcache/backends/hpkv.go @@ -27,6 +27,7 @@ import ( "github.com/vllm-project/aibrix/pkg/utils" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -77,6 +78,18 @@ func (HpKVBackend) ValidateObject(kvCache *orchestrationv1alpha1.KVCache) error return nil } +func (b HpKVBackend) BuildWatcherPodServiceAccount(kvCache *orchestrationv1alpha1.KVCache) *corev1.ServiceAccount { + return buildServiceAccount(kvCache) +} + +func (b HpKVBackend) BuildWatcherPodRole(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.Role { + return buildRole(kvCache) +} + +func (b HpKVBackend) BuildWatcherPodRoleBinding(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.RoleBinding { + return buildRoleBinding(kvCache) +} + func (HpKVBackend) BuildWatcherPod(kvCache *orchestrationv1alpha1.KVCache) *corev1.Pod { // you can reuse your existing buildKVCacheWatcherPod() logic return buildKVCacheWatcherPod(kvCache) @@ -131,6 +144,11 @@ func buildKVCacheWatcherPod(kvCache *orchestrationv1alpha1.KVCache) *corev1.Pod constants.KVCacheLabelKeyIdentifier: kvCache.Name, constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleKVWatcher, }, + Annotations: map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": "8000", + "prometheus.io/path": "/metrics", + }, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(kvCache, orchestrationv1alpha1.GroupVersion.WithKind("KVCache")), }, @@ -163,7 +181,7 @@ func buildKVCacheWatcherPod(kvCache *orchestrationv1alpha1.KVCache) *corev1.Pod Resources: kvCache.Spec.Watcher.Resources, }, }, - ServiceAccountName: "kvcache-watcher-sa", + ServiceAccountName: kvCache.Name, }, } @@ -202,6 +220,24 @@ func buildCacheStatefulSet(kvCache *orchestrationv1alpha1.KVCache) *appsv1.State envs = append(envs, kvCache.Spec.Cache.Env...) } + annotations := map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": strconv.Itoa(params.AdminPort), + "prometheus.io/path": "/metrics", + } + rdmaKey := corev1.ResourceName("vke.volcengine.com/rdma") + if _, ok := kvCache.Spec.Cache.Resources.Limits[rdmaKey]; ok { + annotations["k8s.volcengine.com/pod-networks"] = ` +[ + { + "cniConf": { + "name": "rdma" + } + } +] +` + } + kvCacheServerArgs := []string{ "-a", "$AIBRIX_KVCACHE_RDMA_IP", "-p", "$AIBRIX_KVCACHE_RDMA_PORT", @@ -238,17 +274,7 @@ func buildCacheStatefulSet(kvCache *orchestrationv1alpha1.KVCache) *appsv1.State constants.KVCacheLabelKeyIdentifier: kvCache.Name, constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleCache, }, - Annotations: map[string]string{ - "k8s.volcengine.com/pod-networks": ` -[ - { - "cniConf": { - "name": "rdma" - } - } -] -`, - }, + Annotations: annotations, }, Spec: corev1.PodSpec{ //HostNetwork: true, // CNI doesn't need hostNetwork:true. in that case, RDMA ip won't be injected. diff --git a/pkg/controller/kvcache/backends/infinistore.go b/pkg/controller/kvcache/backends/infinistore.go index de714418f..8cdbbc747 100644 --- a/pkg/controller/kvcache/backends/infinistore.go +++ b/pkg/controller/kvcache/backends/infinistore.go @@ -26,6 +26,7 @@ import ( "github.com/vllm-project/aibrix/pkg/utils" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -72,6 +73,18 @@ func (InfiniStoreBackend) ValidateObject(kvCache *orchestrationv1alpha1.KVCache) return nil } +func (b InfiniStoreBackend) BuildWatcherPodServiceAccount(kvCache *orchestrationv1alpha1.KVCache) *corev1.ServiceAccount { + return buildServiceAccount(kvCache) +} + +func (b InfiniStoreBackend) BuildWatcherPodRole(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.Role { + return buildRole(kvCache) +} + +func (b InfiniStoreBackend) BuildWatcherPodRoleBinding(kvCache *orchestrationv1alpha1.KVCache) *rbacv1.RoleBinding { + return buildRoleBinding(kvCache) +} + func (InfiniStoreBackend) BuildWatcherPod(kvCache *orchestrationv1alpha1.KVCache) *corev1.Pod { return buildKVCacheWatcherPodForInfiniStore(kvCache) } @@ -125,6 +138,11 @@ func buildKVCacheWatcherPodForInfiniStore(kvCache *orchestrationv1alpha1.KVCache constants.KVCacheLabelKeyIdentifier: kvCache.Name, constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleKVWatcher, }, + Annotations: map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": "8000", + "prometheus.io/path": "/metrics", + }, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(kvCache, orchestrationv1alpha1.GroupVersion.WithKind("KVCache")), }, @@ -157,8 +175,7 @@ func buildKVCacheWatcherPodForInfiniStore(kvCache *orchestrationv1alpha1.KVCache Resources: kvCache.Spec.Watcher.Resources, }, }, - // TODO: refactor the permission management here. - ServiceAccountName: "kvcache-watcher-sa", + ServiceAccountName: kvCache.Name, }, } @@ -194,6 +211,24 @@ func buildCacheStatefulSetForInfiniStore(kvCache *orchestrationv1alpha1.KVCache) envs = append(envs, kvCache.Spec.Cache.Env...) } + annotations := map[string]string{ + "prometheus.io/scrape": "true", + "prometheus.io/port": strconv.Itoa(params.AdminPort), + "prometheus.io/path": "/metrics", + } + rdmaKey := corev1.ResourceName("vke.volcengine.com/rdma") + if _, ok := kvCache.Spec.Cache.Resources.Limits[rdmaKey]; ok { + annotations["k8s.volcengine.com/pod-networks"] = ` +[ + { + "cniConf": { + "name": "rdma" + } + } +] +` + } + kvCacheServerArgs := []string{ fmt.Sprintf("--service-port=%s", strconv.Itoa(params.RdmaPort)), fmt.Sprintf("--manage-port=%s", strconv.Itoa(params.AdminPort)), @@ -230,19 +265,7 @@ func buildCacheStatefulSetForInfiniStore(kvCache *orchestrationv1alpha1.KVCache) constants.KVCacheLabelKeyIdentifier: kvCache.Name, constants.KVCacheLabelKeyRole: constants.KVCacheLabelValueRoleCache, }, - // TODO: if there's rdma enabled, then we should attach resources. - // use an annotation to control it? enable RDMA - Annotations: map[string]string{ - "k8s.volcengine.com/pod-networks": ` -[ - { - "cniConf": { - "name": "rdma" - } - } -] -`, - }, + Annotations: annotations, }, Spec: corev1.PodSpec{ //HostNetwork: true, // CNI doesn't need hostNetwork:true. in that case, RDMA ip won't be injected. diff --git a/pkg/controller/kvcache/backends/reconciler.go b/pkg/controller/kvcache/backends/reconciler.go index 79d849566..0fa212a70 100644 --- a/pkg/controller/kvcache/backends/reconciler.go +++ b/pkg/controller/kvcache/backends/reconciler.go @@ -21,6 +21,7 @@ import ( "reflect" orchestrationv1alpha1 "github.com/vllm-project/aibrix/api/orchestration/v1alpha1" + rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" appsv1 "k8s.io/api/apps/v1" @@ -128,6 +129,42 @@ func (r *BaseReconciler) ReconcileStatefulsetObject(ctx context.Context, sts *ap return nil } +func (r *BaseReconciler) reconcileWatcherPodServiceAccount(ctx context.Context, sa *corev1.ServiceAccount) error { + found := &corev1.ServiceAccount{} + err := r.Get(ctx, types.NamespacedName{Name: sa.Name, Namespace: sa.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + klog.InfoS("Creating a new ServiceAccount", "SA.Namespace", sa.Namespace, "SA.Name", sa.Name) + return r.Create(ctx, sa) + } else if err != nil { + return err + } + return nil +} + +func (r *BaseReconciler) reconcileWatcherPodRole(ctx context.Context, role *rbacv1.Role) error { + found := &rbacv1.Role{} + err := r.Get(ctx, types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + klog.InfoS("Creating a new Role", "Role.Namespace", role.Namespace, "Role.Name", role.Name) + return r.Create(ctx, role) + } else if err != nil { + return err + } + return nil +} + +func (r *BaseReconciler) reconcileWatcherPodRoleBinding(ctx context.Context, rolebinding *rbacv1.RoleBinding) error { + found := &rbacv1.RoleBinding{} + err := r.Get(ctx, types.NamespacedName{Name: rolebinding.Name, Namespace: rolebinding.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + klog.InfoS("Creating a new RoleBinding", "RoleBinding.Namespace", rolebinding.Namespace, "RoleBinding.Name", rolebinding.Name) + return r.Create(ctx, rolebinding) + } else if err != nil { + return err + } + return nil +} + // needsUpdateService checks if the service spec of the new service differs from the existing one func needsUpdateService(service, found *corev1.Service) bool { // Compare relevant spec fields @@ -175,6 +212,9 @@ type KVCacheBackend interface { ValidateObject(*orchestrationv1alpha1.KVCache) error BuildMetadataPod(*orchestrationv1alpha1.KVCache) *corev1.Pod BuildMetadataService(*orchestrationv1alpha1.KVCache) *corev1.Service + BuildWatcherPodServiceAccount(*orchestrationv1alpha1.KVCache) *corev1.ServiceAccount + BuildWatcherPodRole(*orchestrationv1alpha1.KVCache) *rbacv1.Role + BuildWatcherPodRoleBinding(*orchestrationv1alpha1.KVCache) *rbacv1.RoleBinding BuildWatcherPod(*orchestrationv1alpha1.KVCache) *corev1.Pod BuildCacheStatefulSet(*orchestrationv1alpha1.KVCache) *appsv1.StatefulSet BuildService(*orchestrationv1alpha1.KVCache) *corev1.Service diff --git a/pkg/controller/kvcache/kvcache_controller.go b/pkg/controller/kvcache/kvcache_controller.go index 4fd982a86..15fe8c294 100644 --- a/pkg/controller/kvcache/kvcache_controller.go +++ b/pkg/controller/kvcache/kvcache_controller.go @@ -127,12 +127,17 @@ type KVCacheReconciler struct { // +kubebuilder:rbac:groups=orchestration.aibrix.ai,resources=kvcaches/finalizers,verbs=update // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete;deletecollection // +kubebuilder:rbac:groups=core,resources=pods/status,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=pods/exec,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=core,resources=services/status,verbs=get;update;patch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=statefulsets/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;delete +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;delete;update +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;delete +// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list // Reconcile reconciles a KVCache to desired state. func (r *KVCacheReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {