istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package controller 16 17 import ( 18 v1 "k8s.io/api/core/v1" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/labels" 21 "k8s.io/apimachinery/pkg/types" 22 23 "istio.io/istio/pilot/pkg/features" 24 "istio.io/istio/pilot/pkg/keycertbundle" 25 "istio.io/istio/pkg/config/constants" 26 "istio.io/istio/pkg/kube" 27 "istio.io/istio/pkg/kube/controllers" 28 "istio.io/istio/pkg/kube/inject" 29 "istio.io/istio/pkg/kube/kclient" 30 "istio.io/istio/pkg/util/sets" 31 "istio.io/istio/security/pkg/k8s" 32 ) 33 34 const ( 35 // CACertNamespaceConfigMap is the name of the ConfigMap in each namespace storing the root cert of non-Kube CA. 36 CACertNamespaceConfigMap = "istio-ca-root-cert" 37 38 // maxRetries is the number of times a namespace will be retried before it is dropped out of the queue. 39 // With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the 40 // sequence of delays between successive queuing of a namespace. 41 // 42 // 5ms, 10ms, 20ms, 40ms, 80ms 43 maxRetries = 5 44 ) 45 46 var configMapLabel = map[string]string{"istio.io/config": "true"} 47 48 // NamespaceController manages reconciles a configmap in each namespace with a desired set of data. 49 type NamespaceController struct { 50 caBundleWatcher *keycertbundle.Watcher 51 52 queue controllers.Queue 53 54 namespaces kclient.Client[*v1.Namespace] 55 configmaps kclient.Client[*v1.ConfigMap] 56 57 ignoredNamespaces sets.Set[string] 58 } 59 60 // NewNamespaceController returns a pointer to a newly constructed NamespaceController instance. 61 func NewNamespaceController(kubeClient kube.Client, caBundleWatcher *keycertbundle.Watcher) *NamespaceController { 62 c := &NamespaceController{ 63 caBundleWatcher: caBundleWatcher, 64 } 65 c.queue = controllers.NewQueue("namespace controller", 66 controllers.WithReconciler(c.reconcileCACert), 67 controllers.WithMaxAttempts(maxRetries)) 68 69 c.configmaps = kclient.NewFiltered[*v1.ConfigMap](kubeClient, kclient.Filter{ 70 FieldSelector: "metadata.name=" + CACertNamespaceConfigMap, 71 ObjectFilter: kube.FilterIfEnhancedFilteringEnabled(kubeClient), 72 }) 73 c.namespaces = kclient.NewFiltered[*v1.Namespace](kubeClient, kclient.Filter{ 74 ObjectFilter: kube.FilterIfEnhancedFilteringEnabled(kubeClient), 75 }) 76 // kube-system is not skipped to enable deploying ztunnel in that namespace 77 c.ignoredNamespaces = inject.IgnoredNamespaces.Copy().Delete(constants.KubeSystemNamespace) 78 79 c.configmaps.AddEventHandler(controllers.FilteredObjectSpecHandler(c.queue.AddObject, func(o controllers.Object) bool { 80 // skip special kubernetes system namespaces 81 return !c.ignoredNamespaces.Contains(o.GetNamespace()) 82 })) 83 84 c.namespaces.AddEventHandler(controllers.FilteredObjectSpecHandler(c.queue.AddObject, func(o controllers.Object) bool { 85 if features.InformerWatchNamespace != "" && features.InformerWatchNamespace != o.GetName() { 86 // We are only watching one namespace, and its not this one 87 return false 88 } 89 if c.ignoredNamespaces.Contains(o.GetName()) { 90 // skip special kubernetes system namespaces 91 return false 92 } 93 return true 94 })) 95 return c 96 } 97 98 // Run starts the NamespaceController until a value is sent to stopCh. 99 func (nc *NamespaceController) Run(stopCh <-chan struct{}) { 100 if !kube.WaitForCacheSync("namespace controller", stopCh, nc.namespaces.HasSynced, nc.configmaps.HasSynced) { 101 return 102 } 103 104 go nc.startCaBundleWatcher(stopCh) 105 nc.queue.Run(stopCh) 106 controllers.ShutdownAll(nc.configmaps, nc.namespaces) 107 } 108 109 // startCaBundleWatcher listens for updates to the CA bundle and update cm in each namespace 110 func (nc *NamespaceController) startCaBundleWatcher(stop <-chan struct{}) { 111 id, watchCh := nc.caBundleWatcher.AddWatcher() 112 defer nc.caBundleWatcher.RemoveWatcher(id) 113 for { 114 select { 115 case <-watchCh: 116 for _, ns := range nc.namespaces.List("", labels.Everything()) { 117 nc.namespaceChange(ns) 118 } 119 case <-stop: 120 return 121 } 122 } 123 } 124 125 // reconcileCACert will reconcile the ca root cert configmap for the specified namespace 126 // If the configmap is not found, it will be created. 127 // If the namespace is filtered out by discovery selector, the configmap will be deleted. 128 func (nc *NamespaceController) reconcileCACert(o types.NamespacedName) error { 129 ns := o.Namespace 130 if ns == "" { 131 // For Namespace object, it will not have o.Namespace field set 132 ns = o.Name 133 } 134 135 meta := metav1.ObjectMeta{ 136 Name: CACertNamespaceConfigMap, 137 Namespace: ns, 138 Labels: configMapLabel, 139 } 140 return k8s.InsertDataToConfigMap(nc.configmaps, meta, nc.caBundleWatcher.GetCABundle()) 141 } 142 143 // On namespace change, update the config map. 144 // If terminating, this will be skipped 145 func (nc *NamespaceController) namespaceChange(ns *v1.Namespace) { 146 if ns.Status.Phase != v1.NamespaceTerminating { 147 nc.syncNamespace(ns.Name) 148 } 149 } 150 151 func (nc *NamespaceController) syncNamespace(ns string) { 152 // skip special kubernetes system namespaces 153 if nc.ignoredNamespaces.Contains(ns) { 154 return 155 } 156 nc.queue.Add(types.NamespacedName{Name: ns}) 157 }