Global Owner

This is an example controller implementation using metacontroller for a Custom Resource called GlobalOwner, which is responsible for setting ownership on any kubernetes native or custom resources across the cluster.

Examples

This example presents the functionality by adopting ConfigMaps matching label selector expression and setting ownership on those resources across the cluster, but the same process could be applied to any resource kind existing in the cluster. For each resource type the permissions to own the object will be granularly adjusted.

Steps

To create an example GlobalOwner and a ConfigMap:

kubectl apply -f example

A GlobalOwner resource will be created and will adopt all resources matching the given GVKs provided in the spec.childResources field assuming they match the label selector spec.selector value.

This example shows how a GlobalOwner resource could be used to adopt all ConfigMaps that have an adopt label specified.

Example ConfigMap and a Secret resource located in example/example-resource-set.yaml:

apiVersion: v1
kind: ConfigMap
metadata:
  name: test
  namespace: default
  labels:
    adopt: "true"
data:
  some: "value"
  other: "value"
---
apiVersion: v1
kind: Secret
metadata:
  name: test
  namespace: default
  labels:
    adopt: "true"
data: {}

After these resources are applied in the cluster, the ownership reference should be set on the resource.

$ kubectl get globalowner -o yaml

returns

apiVersion: globalowner.metacontroller.io/v1alpha1
kind: GlobalOwner
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"globalowner.metacontroller.io/v1alpha1","kind":"GlobalOwner","metadata":{"annotations":{},"name":"global-owner"},"spec":{"selector":{"matchExpressions":[{"key":"skip","operator":"DoesNotExist"}]}}}
  creationTimestamp: "2023-09-11T11:18:25Z"
  generation: 1
  name: global-owner
  resourceVersion: "3436"
  uid: 53f365d2-4c8e-469b-b09a-62994a968f8f
spec:
  childResources:
  - apiVersion: v1
    resource: secrets
    namespace: default
    names:
    - test
  - apiVersion: v1
    resource: configmaps
status:
  observedGeneration: 1
$ kubectl get cm test -n default -o yaml

will show that a ConfigMap has an OwnershipReference pointing to the GlobalOwner resource.

apiVersion: v1
data:
  other: value
  some: value
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"other":"value","some":"value"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"adopt":"true"},"name":"test","namespace":"default"}}
  creationTimestamp: "2023-09-11T12:23:41Z"
  labels:
    adopt: "true"
  name: test
  namespace: default
  ownerReferences:
  - apiVersion: globalowner.metacontroller.io/v1alpha1
    blockOwnerDeletion: true
    controller: true
    kind: GlobalOwner
    name: global-owner
    uid: 6a3269db-f713-41cb-8111-6155f2c2b4b7
  resourceVersion: "1602"
  uid: e3aa2bc8-7615-44de-a788-e6a5296b47bb

API

This section describes APIs used to interact with the Global Owner controller.

Global Owner

This document describes the Global Owner resource.

Group Owner

This document describes the internal Group Owner resource role, resource fields and behaviour in the cluster.

Global Owner resource

This is an API specification for the GlobalOwner resource.

Resources

This is a list of structures representing the GlobalOwner resource API.

GlobalOwner

type GlobalOwner struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`

	Spec   GlobalOwnerSpec   `json:"spec"`
	Status GlobalOwnerStatus `json:"status,omitempty"`
}

GlobalOwner spec

type GlobalOwnerSpec struct {

	// Global label selector value. Will be applied to all child resources
	// if resource does not have its own.
	Selector       *metav1.LabelSelector `json:"selector,omitempty"`

	// A list of child resources which global owner should adopt.
	// Each child resource is passed to generated Group Owner object spec.
	//
	// The order of the resources in the list specifies removal ordering,
	// top to bottom.
	ChildResources []ChildResource       `json:"childResources"`
}

GlobalOwner resource rule

type ResourceRule struct {

	// Resource api version, for example: v1
	APIVersion string `json:"apiVersion"`

	// Resource plural name, for example: secrets
	Resource   string `json:"resource"`
}

GlobalOwner child resource

type ChildResource struct {
	ResourceRule `json:",inline"`

	// A list of unique names for the resources to adopt
	// Mutually exclusive with the selector value.
	Names     []string              `json:"names,omitempty"`

	// A namespace, where the resources should be looked up.
	Namespace string                `json:"namespace,omitempty"`

	// Label selector value for resource group.
	// Mutually exclusive with the names and namespace values.
	// Has precedence on Names/Namespaces if specified.
	Selector  *metav1.LabelSelector `json:"selector,omitempty"`
}

GlobalOwner status

type GlobalOwnerStatus struct {
	ObservedGeneration int `json:"observedGeneration,omitempty"`
}

Group Owner resource

This is an API specification for the GroupOwner resource.

Resources

This is a list of structures representing the GroupOwner resource API.

GroupOwner resource

type GroupOwner struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`

	Spec   GroupOwnerSpec   `json:"spec"`
	Status GroupOwnerStatus `json:"status,omitempty"`
}

GroupOwner spec

type GroupOwnerSpec struct {
	Selector      *metav1.LabelSelector `json:"selector,omitempty"`
	ChildResource ChildResource         `json:"childResource"`
}

GroupOwner status

type GroupOwnerStatus struct {
	OwnedResources     []OwnedResource `json:"ownedResources,omitempty"`
	ObservedGeneration int             `json:"observedGeneration,omitempty"`
}

OwnedResource

type OwnedResource struct {
	metav1.TypeMeta `json:",inline"`

	Name      string `json:"name,omitempty"`
	Namespace string `json:"namespace,omitempty"`
}

Concepts

This section contains the concepts and the architecture of individual components.

Terms

Bootstrap

This page describes the bootstrap phase of the controller.

Adopt

This page descibes the adoption process for the selected resources

Ownership chain

This page descibes the ownership chain process between individual group owner resources

Deletion

This page describes how the deletion process is being performed

Bootstrap

This process involves creating infrastructure resources, bound to the parent GlobalOwner object by ownership references, and allowing the child objects to be adopted by the GlobalOwner resource later.

Steps

Bootstrap process is carried by a single DecoratorController replica created during installation phase.

The process consists of 2 parts:

  1. For each child group in the GlobalOwner resource, create a CompositeController replica.
  2. Create an aggregated ClusterRole with permissions required to access every resource from child groups. The rules in this role will aggregate to the metacontroller replica installed with the controller.

A ClusterRole per each group has only these permissions:

  • get
  • list
  • watch
  • update - required for the created CompositeController to apply ownership reference on the resource.

Cleanup

Upon removal of the parent GlobalOwner resource, every bootstrapped component will be deleted using kubernetes garbage collection. This allows for the aggregated permissions to be scaled down upon removal, as the ClusterRole is getting removed.

Adopt

This process involves listing and watching resource groups specified under the ChildResources field in the GlobalOwner resource, and setting ownership references on each observed resource, using metacontroller adoption rules.

Process

Using collected objects from the related list collected using the customize hook semantics, an adopt CompositeController is setting a list of children resources in the sync hook response matching the namespace and name of each related resource. Returned list of children may look like:

[
    {
        "kind": "Secret",
        "apiVersion": "v1",
        "name": "owned-secret",
        "namespace": "default",
    }, {
        "kind": "ConfigMap",
        "apiVersion": "v1",
        "name": "owned-config-map",
        "namespace": "default",
    },
]

As every object in the list is already created in the cluster, the only change the metacontroller replica will do, is to apply the ownership references on the object, pointing to the parent GroupOwner resource instance.

Cleanup

Upon removal of the parent GlobalOwner resource, each GroupOwner resource and subsequentially all adopted resources will be removed in the order specified by the ChildResources content. Resource groups will be removed from top to bottom of the list.

Ownership chain

This process creates a GroupOwner resource per each resource group specified in the parent GlobalOwner resource spec.

Process

The process of setting up ownership chain consists of 2 parts.

When the bootstrap process finishes, each created CompositeController is creating a GroupOwner resource replica from the parents GlobalOwner children resource group.

Each of the created GroupOwner resources have a label matching the CompositeController label selector, for narrowing down the adopted resources to the target GroupOwner object. Secrets owned by the Secret group owner.

Once all of the GroupOwner resources are created, the controller is setting ownership references between the GroupOwner resources. This (in the best case scenario - see the issue) allows the removal process to fully rely on the kubernetes garbage collection by using foreground cascading deletion finalizer on the GroupOwner resource. More in the design document.

Deletion

This process specifies how the ordered deletion is handled, when the GlobalOwner resource is removed.

Deletion

TODO

Opt-out

As the resource is adopted using owner reference to the parent object, when the parent object is removed using foreground or background policy, the adopted resource will be removed as well. To opt out of this behavior, the metacontroller instance should be scaled down, the finalizer metacontroller.io/compositecontroller-<global-owner-name> should be removed from the resource, and then the GlobalOwner resource can be deleted with the orphan deletion policy. Adopted resources will stay untouched, however, the CompositeController and the ClusterRole created by the GlobalOwner resource will stay in the cluster, so the permission scope will not be reduced. Those will have to be cleaned up manually.

Guide

This section contains configuration and installation tips for Global Owner resource controller

Installation

This page describes how to install Global Owner controller using kustomize.

Helm installation

This page describes how to install Global Owner controller using Helm.

Configuration

This page described how a Global Owner resource could be configured to apply ownership on your resources.

Configuration

This page describes how to configure the Global Owner resource.

Helm values

ParameterDescriptionDefault
namespaceOverrideNamespace override value for installed global owner controller instance""
nameOverrideName override value for installed global owner controller instance""
imageJsonnet image to use for controller functionalityghcr.io/danil-grigorev/jsonnetd:v0.3.1
aggregationLabelClusterRole aggregation label to provide granular permission scaling to the owned set of resources. Matches the metacontroller ClusterRole aggregation label value.rbac.metacontroller.k8s.io/aggregate-to-metacontroller

Install Global Owner using Helm

This page describes how to install Global Owner controller using helm.

Building the chart from source code

The chart can be built from repository source:

git clone https://github.com/Danil-Grigorev/global-owner.git
cd global-owner
make release-chart

Installing the chart from package

helm install globalowner out/package/global-owner-v*.tgz --wait

Installing chart from ghcr.io

Charts are published as packages on ghcr.io

To install it from registry:

helm install globalowner -n metacontroller --create-namespace oci://ghcr.io/danil-grigorev/global-owner --version=v0.5.1 --wait

Installation

This page describes how to install Global Owner resource controller using kustomize.

Prerequisites

Deploy the controller

kubectl apply -k https://github.com/danil-grigorev/global-owner/v1

or locally from the cloned repo:

kubectl apply -k v1

Design

Ordered Deletion

This is a design document for ordered resource deletion using only ownership references and native kubernetes garbage collection.

Ordered Deletion

Resources deletion is processed in the order specified on the GlobalOwner resource.

Deletion process

Blockers

Unfortunately this design does not work, because of an issue in the garbage collector implementation: https://github.com/kubernetes/kubernetes/issues/121113. Until it is fixed, this is just a concept.

Deletion

As the resource is adopted using owner reference to the parent object, when the parent object is removed using foreground or background policy, the adopted resource will be removed as well. To opt out of this behavior, the metacontroller instance should be scaled down, the finalizer metacontroller.io/compositecontroller-<global-owner-name> should be removed from the resource, and then the GlobalOwner resource can be deleted with the orphan deletion policy. Adopted resources will stay untouched, however, the CompositeController and the ClusterRole created by the GlobalOwner resource will stay in the cluster, so the permission scope will not be reduced. Those will have to be cleaned up manually.