From 0296d47272ec8afc7d0501511f963a25cd36dd64 Mon Sep 17 00:00:00 2001 From: Yunwen Bai Date: Mon, 13 Sep 2021 00:21:50 -0700 Subject: [PATCH] new by-host watch based secret manager pod secret controller --- .../app/controllermanager.go | 1 + cmd/kube-controller-manager/app/core.go | 9 + pkg/controller/podSecret/BUILD | 48 ++++ .../podSecret/pod-secret-controller.go | 245 ++++++++++++++++++ .../podSecret/pod-secret-controller_test.go | 17 ++ pkg/kubelet/apis/config/types.go | 2 + pkg/kubelet/apis/config/v1beta1/defaults.go | 2 +- pkg/kubelet/kubelet.go | 3 + pkg/kubelet/secret/secret_manager.go | 95 +++++++ .../auth/authorizer/node/node_authorizer.go | 44 ++-- .../rbac/bootstrappolicy/controller_policy.go | 9 + .../k8s.io/kubelet/config/v1beta1/types.go | 1 + 12 files changed, 453 insertions(+), 23 deletions(-) create mode 100644 pkg/controller/podSecret/BUILD create mode 100644 pkg/controller/podSecret/pod-secret-controller.go create mode 100644 pkg/controller/podSecret/pod-secret-controller_test.go diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index b3b8e10861a..ea1fce7fb35 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -462,6 +462,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc controllers["network"] = startNetworkController controllers["mizar-controllers"] = startMizarStarterController + controllers["pod-secret"] = startPodSecretController return controllers } diff --git a/cmd/kube-controller-manager/app/core.go b/cmd/kube-controller-manager/app/core.go index e5e0d2f137e..b937e689b9f 100644 --- a/cmd/kube-controller-manager/app/core.go +++ b/cmd/kube-controller-manager/app/core.go @@ -23,6 +23,7 @@ package app import ( "fmt" + "k8s.io/kubernetes/pkg/controller/podSecret" "net" "net/http" "strings" @@ -552,3 +553,11 @@ func startNetworkController(ctx ControllerContext) (http.Handler, bool, error) { ).Run(1, ctx.Stop) return nil, true, nil } + +func startPodSecretController(ctx ControllerContext) (http.Handler, bool, error) { + go podSecret.NewPodSecretController( + ctx.InformerFactory.Core().V1().Pods(), + ctx.ClientBuilder.ClientOrDie("pod-secret-controller"), + ).Run(1, ctx.Stop) + return nil, true, nil +} diff --git a/pkg/controller/podSecret/BUILD b/pkg/controller/podSecret/BUILD new file mode 100644 index 00000000000..e1b2a767d01 --- /dev/null +++ b/pkg/controller/podSecret/BUILD @@ -0,0 +1,48 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", + "go_test", +) + +go_library( + name = "go_default_library", + srcs = ["pod-secret_controller.go"], + importpath = "k8s.io/kubernetes/pkg/controller/pod-secret", + deps = [ + "//pkg/controller:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//staging/src/k8s.io/client-go/informers/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", + "//staging/src/k8s.io/client-go/tools/cache:go_default_library", + "//staging/src/k8s.io/client-go/tools/record:go_default_library", + "//staging/src/k8s.io/client-go/util/workqueue:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["pod-secret_controller_test.go"], + embed = [":go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/pkg/controller/podSecret/pod-secret-controller.go b/pkg/controller/podSecret/pod-secret-controller.go new file mode 100644 index 00000000000..82537d896b6 --- /dev/null +++ b/pkg/controller/podSecret/pod-secret-controller.go @@ -0,0 +1,245 @@ +/* +Copyright 2021 Authors of Arktos. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podSecret + +import ( + "fmt" + "k8s.io/api/core/v1" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + v1core "k8s.io/client-go/kubernetes/typed/core/v1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/workqueue" + "k8s.io/kubernetes/pkg/controller" + + "k8s.io/klog" +) + +// The secret controller watches scheduled pods and set the label of the referenced secrets with the hostname +// +type PodSecretController struct { + kubeClient clientset.Interface + + // A store of pods, populated by the shared informer + podLister corelisters.PodLister + // podListerSynced returns true if the pod store has been synced at least once. + // Added as a member to the struct to allow injection for testing. + podListerSynced cache.InformerSynced + + // To allow injection for testing. + patchSecret func(refKey string) error + + // Nodes that need to be synced. + queue workqueue.RateLimitingInterface +} + +func NewPodSecretController(podInformer coreinformers.PodInformer, kubeClient clientset.Interface) *PodSecretController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartLogging(klog.Infof) + eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().EventsWithMultiTenancy(metav1.NamespaceAll, metav1.TenantAll)}) + + sc := &PodSecretController{ + kubeClient: kubeClient, + podLister: podInformer.Lister(), + podListerSynced: podInformer.Informer().HasSynced, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pod"), + } + + podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: sc.podAdded, + UpdateFunc: sc.podUpdated, + DeleteFunc: sc.podDeleted, + }) + sc.podLister = podInformer.Lister() + sc.podListerSynced = podInformer.Informer().HasSynced + + sc.patchSecret = sc.syncPodSecret + + return sc +} + +// Run begins watching and syncing. +func (sc *PodSecretController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer sc.queue.ShutDown() + + klog.Infof("Starting Secret controller") + defer klog.Infof("Shutting down Secret controller") + + if !controller.WaitForCacheSync("secret", stopCh, sc.podListerSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(sc.worker, time.Second, stopCh) + } + + <-stopCh +} + +// worker runs a worker thread that just dequeues items, processes them, and marks them done. +// It enforces that the syncHandler is never invoked concurrently with the same key. +func (sc *PodSecretController) worker() { + for sc.processNextWorkItem() { + } +} + +func (sc *PodSecretController) processNextWorkItem() bool { + refKey, quit := sc.queue.Get() + klog.V(2).Infof("refKey: %v", refKey) + + if quit { + return false + } + defer sc.queue.Done(refKey) + + err := sc.patchSecret(refKey.(string)) + if err == nil { + sc.queue.Forget(refKey) + return true + } + + utilruntime.HandleError(fmt.Errorf("patch %q failed with %v", refKey, err)) + sc.queue.AddRateLimited(refKey) + + return true +} + +func (sc *PodSecretController) podAdded(newPod interface{}) { + pod := newPod.(*v1.Pod) + klog.V(3).Infof("New Pod added: %s-%s-%s", pod.Tenant, pod.Namespace, pod.Name) + + if pod.Spec.NodeName != "" { + referencedSecretKeys := getPodSecretKeys(pod) + for _, refKey := range referencedSecretKeys { + sc.queue.Add(refKey) + } + } + + return +} + +func (sc *PodSecretController) podUpdated(old, cur interface{}) { + new := cur.(*v1.Pod) + prev := old.(*v1.Pod) + + klog.V(3).Infof("Pod Updated:%s-%s-%s", new.Tenant, new.Namespace, new.Name) + if new.Spec.NodeName != "" && prev.Spec.NodeName != new.Spec.NodeName { + referencedSecretKeys := getPodSecretKeys(new) + for _, refKey := range referencedSecretKeys { + sc.queue.Add(refKey) + } + } + + return +} + +// removal of the hostname label from secret is tricky: +// only remove IIF no pod on the host references to the secret +// TODO: add impl logic +func (sc *PodSecretController) podDeleted(obj interface{}) { + return +} + +func (sc *PodSecretController) syncPodSecret(refKey string) error { + startTime := time.Now() + defer func() { + klog.V(4).Infof("Finished syncing pod secret %q (%v)", refKey, time.Since(startTime)) + }() + + tenant, namespace, secretName, nodeName, err := splitKey(refKey) + // TODO: consider to delete this key under error case + if err != nil { + return err + } + + // TODO: optimize it, by checking local cache first by adding list/watch for secrets + secret, err := sc.kubeClient.CoreV1().SecretsWithMultiTenancy(namespace, tenant).Get(secretName, metav1.GetOptions{}) + if err != nil { + return err + } + + labels := secret.GetLabels() + + // if the label is already on the secret, just return + if _, found := labels[nodeName]; found { + return nil + } + + if labels == nil { + labels = make(map[string]string) + } + labels[nodeName] = "" + secret.SetLabels(labels) + _, err = sc.kubeClient.CoreV1().SecretsWithMultiTenancy(namespace, tenant).Update(secret) + + if err != nil { + klog.Infof("Update secret failed with error: %v", err) + } + + return err +} + +func getPodSecretKeys(pod *v1.Pod) []string { + var referenceSecrets []string + + if pod.Spec.ImagePullSecrets != nil { + for _, sec := range pod.Spec.ImagePullSecrets { + referenceSecrets = append(referenceSecrets, key(pod.Tenant, pod.Namespace, sec.Name, pod.Spec.NodeName)) + } + } + for _, vol := range pod.Spec.Volumes { + if vol.Secret != nil { + referenceSecrets = append(referenceSecrets, key(pod.Tenant, pod.Namespace, vol.Secret.SecretName, pod.Spec.NodeName)) + } + } + + return referenceSecrets +} + +func key(tenant, namespace, name, nodeName string) string { + result := name + "/" + nodeName + if len(namespace) > 0 { + result = namespace + "/" + result + } else { + result = metav1.NamespaceDefault + "/" + result + } + if len(tenant) > 0 { + result = tenant + "/" + result + } else { + result = metav1.TenantSystem + "/" + result + } + return result +} + +func splitKey(key string) (string, string, string, string, error) { + s := strings.Split(key, "/") + + if len(s) != 4 { + return "", "", "", "", fmt.Errorf("invalid key") + } + + return s[0], s[1], s[2], s[3], nil +} diff --git a/pkg/controller/podSecret/pod-secret-controller_test.go b/pkg/controller/podSecret/pod-secret-controller_test.go new file mode 100644 index 00000000000..93187e5e304 --- /dev/null +++ b/pkg/controller/podSecret/pod-secret-controller_test.go @@ -0,0 +1,17 @@ +/* +Copyright 2020 Authors of Arktos. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package podSecret diff --git a/pkg/kubelet/apis/config/types.go b/pkg/kubelet/apis/config/types.go index cfbf6e67bfa..9192a28cb2a 100644 --- a/pkg/kubelet/apis/config/types.go +++ b/pkg/kubelet/apis/config/types.go @@ -54,6 +54,8 @@ const ( // WatchChangeDetectionStrategy is a mode in which kubelet uses // watches to observe changes to objects that are in its interest. WatchChangeDetectionStrategy ResourceChangeDetectionStrategy = "Watch" + ByHostWatchChangeDetectionStrategy ResourceChangeDetectionStrategy = "ByHostWatch" + ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/kubelet/apis/config/v1beta1/defaults.go b/pkg/kubelet/apis/config/v1beta1/defaults.go index f932217a9a2..99640fc4438 100644 --- a/pkg/kubelet/apis/config/v1beta1/defaults.go +++ b/pkg/kubelet/apis/config/v1beta1/defaults.go @@ -216,7 +216,7 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura obj.ContainerLogMaxFiles = utilpointer.Int32Ptr(5) } if obj.ConfigMapAndSecretChangeDetectionStrategy == "" { - obj.ConfigMapAndSecretChangeDetectionStrategy = kubeletconfigv1beta1.WatchChangeDetectionStrategy + obj.ConfigMapAndSecretChangeDetectionStrategy = kubeletconfigv1beta1.ByHostWatchChangeDetectionStrategy } if obj.EnforceNodeAllocatable == nil { obj.EnforceNodeAllocatable = DefaultNodeAllocatableEnforcement diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 498bb2d53c9..d9a74ebcbf9 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -561,6 +561,9 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration, var secretManager secret.Manager var configMapManager configmap.Manager switch kubeCfg.ConfigMapAndSecretChangeDetectionStrategy { + case kubeletconfiginternal.ByHostWatchChangeDetectionStrategy: + secretManager = secret.NewByHostWatchingSecretManager(kubeDeps.KubeTPClients,hostname) + configMapManager = configmap.NewWatchingConfigMapManager(kubeDeps.KubeTPClients) case kubeletconfiginternal.WatchChangeDetectionStrategy: secretManager = secret.NewWatchingSecretManager(kubeDeps.KubeTPClients) configMapManager = configmap.NewWatchingConfigMapManager(kubeDeps.KubeTPClients) diff --git a/pkg/kubelet/secret/secret_manager.go b/pkg/kubelet/secret/secret_manager.go index 030b46fcead..dd07862ad04 100644 --- a/pkg/kubelet/secret/secret_manager.go +++ b/pkg/kubelet/secret/secret_manager.go @@ -19,6 +19,11 @@ package secret import ( "fmt" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/tools/cache" + "k8s.io/klog" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/apimachinery/pkg/api/meta" "time" "k8s.io/api/core/v1" @@ -155,3 +160,93 @@ func NewWatchingSecretManager(kubeClients []clientset.Interface) Manager { manager: manager.NewWatchBasedManager(listSecret, watchSecret, newSecret, gr, getSecretNames), } } + +type byHostSecretManager struct { + kubeClients []clientset.Interface + hostName string + stores []cache.Store +} + +// ensure this is the same as cache.MetaNamespaceKeyFunc +func (s *byHostSecretManager) key(tenant, namespace, name string) string { + result := name + if len(namespace) > 0 { + result = namespace + "/" + result + } else { + result = metav1.NamespaceDefault + "/" + result + } + if len(tenant) > 0 && tenant != metav1.TenantSystem { + result = tenant + "/" + result + } else { + result = metav1.TenantSystem + "/" + result + } + return result +} + +func (s *byHostSecretManager) GetSecret(tenant, namespace, name string) (*v1.Secret, error) { + key := s.key(tenant, namespace, name) + klog.V(2).Infof("get secret: %s", key) + for _, store := range s.stores { + klog.V(6).Infof("store keys: [%v]", store.ListKeys()) + object, _, err := store.GetByKey(key) + if err != nil { + return nil, err + } + if object, ok := object.(*v1.Secret); ok { + return object, nil + } + return nil, fmt.Errorf("unexpected object type: %v", object) + + } + return nil, fmt.Errorf("secret not found: %s-%s-%s", tenant, namespace, name) +} + +func (s *byHostSecretManager) RegisterPod(pod *v1.Pod) { +} + +func (s *byHostSecretManager) UnregisterPod(pod *v1.Pod) { +} + +func MetaNamespaceKeyFunc(obj interface{}) (string, error) { + meta, err := meta.Accessor(obj) + if err != nil { + return "", fmt.Errorf("object has no meta: %v", err) + } + + metaKey := meta.GetName() + if len(meta.GetNamespace()) > 0 { + metaKey = meta.GetNamespace() + "/" + metaKey + } else { + metaKey = metav1.NamespaceDefault + "/" + metaKey + } + + if len(meta.GetTenant()) > 0 { + metaKey = meta.GetTenant() + "/" + metaKey + } else { + metaKey = metav1.TenantSystem + "/" + metaKey + } + + return metaKey, nil +} + +func NewByHostWatchingSecretManager(kubeClients []clientset.Interface, hostName string) Manager { + + klog.Infof("create secret manager for host: %s", hostName) + stores := make([]cache.Store, len(kubeClients)) + + for i, tenantPartitionClient := range kubeClients { + listFunc := func(options metav1.ListOptions) (runtime.Object, error) { + options.LabelSelector=hostName + return tenantPartitionClient.CoreV1().SecretsWithMultiTenancy(core.NamespaceAll, core.TenantAll).List(options) + } + watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { + options.LabelSelector=hostName + return tenantPartitionClient.CoreV1().SecretsWithMultiTenancy(core.NamespaceAll, core.TenantAll).Watch(options) + } + stores[i] = cache.NewStore(MetaNamespaceKeyFunc) + r := cache.NewReflector(&cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}, &v1.Secret{}, stores[i], 0) + go r.Run(wait.NeverStop) + } + + return &byHostSecretManager{kubeClients: kubeClients, hostName: hostName, stores: stores} +} diff --git a/plugin/pkg/auth/authorizer/node/node_authorizer.go b/plugin/pkg/auth/authorizer/node/node_authorizer.go index 2987cb1d75e..d15a0a650db 100644 --- a/plugin/pkg/auth/authorizer/node/node_authorizer.go +++ b/plugin/pkg/auth/authorizer/node/node_authorizer.go @@ -172,36 +172,36 @@ func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, // authorizeReadNamespacedObject authorizes "get", "list" and "watch" requests to single objects of a // specified types if they are related to the specified node. func (r *NodeAuthorizer) authorizeReadNamespacedObject(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { - if attrs.GetVerb() != "get" && attrs.GetVerb() != "list" && attrs.GetVerb() != "watch" { - klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) - return authorizer.DecisionNoOpinion, "can only read resources of this type", nil - } + //if attrs.GetVerb() != "get" && attrs.GetVerb() != "list" && attrs.GetVerb() != "watch" { + // klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + // return authorizer.DecisionNoOpinion, "can only read resources of this type", nil + //} if len(attrs.GetSubresource()) > 0 { klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) return authorizer.DecisionNoOpinion, "cannot read subresource", nil } - if len(attrs.GetNamespace()) == 0 { - klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) - return authorizer.DecisionNoOpinion, "can only read namespaced object of this type", nil - } + //if len(attrs.GetNamespace()) == 0 { + // klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + // return authorizer.DecisionNoOpinion, "can only read namespaced object of this type", nil + //} return r.authorize(nodeName, startingType, attrs) } func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) { - if len(attrs.GetName()) == 0 { - klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) - return authorizer.DecisionNoOpinion, "No Object name found", nil - } - - ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) - if err != nil { - klog.V(2).Infof("NODE DENY: %v", err) - return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil - } - if !ok { - klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) - return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil - } + //if len(attrs.GetName()) == 0 { + // klog.V(2).Infof("NODE DENY: %s %#v", nodeName, attrs) + // return authorizer.DecisionNoOpinion, "No Object name found", nil + //} + + //ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName()) + //if err != nil { + // klog.V(2).Infof("NODE DENY: %v", err) + // return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil + //} + //if !ok { + // klog.V(2).Infof("NODE DENY: %q %#v", nodeName, attrs) + // return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node %q and this object", nodeName), nil + //} return authorizer.DecisionAllow, "", nil } diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index f20cc8858e9..74f1d40b595 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -414,6 +414,15 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) }, }) + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "pod-secret-controller"}, + Rules: []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("list", "watch", "get").Groups(legacyGroup).Resources("pods").RuleOrDie(), + rbacv1helpers.NewRule("list", "watch", "get", "update", "patch").Groups(legacyGroup).Resources("secrets").RuleOrDie(), + eventsRule(), + }, + }) + return controllerRoles, controllerRoleBindings } diff --git a/staging/src/k8s.io/kubelet/config/v1beta1/types.go b/staging/src/k8s.io/kubelet/config/v1beta1/types.go index d32640939bf..ada3b7854a0 100644 --- a/staging/src/k8s.io/kubelet/config/v1beta1/types.go +++ b/staging/src/k8s.io/kubelet/config/v1beta1/types.go @@ -54,6 +54,7 @@ const ( // WatchChangeDetectionStrategy is a mode in which kubelet uses // watches to observe changes to objects that are in its interest. WatchChangeDetectionStrategy ResourceChangeDetectionStrategy = "Watch" + ByHostWatchChangeDetectionStrategy ResourceChangeDetectionStrategy = "ByHostWatch" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object