Skip to content
This repository has been archived by the owner on Jul 22, 2022. It is now read-only.

Support for cluster-scoped CRDs and namespace-scoped resources in LambdaController #2

Closed
joelanford opened this issue Dec 16, 2017 · 15 comments

Comments

@joelanford
Copy link

I saw this demo'ed at KubeCon and it gave me some inspiration to try writing a custom LambdaController sync hook.

I'm attempting to watch cluster-scoped custom resources to manage both cluster-scoped and namespace-scoped child resources (for now, namespaces and services). However, for all of the namespace-scoped resources in the returned children array, I get the following error:

an empty namespace may not be set during creation

I've confirmed that the response coming from my controller contains a namespace for these services, so I'm a bit confused.

Looking through the lambda_controller.go code, it appears that there's a limitation that child resources must be in the same namespace as the parent resource? Just want to confirm if what I'm trying to do is possible.

Thanks!

@enisoc
Copy link

enisoc commented Dec 18, 2017

This isn't supported yet, but it's something I plan to add.

Currently, the parent and children have to be in the same namespace. Part of this is because the standard way to express parent-child relationships for k8s API objects (OwnerReference) doesn't support cluster-scoped or cross-namespace references.

This means we can't rely on the garbage collector to clean up children when the parent goes away, and we can't rely on ControllerRef to prevent fighting among controllers with overlapping label selectors.

@marcwickenden
Copy link

As requested by @enisoc my use case is automating the deployment of individual namespaces containing various deployments/services + an ingress on a multi-tenant platform. The namespaces come and go and each namespace belongs to a tenant for ease of management. Ideally we could return the namespace JSON (and an ingress-controller deployment) in a hook in response to the generation of a custom resource. Then have all child objects go into the same "parent" namespace. Make sense?

@joelanford
Copy link
Author

This is also my use case. Thanks for describing it so concisely!

@enisoc
Copy link

enisoc commented Jun 13, 2018

Thanks for the details. The GC problem mentioned above is still the main blocker as far as I can tell. I haven't seen any indication that the core k8s GC is going to support cluster-scoped or cross-namespace cleanup any time soon, so I'm going to try to think of a way to make this possible without waiting for that.

Just to start some brainstorming:

One potential idea is for Metacontroller to place a finalizer on the parent object so it doesn't disappear immediately when deleted. Since Metacontroller presumably already had a way to decide which child objects it would have sent to your hook, it should know enough to be capable of cleaning those up for you before removing the finalizer.

One limitation of this approach is that it doesn't prevent other controllers from stealing your children, because they would have no ControllerRef (since ControllerRef doesn't allow you to specify cross-namespace references) and would thus look like orphans.

Another option might be for Metacontroller to create "proxy" parent objects in the same namespace as the children. This would prevent the children from looking like orphans. Then when the real parent is deleted, our finalizer will go and delete the proxy parents. Finally, the regular GC will delete the children since the proxy parent is now gone.

@janetkuo @liyinan926 @crimsonfaith91 What do you think?

@enisoc
Copy link

enisoc commented Jun 13, 2018

Another idea is a "namespace controller" that lets you create singleton objects in each namespace. We would call your hook once for each namespace, as namespaces are added/updated, and let you return objects to create in that namespace. You could use labels or annotations on the namespaces to decide what to do, if you need extra parameters or if you only want to process certain namespaces.

This would be a more limited pattern, but it bypasses the GC problem discussed above. For the use case that @marcwickenden described, this would mean you directly create the namespace (with whatever annotations you need) instead of creating a cluster-scoped CRD that in turn creates a namespace.

@marcwickenden
Copy link

I nearly wrote this exact idea in Slack! Wasn’t sure if that was deviating from the model too much.

@crimsonfaith91
Copy link
Contributor

crimsonfaith91 commented Jun 14, 2018

I lean towards creating "proxy" parent objects. As k8s grows, there is possibility for GC to support cross-namespace cleanup in future. We can add an annotation for all created "proxy" parent objects so that we can delete them easily when we have cross-namespace GC. This can be done with 2 steps:

  1. use annotation key to identify all "proxy" parents
  2. for each parent, set OwnerReferences field of its children to real parent, which can be found from annotation value in form of <namespace>/<real-parent-name>

Namespace controller is a good idea, but if we are adding labels or annotations to decide what to do with the namespaces, it may be easier to do so with the "proxy" parent objects. Furthermore, I don't think we should bypass GC as this involves parent-children relationship.

@rlguarino
Copy link
Contributor

It is possible that the proposed #59 changes work for this use case? It might require two controllers but the MapController seems like it would be good for bridging the Cluster -> Namespace scoped boundary.

@enisoc
Copy link

enisoc commented Jul 6, 2018

