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  }