Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support crd discovery selector #36639

Merged
merged 27 commits into from
Sep 29, 2022
Merged

Conversation

hzxuzhonghu
Copy link
Member

@hzxuzhonghu hzxuzhonghu commented Dec 27, 2021

Please provide a description of this PR:

Fix #36627

@hzxuzhonghu hzxuzhonghu requested review from a team as code owners December 27, 2021 07:59
@istio-testing istio-testing added do-not-merge/work-in-progress Block merging of a PR because it isn't ready yet. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Dec 27, 2021
@istio-policy-bot istio-policy-bot added area/networking release-notes-none Indicates a PR that does not require release notes. labels Dec 27, 2021
Copy link
Member

@howardjohn howardjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. The API explicitly only applies to discovery, so making it do more will be incompatible.

It would be good to revisit the design doc. I recall we explicitly decided to make it only discovery. If we are changing our minds there we ought to understand why our decisions have changed.

@hzxuzhonghu
Copy link
Member Author

The api does not explicitly say it does not limit CR, and also it does not explicitly say it works on it. Will search the design

@hzxuzhonghu
Copy link
Member Author

Unfortunately, there is even no mention of crd in the design https://docs.google.com/document/d/1S3GPCUBmTmvh09NmpuMCG3vwDTNkVeRhGT3lbLyA4qs/edit?resourcekey=0-a8iMutRwjQElYN5dlgEDdw#heading=h.h3lxcxfhqndp

SO I think there is no compatibility issue, this can be treated as a feature request

@howardjohn
Copy link
Member

Regardless of if it's in the doc dramatically changing the functionality of an existing API is incompatible.

Let's take a step back here. I haven't seen any discussion of the motivations for this change. We already have a lot of 'scoping' mechanisms. What problems are we trying to solve here?

@hzxuzhonghu
Copy link
Member Author

#35092

@hzxuzhonghu
Copy link
Member Author

@howardjohn @ramaraochavali What do i need to promote this feature?

@howardjohn
Copy link
Member

I still don't understand the use case. The issue you linked does not explain why they want this.

@hzxuzhonghu
Copy link
Member Author

An special case i can think of is in multi tenant case, multi istio can be deployed in a single cluster. We donot want the crd{CR} to interference each other.

@hzxuzhonghu
Copy link
Member Author

@howardjohn #31115

@howardjohn
Copy link
Member

Doesn't Sidecar scoping handle this?

In multitenant situations I would imagine its typical to not actually have permission to read from all namespaces?

@howardjohn
Copy link
Member

I think this is a pretty important topic and should probably have a proper design. At the very least, we cannot change the behavior of the existing field or there will be incompatible behavior change when users of discoverySelector upgrade to this commit

@hzxuzhonghu
Copy link
Member Author

I donot think there are many users of discoverySelector who have CR created in namespaces that are not selected.

For sake, I am fine to add a knob for this. We can also propose a discussion about it.

@howardjohn
Copy link
Member

I haven't tested this, but the doc for sidecar workload selector (this is what you mean, right?) does not mention scoping for all CRs but only a handful? Does scoping work on all CRs?

All the CRs it does not scope are already scoped by namespace. For example, if you have a PeerAuthentication if foobar namespace, and have no proxies in foobar, it does nothing.

@emike922
Copy link

For us (non-vanilla) this might be a convenient way to avoid configuration leakage between multiple tenants/istiod control planes. Tenant boundaries could be defined in terms of immutable kubernetes.io/metadata.name labels, allowing to dynamically extend or shrink the scope of each control plane without too much hassle or risk.

For vanilla users, this might be useful to include or exclude namespaces for configuration discovery, for example to ignore "less trusted" namespaces when operating in a shared environment.
It could also be useful to enable testing of revisions with incompatible or maybe just experimental (globally-scoped) configurations without affecting the productive instance.

@kfaseela
Copy link
Member

kfaseela commented Jan 25, 2022

Doesn't Sidecar scoping handle this?

In multitenant situations I would imagine its typical to not actually have permission to read from all namespaces?

@howardjohn sidecar scoping solves part of the issue, but what about the gateways?

@howardjohn
Copy link
Member

@kfaseela Gateways have namespace scoping options already: PILOT_SCOPE_GATEWAY_TO_NAMESPACE or use new Kubernetes Gateway API which has this as default

@kfaseela
Copy link
Member

@kfaseela Gateways have namespace scoping options already: PILOT_SCOPE_GATEWAY_TO_NAMESPACE or use new Kubernetes Gateway API which has this as default

Would try this end to end along with sidecar scoping and get back. But wouldn't it simplify the configuration a lot when we have this supported out of the box, while using discovery selectors? Ideally we don't want to configure additional sidecar config to limit the CRD scope.

@emike922
Copy link

This might also be useful as an option to lock down certain CR discovery settings that cannot be overridden by the user (default mesh-wide sidecar w/o workload selector in root namespace is easily overridden by another sidecar if I understand correctly)