@rlguarino That's a good point. If MapController could work on Namespace objects, the "namespace controller" would be unnecessary. But since MapController supports an explicit parent object, we would still need to implement cross-namespace GC so that the output objects in various namespaces get cleaned up when the parent is deleted.

Another alternative could be to add limited support for Namespace objects in DecoratorController. That is, Namespace would be the only cluster-scoped object that DecoratorController can add attachments to. This would bypass the fact that OwnerRef-based GC doesn't work across namespace boundaries, because when a namespace is deleted, the objects in that namespace get cleaned up by a separate GC that doesn't rely on OwnerRef.

The downside is that this introduces a special case in DecoratorController -- one particular resource that behaves differently. The proposed NamespaceController API would essentially be this special case of DecoratorController, but pulled out to keep things clean.

@rlguarino
Copy link
Contributor

I did some experiments this morning with cluster scoped owner references on a GKE cluster (1.10.5-gke.0) and it looks like it's working to me. Reading through this issue and the PR that closed it it looks like Cluster scoped parents should be supported.

I was able to create a create a CluterRoleBinding that references a ClusterRole owner, when I deleted the parent ClusterRole (foreground) the child was removed. When I deleted using --cascade=true the owner reference was removed orphaning the child. The same thing happened when I referenced a ClusterRole from a Service in the default namespace.

Parent Child Expected Behavior (Deleting parent deletes the child)
Cluster Cluster 👍
Cluster Namespace 👍
Namespace Namespace 👍
Namespace Cluster ❓ (1)
Namespace Namespace (different) ❓ (2)

So to me it looks like cluster owners should always work, namespaced to namespaced should also work, but I'm not sure how namespaced parents of cluster resources would work..

WIthout too much magic, I think it would be possible to make ConpositeController/DecoratorController/MapController support look something like the above matrix by merging #62.

(1) I haven't tested this, I don't expect it to make any sense.
(2) I'm not really sure how useful cross-namespace support would be so I've been mostly ignoring the case where both parent and child resources are namespaced and they exist in different namespaces.

@enisoc
Copy link

enisoc commented Jul 6, 2018

@rlguarino That's great news! Thanks for the research! Apparently my understanding has been out of date.

I've merged #62. Let's see what else needs to change on the Metacontroller side to make this work.

One problem is that the sync hook API assumes everything is in the same namespace. For example, the children map is keyed only by name, not namespace. We'll need to make an API change to support namespaced children for a cluster-scoped parent, since the names might collide across namespaces.

There are also likely other places where we need to relax the assumption that everything is in the same namespace, but that's definitely feasible if the built-in GC supports cluster-scoped OwnerRefs.

@rlguarino
Copy link
Contributor

rlguarino commented Jul 7, 2018 via email

@enisoc
Copy link

enisoc commented Jul 11, 2018

@rlguarino It would be awesome if you're able to take a stab when you get back! Please update this thread if you do (or don't) so we can avoid overlap. Also feel free to bug me in the #metacontroller channel on Kubernetes Slack if you want to discuss anything synchronously.

@rlguarino
Copy link
Contributor

Based on our conversation in #metacontroller channel on Kubernetes Slack I put together a POC PR #71

rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
rlguarino added a commit to rlguarino/metacontroller that referenced this issue Sep 17, 2018
This CL adds initial support for clustered parents the decorator
controller. The composite controller will work as long as you're not
using rolling updates.

This CL changes the behavior of the client map which is sent as
`attachments` or `children` in the DecoratorController and
CompositeController, respectively. The new keys can be thought of as a
relative path to the child. When both the parent and child are at the
same scope - either both namespaced or both clustered - the key is just
the child's name. When the parent is clustered and the child is
namespaced the relative are relative - the children's keys will always
be prefaced with the namespace - this is to disambiguate between two
children with the same name in different namespaces.

To test this change this CL also adds two examples. The first example is
of a decorator controller that creates a "reader" ClusterRole, similar
to the default roles and rolebindings for each CRD with the
`enable-default-roles` annotation.

The second is a decorator controller which creates role bindings in the
default namespace that bind the default service account to clusterrolebindings.

Closes GoogleCloudPlatform#2
@enisoc enisoc closed this as completed in f601cde Sep 18, 2018
crimsonfaith91 added a commit to crimsonfaith91/metacontroller that referenced this issue Oct 10, 2018
@jianzhangbjz
Copy link

Hi, Guys

Just to chime in, I was wondering how to understand the difference between the cluster-scoped and the namespaced CRD. Actually, we can get the Kind that defines by the namespaced CRD across the whole cluster. So, what's the namespaces-scoped role?I could not find more details in CRD.
For example:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct

HTTP path:

/apis/stable.example.com/v1/namespaces/*/crontabs/status

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants