Registering Extension Controllers
Before Gardener can manage shoot clusters, it needs to know which required and optional extensions are available in the landscape. The following sections explain the general registration process for extensions.
Extensions
The registration starts by creating Extension resources in the garden runtime cluster. They represent the single source for all deployment aspects an extension may offer (garden, seed, shoot, admission). The gardener-operator takes these resources to deploy the extension controllers to the garden runtime cluster, as well as creating corresponding ControllerRegistration and ControllerDeployment resources in the virtual garden cluster.
Please see the following example of an Extension resource which mainly configures:
- The resource kinds and types the extension is responsible for
- A Reference the OCI Helm chart(s) for the extension admission and optional values
- A Reference the OCI Helm chart for the extension controller
- Optional values for the extension in the garden runtime cluster
- Optional values for the deployment in the seed clusters
apiVersion: operator.gardener.cloud/v1alpha1
kind: Extension
metadata:
name: provider-local
spec:
resources:
- kind: BackupBucket
type: local
- kind: BackupEntry
type: local
- kind: DNSRecord
type: local
- kind: Infrastructure
type: local
- kind: ControlPlane
type: local
- kind: Worker
type: local
deployment:
admission:
runtimeCluster:
helm:
ociRepository:
ref: registry.example.com/gardener/extensions/local/admission-runtime:v1.0.0
virtualCluster:
helm:
ociRepository:
ref: registry.example.com/gardener/extensions/local/adission-application:v1.0.0
values: {}
extension:
helm:
ociRepository:
ref: registry.example.com/gardener/extensions/local/extension:v1.0.0
values:
controllers:
dnsrecord:
concurrentSyncs: 20
runtimeClusterValues:
controllers:
dnsrecord:
concurrentSyncs: 1Operators may use Extensions to observe their status conditions, regularly updated by gardener-operator. They provide more information about whether an extension is currently in use and if their installation was successful.
status:
conditions:
- lastTransitionTime: "2025-03-12T13:46:51Z"
lastUpdateTime: "2025-03-12T13:46:51Z"
message: Extension required for kinds [DNSRecord]
reason: ExtensionRequired
status: "True"
type: RequiredRuntime
- lastTransitionTime: "2025-01-20T10:39:47Z"
lastUpdateTime: "2025-01-20T10:39:47Z"
message: Extension has required ControllerInstallations for seed clusters
reason: RequiredControllerInstallation
status: "True"
type: RequiredVirtual
- lastTransitionTime: "2025-04-03T06:42:37Z"
lastUpdateTime: "2025-04-03T06:42:37Z"
message: Extension has been reconciled successfully
reason: ReconcileSuccessful
status: "True"
type: InstalledControllerRegistrations
In the virtual garden cluster, the native extension registration resource kinds are ControllerRegistration and ControllerDeployment. These resources are usually created by the gardener-operator based on Extensions in the runtime cluster. They provide the gardenlets in the seed clusters with information about which extensions are available and how to deploy them.
NOTE
Before gardener/gardener#9635, the only option to register extensions was via ControllerRegistration/ControllerDeployment resources. In the meantime, they became an implementation detail of the extension registration and should be treated as a gardener internal object. While it's still possible to create them manually (without Extensions), operators should only consider this option for advanced use cases.
Once created, gardener evaluates the registrations and deployments and creates ControllerInstallation resources which describe the request "please install this controller X to this seed Y".
The specification mainly describes which of Gardener's extension CRDs are managed, for example:
apiVersion: core.gardener.cloud/v1
kind: ControllerDeployment
metadata:
name: provider-local
helm:
ociRepository:
ref: registry.example.com/gardener/extensions/local/extension:v1.0.0
values:
controllers:
dnsrecord:
concurrentSyncs: 20
---
apiVersion: core.gardener.cloud/v1beta1
kind: ControllerRegistration
metadata:
name: provider-local
spec:
deployment:
deploymentRefs:
- name: provider-local
resources:
- kind: BackupBucket
type: local
- kind: BackupEntry
type: local
- kind: DNSRecord
type: local
- kind: Infrastructure
type: local
- kind: ControlPlane
type: local
- kind: Worker
type: localThis information tells Gardener that there is an extension controller that can handle BackupBucket, BackupEntry, DNSRecord, Infrastructure, ControlPlane and Worker resources of type local. A reference to the shown ControllerDeployment specifies how the deployment of the extension controller is accomplished.
Deploying Extension Controllers
In the garden runtime cluster gardener-operator deploys the extension controllers directly, as soon as it is considered as required. Deployments in the seed clusters are represented by another resource called ControllerInstallation.
apiVersion: core.gardener.cloud/v1beta1
kind: ControllerInstallation
metadata:
name: provider-local
spec:
deploymentRef:
name: provider-local
registrationRef:
name: provider-local
seedRef:
name: local-1This resource expresses that Gardener requires the provider-local extension controller to run on the local-1 seed cluster.
gardener-controller-manager automatically determines which extension controller is required on which seed cluster and will only create ControllerInstallation objects for those. Also, it will automatically delete ControllerInstallations referencing extension controllers that are no longer required on a seed (e.g., because all shoots on it have been deleted). There are additional configuration options, please see the Deployment Configuration Options section. After gardener-controller-manager has written the ControllerInstallation resource, gardenlet picks it up and installs the controller on the respective Seed using the referenced ControllerDeployment.
Helm Charts
Extensions and ControllerDeployments both need to specify a reference to an OCI Helm chart that contains the extension controller. Those charts are usually provided by the extension and allow their deployment to the garden runtime or seed clusters.
NOTE
Due to legacy reasons, a ControllerDeployment can work with a rawChart instead of an OCI image reference. If your extension does not yet offer an OCI image, you may consider using this option as a temporary workaround. Please note, that rawChart is not supported in Extensions and thus cannot be used for a deployment in the garden runtime cluster.
helm:
ociRepository:
# full ref with either tag or digest, or both
ref: registry.example.com/foo:1.0.0@sha256:abc
---
helm:
ociRepository:
# repository and tag
repository: registry.example.com
tag: 1.0.0
---
helm:
ociRepository:
# repository and digest
repository: registry.example.com
digest: sha256:abc
---
helm:
ociRepository:
# when specifying both tag and digest, the tag is ignored.
repository: registry.example.com
tag: 1.0.0
digest: sha256:abcIf needed, a pull secret can be referenced in the ControllerDeployment.helm.ociRepository.pullSecretRef field.
helm:
ociRepository:
repository: registry.example.com
tag: 1.0.0
pullSecretRef:
name: my-pull-secretThe pull secret must be available in the garden namespace of the cluster where the ControllerDeployment is created and must contain the data key .dockerconfigjson with the base64-encoded Docker configuration JSON.
---
apiVersion: v1
kind: Secret
metadata:
name: my-pull-secret
namespace: garden
labels:
gardener.cloud/role: helm-pull-secret
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <base64-encoded-docker-config-json>The downloaded chart is cached in memory. It is recommended to always specify a digest, because if it is not specified, the manifest is fetched in every reconciliation to compare the digest with the local cache.
Helm Values
No matter where the chart originates from, gardener-operator and gardenlet deploy it with the provided Helm values. The chart and the values can be updated at any time - Gardener will recognize it and re-trigger the deployment process. In order to allow extension controller deployments to get information about the garden and the seed cluster, additional properties are mixed into the values (root level) of every deployed Helm chart:
Additional properties for garden deployment
yamlgardener: runtimeCluster: enabled: true priorityClassName: <priority-class-name-for-extension>Additional properties for seed deployment
yamlgardener: version: <gardener-version> garden: clusterIdentity: <uuid-of-gardener-installation> genericKubeconfigSecretName: <generic-garden-kubeconfig-secret-name> seed: name: <seed-name> clusterIdentity: <seed-cluster-identity> annotations: <seed-annotations> labels: <seed-labels> provider: <seed-provider-type> region: <seed-region> volumeProvider: <seed-first-volume-provider> volumeProviders: <seed-volume-providers> ingressDomain: <seed-ingress-domain> protected: <seed-protected-taint> visible: <seed-visible-setting> taints: <seed-taints> networks: <seed-networks> blockCIDRs: <seed-networks-blockCIDRs> spec: <seed-spec> gardenlet: featureGates: <gardenlet-feature-gates>If the extension is deployed in an self-hosted shoot cluster, then the
.gardener.selfHostedShootClusterfield is additionally propagated and set totrue.
Extension controller deployments can use this information in their Helm chart in case they require knowledge about the garden and the seed environment. The list might be extended in the future.
Deployment Configuration Options
The .spec.extension structure allows to configure a deployment policy. There are the following policies:
OnDemand(default): Gardener will demand the deployment and deletion of the extension controller to/from seed clusters dynamically. It will automatically determine (based on other resources likeShoots) whether it is required and decide accordingly.Always: Gardener will demand the deployment of the extension controller to seed clusters independent of whether it is actually required or not. This might be helpful if you want to add a new component/controller to all seed clusters by default. Another use-case is to minimize the durations until extension controllers get deployed and ready in case you have highly fluctuating seed clusters.AlwaysExceptNoShoots: Similar toAlways, but if the seed does not have any shoots, then the extension controller is not being deployed. It will be deleted from a seed after the last shoot has been removed from it.
Also, the .spec.extension.seedSelector allows to specify a label selector for seed clusters. Only if it matches the labels of a seed, then it will be deployed to it. Please note that a seed selector can only be specified for secondary controllers (primary=false for all .spec.resources[]).
Extension Resource Configurations
The extensibility contract allows the following configuration options per registered extension resource (see resources below):
apiVersion: operator.gardener.cloud/v1alpha1
kind: Extension
metadata:
name: extension-foo
spec:
resources:
- kind: Extension
type: foo
primary: true
autoEnable:
- shoot
clusterCompatibility:
- seed
- shoot
reconcileTimeout: 30s
lifecycle:
reconcile: AfterKubeAPIServer
delete: BeforeKubeAPIServer
migrate: BeforeKubeAPIServerThe autoEnable=[shoot] option specifies that the Extension/foo object shall be created by default for all shoots (unless they opted out by setting .spec.extensions[].enabled=false in the Shoot spec).
The autoEnable=[seed,shoot] option specifies that the Extension/foo can be enabled in seed and shoot clusters.
The reconcileTimeout tells Gardener how long it should wait during its reconciliation flow for the Extension/foo's reconciliation to finish.
primary specifies whether the extension controller is the main one responsible for the lifecycle of the Extension resource. Setting primary to false would allow to register additional, secondary controllers that may also watch/react on the Extension/foo resources, however, only the primary controller may change/update the main status of the extension object. Particularly, only the primary controller may set .status.lastOperation, .status.lastError, .status.observedGeneration, and .status.state. Secondary controllers may contribute to the .status.conditions[] if they like, of course.
Secondary controllers might be helpful in scenarios where additional tasks need to be completed which are not part of the reconciliation logic of the primary controller but separated out into a dedicated extension.
⚠️ There must be exactly one primary controller for every registered kind/type combination. Also, please note that the primary field cannot be changed after creation of the Extension.
Extension Lifecycle
The lifecycle field tells Gardener when to perform a certain action on the Extension (extensions.gardener.cloud/v1alpha1) resource during the reconciliation flows. If omitted, then the default behaviour will be applied. Please find more information on the defaults in the explanation below. Possible values for each control flow are AfterKubeAPIServer, BeforeKubeAPIServer, and AfterWorker. Let's take the following configuration and explain it.
...
lifecycle:
reconcile: AfterKubeAPIServer
delete: BeforeKubeAPIServer
migrate: BeforeKubeAPIServerreconcile: AfterKubeAPIServermeans that the extension resource will be reconciled after the successful reconciliation of thekube-apiserverduring shoot reconciliation. This is also the default behaviour if this value is not specified. During shoot hibernation, the opposite rule is applied, meaning that in this case the reconciliation of the extension will happen before thekube-apiserveris scaled to 0 replicas. On the other hand, if the extension needs to be reconciled before thekube-apiserverand scaled down after it, then the valueBeforeKubeAPIServershould be used.delete: BeforeKubeAPIServermeans that the extension resource will be deleted before thekube-apiserveris destroyed during shoot deletion. This is the default behaviour if this value is not specified.migrate: BeforeKubeAPIServermeans that the extension resource will be migrated before thekube-apiserveris destroyed in the source cluster during control plane migration. This is the default behaviour if this value is not specified. The restoration of the control plane follows the reconciliation control flow.
Due to technical reasons, exceptions apply for different reconcile flows, for example:
- The garden reconciliation doesn't distinguish between
AfterKubeAPIServerandAfterWorker. - The seed reconciliation completely ignores the
lifecyclefield. - The lifecycle value
AfterWorkeris only available duringreconcile. When specified, the extension resource will be reconciled after the workers are deployed. This is useful for extensions that want to deploy a workload in the shoot control plane and want to wait for the workload to run and get ready on a node. During shoot creation the extension will start its reconciliation before the first workers have joined the cluster, they will become available at some later point.