@istio-testing istio-testing added the needs-rebase Indicates a PR needs to be rebased before being merged label Jan 25, 2022
@istio-testing istio-testing removed the needs-rebase Indicates a PR needs to be rebased before being merged label Jan 26, 2022
@SpecialYang
Copy link
Member

We have implemented this feature in production but by little dirty way. We can deploy multiple istiod instances in one K8s cluster for tenant case. These istiod instances can not only locate at individual namespaces, but also locate at the same namespace isolated by labels of CR.

Part of code changes like below.

Server.

if alifeatures.WatchResourcesByNamespaceForPrimaryCluster != "" {
	informerOptions := kubelib.InformerOptions{
		WatchedNamespace: alifeatures.WatchResourcesByNamespaceForPrimaryCluster,
		Label:            alifeatures.WatchResourcesByLabelForPrimaryCluster,
	}
	s.kubeClient, err = kubelib.NewClientWithInformerOptions(kubelib.NewClientConfigForRestConfig(kubeRestConfig), informerOptions)
} else {
	s.kubeClient, err = kubelib.NewClient(kubelib.NewClientConfigForRestConfig(kubeRestConfig))
}

KubeClient.

func NewClientWithInformerOptions(clientConfig clientcmd.ClientConfig, options InformerOptions) (*client, error) {
	var c client
	var err error
	clientFactory := newClientFactory(clientConfig)
	c.clientFactory = clientFactory
	c.config, err = clientFactory.ToRESTConfig()
	if err != nil {
		return nil, err
	}
	c.restClient, err = clientFactory.RESTClient()
	if err != nil {
		return nil, err
	}
	c.discoveryClient, err = clientFactory.ToDiscoveryClient()
	if err != nil {
		return nil, err
	}
	c.mapper = restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(c.discoveryClient))
	c.Interface, err = kubernetes.NewForConfig(c.config)
	c.kube = c.Interface
	if err != nil {
		return nil, err
	}
	c.kubeInformer = informers.NewSharedInformerFactoryWithOptions(c.Interface, resyncInterval, informers.WithNamespace(options.WatchedNamespace))
	c.metadata, err = metadata.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	c.metadataInformer = metadatainformer.NewFilteredSharedInformerFactory(c.metadata, resyncInterval, options.WatchedNamespace, nil)
	c.dynamic, err = dynamic.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	c.dynamicInformer = dynamicinformer.NewFilteredDynamicSharedInformerFactory(c.dynamic, resyncInterval, options.WatchedNamespace, nil)
	c.istio, err = istioclient.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	if options.Label != "" {
		c.istioInformer = istioinformer.NewSharedInformerFactoryWithOptions(c.istio, resyncInterval, istioinformer.WithNamespace(options.WatchedNamespace), istioinformer.WithTweakListOptions(func(listOpts *metav1.ListOptions) {
			listOpts.LabelSelector = options.Label
		}))
	} else {
		c.istioInformer = istioinformer.NewSharedInformerFactoryWithOptions(c.istio, resyncInterval, istioinformer.WithNamespace(options.WatchedNamespace))
	}
	c.gatewayapi, err = gatewayapiclient.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	if options.Label != "" {
		c.gatewayapiInformer = gatewayapiinformer.NewSharedInformerFactoryWithOptions(c.gatewayapi, resyncInterval, gatewayapiinformer.WithNamespace(options.WatchedNamespace), gatewayapiinformer.WithTweakListOptions(func(listOpts *metav1.ListOptions) {
			listOpts.LabelSelector = options.Label
		}))
	} else {
		c.gatewayapiInformer = gatewayapiinformer.NewSharedInformerFactoryWithOptions(c.gatewayapi, resyncInterval, gatewayapiinformer.WithNamespace(options.WatchedNamespace))
	}
	c.mcsapis, err = mcsapisClient.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	if options.Label != "" {
		c.mcsapisInformers = mcsapisInformer.NewSharedInformerFactoryWithOptions(c.mcsapis, resyncInterval, mcsapisInformer.WithNamespace(options.WatchedNamespace), mcsapisInformer.WithTweakListOptions(func(listOpts *metav1.ListOptions) {
			listOpts.LabelSelector = options.Label
		}))
	} else {
		c.mcsapisInformers = mcsapisInformer.NewSharedInformerFactoryWithOptions(c.mcsapis, resyncInterval, mcsapisInformer.WithNamespace(options.WatchedNamespace))
	}
	c.extSet, err = kubeExtClient.NewForConfig(c.config)
	if err != nil {
		return nil, err
	}
	return &c, nil
}

@istio-policy-bot istio-policy-bot added the lifecycle/stale Indicates a PR or issue hasn't been manipulated by an Istio team member for a while label Mar 12, 2022
return i.Lister()
}
return i.Lister().ByNamespace(namespace)
informer: informer.NewFilteredSharedIndexInformer(cl.namespacesFilter, i.Informer()),
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lister: cache.NewGenericLister(h.informer.GetIndexer(), schema.Resource().GroupVersionResource().GroupResource())

can we do this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely we can implement it in theory, but I think currently it is enough to use

NewGenericLister does not provide much benefit to our case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? It makes the code much simpler as we do not need to do the weird indexer operations we have replaced it with, and is standard k8s practice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you can see with generic lister for clusterscoped resource, we need to handle differently. however with underlying indexer, we do not need to care about it, that is why I believe this way is more simple

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, all the indexer related code are in pkg/kube, the fiteredInformer does only provide Get List interface.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how it's simpler. The lister requires 1 single if statement here - the indexer approach has like 50 lines of added complexity throughout the PR of more complex error handling, type casting, and still has the if for namespaces but at each List call instead of in one single place

Listers are the Kubernetes standard for his to list objects, I don't see a compelling reason to move away from that. It also seems unrelated to the rest of the PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericLister returns runtime.Object, which also need type cast, error handling. I do not have strong preference though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but we don't need this: https://github.com/istio/istio/pull/36639/files#diff-eff3d408ac506e9395b51f480c999a588f9fa408e392ce77506f25ceae00d6a1R328

and we now have to do:

	item, _, err := h.informer.GetIndexer().GetByKey(KeyFunc(namespace, name))
	if item == nil || err != nil {

instead of just

	obj, err := h.lister(namespace).Get(name)
	if err != nil {

@istio-testing istio-testing added the needs-rebase Indicates a PR needs to be rebased before being merged label Sep 26, 2022
@istio-testing istio-testing removed the needs-rebase Indicates a PR needs to be rebased before being merged label Sep 27, 2022
Copy link
Member

@howardjohn howardjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall it's looking right but it's changing a lot of unrelated things which I think add complexity

ingressLister: ingressInformer.Lister(),
classes: classes,
serviceInformer: serviceInformer.Informer(),
serviceLister: serviceInformer.Lister(),
}

if options.DiscoveryNamespacesFilter != nil {
c.filteredIngressInformer = informer.NewFilteredSharedIndexInformer(options.DiscoveryNamespacesFilter.Filter, ingressInformer.Informer())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need for if, the result is the same

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still applicable but not blocking

@@ -74,6 +74,10 @@ func (d *discoveryNamespacesFilter) Filter(obj any) bool {
return true
}

if ns, ok := obj.(string); ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to filter out namespaces, which is useful for namespace controller

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not used in this PR, right? Just the future?

In the namespace controllre PR we discussed making a FilterObject(obj Object) and FilterNamespace(namespace string). Instead of doing all the type casting, etc. This is more explicit API

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Copy link
Member

@howardjohn howardjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, we can followup on the rest

ingressLister: ingressInformer.Lister(),
classes: classes,
serviceInformer: serviceInformer.Informer(),
serviceLister: serviceInformer.Lister(),
}

if options.DiscoveryNamespacesFilter != nil {
c.filteredIngressInformer = informer.NewFilteredSharedIndexInformer(options.DiscoveryNamespacesFilter.Filter, ingressInformer.Informer())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still applicable but not blocking

@@ -802,7 +803,7 @@ func (c *Controller) syncNodes() error {

func (c *Controller) syncServices() error {
var err *multierror.Error
services := c.serviceInformer.GetIndexer().List()
services, _ := c.serviceInformer.List("")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use metav1.NamespaceAll

if c.opts.DiscoveryNamespacesFilter != nil {
ec.filteredInformer = informer.NewFilteredSharedIndexInformer(c.opts.DiscoveryNamespacesFilter.Filter, dInformer.Informer())
} else {
ec.filteredInformer = informer.NewFilteredSharedIndexInformer(nil, dInformer.Informer())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment about reducing if

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.opts.DiscoveryNamespacesFilter can be nil, this can be optimized later, because we need to filter namespace string as well

nsInformer := kubeclientset.KubeInformer().Core().V1().Namespaces().Informer()
_ = nsInformer.SetTransform(kube.StripUnusedFields)
nsLister := kubeclientset.KubeInformer().Core().V1().Namespaces().Lister()
controller.DiscoveryNamespacesFilter = filter.NewDiscoveryNamespacesFilter(nsLister, meshWatcher.Mesh().GetDiscoverySelectors())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to protect this with the feature flag?

@istio-testing istio-testing merged commit ffe8a3f into istio:master Sep 29, 2022
@hzxuzhonghu hzxuzhonghu deleted the crd-filter branch September 29, 2022 04:04
howardjohn added a commit to howardjohn/istio that referenced this pull request Oct 3, 2022
Address some followup comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/networking lifecycle/automatically-closed Indicates a PR or issue that has been closed automatically. release-notes-none Indicates a PR that does not require release notes. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DiscoverySelectors should also work on CRs
7 participants