CRD Deletion Safeguarding
This tutorial installs cert-manager using component-operator. Along the way you will see how the HelmGenerator translates Helm hook annotations into component-operator lifecycle annotations, and observe how component-operator protects against premature deletion when foreign instances of managed CRDs still exist in the cluster.
Prerequisites
You need a Kubernetes cluster with Flux source-controller and component-operator installed. If you don’t have one yet, the Cluster Setup guide walks you through creating a local kind cluster with everything in place.
1. Create the Flux source objects
cert-manager publishes its Helm chart to an OCI registry. Create a HelmRepository pointing at the registry and a HelmChart selecting the cert-manager chart:
# cert-manager-source.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: cert-manager
namespace: default
spec:
type: oci
url: oci://quay.io/jetstack/charts
interval: 5m
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmChart
metadata:
name: cert-manager
namespace: default
spec:
chart: cert-manager
sourceRef:
kind: HelmRepository
name: cert-manager
interval: 5m
kubectl apply -f cert-manager-source.yaml
Wait until the chart artifact is fetched:
kubectl get helmchart cert-manager
The READY column should show True before continuing.
2. Create the Component
Create a Component that references the HelmChart source and deploys into the cert-manager namespace. Setting crds.enabled: true bundles the CRD manifests directly into the chart output so that component-operator manages them as regular dependent objects:
# cert-manager-component.yaml
---
apiVersion: core.cs.sap.com/v1alpha1
kind: Component
metadata:
name: cert-manager
namespace: default
spec:
sourceRef:
fluxHelmChart:
name: cert-manager
path: cert-manager
namespace: cert-manager
values:
crds:
enabled: true
kubectl apply -f cert-manager-component.yaml
Watch the component converge:
kubectl wait component cert-manager --for condition=Ready
Observe that component-operator has automatically created the cert-manager namespace and applied all dependent objects in the right order.
3. Explore the dependent objects
Inspecting the inventory
component-operator tracks every resource it has applied in the component’s inventory. You can list the managed objects:
kubectl get component cert-manager -o json | jq .status.inventory
The list includes the CRD definitions, the cert-manager, cert-manager-cainjector, and cert-manager-webhook deployments, their service accounts and RBAC objects, and the services.
Helm hook annotations and their mappings
The cert-manager chart ships a startupapicheck Job that carries standard Helm hook annotations:
# original annotations on the Job in the chart template
helm.sh/hook: post-install
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
When the HelmGenerator renders the chart, it translates these to component-operator annotations on the applied object:
helm.sh/hook-weightin combination withhelm.sh/hook: post-installmaps tocomponent-operator.cs.sap.com/apply-order, placing the Job in its own wave after the main cert-manager resources.helm.sh/hook-delete-policy: before-hook-creationchanges the update policy of the job toRecreate, such that the job will in fact be deleted and created again with the next change being reconciled.helm.sh/hook-delete-policy: hook-succeededmaps tocomponent-operator.cs.sap.com/purge-orderwith the same wave number. This tells component-operator to delete the Job from the cluster at the end of that apply wave, exactly mirroring the Helm hook behaviour.
You can inspect the annotations on the live object to verify (the Job is purged after the first successful run, so catch it while the component is first being applied, or look at what component-operator recorded):
kubectl get job -n cert-manager -l app.kubernetes.io/component=startupapicheck \
-o jsonpath='{.items[0].metadata.annotations}' | jq .
CRD deployment
Because the cert-manager CRDs are included in the chart output (crds.enabled: true), component-operator picks them up as regular dependent objects and applies its implicit ordering: CRD definitions deployed early to ensure that controllers reconciling them can properly start afterwards. Note that the annotation helm.sh/resource-policy: keep in the original Helm output maps to a deletion policy of Orphan for the CRD objects.
4. Create a Certificate using a self-signed Issuer
With cert-manager running, create a ClusterIssuer and a Certificate to verify the installation:
# selfsigned-test.yaml
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example
namespace: default
spec:
secretName: example-tls
issuerRef:
name: selfsigned
kind: ClusterIssuer
dnsNames:
- example.local
kubectl apply -f selfsigned-test.yaml
Wait for the certificate to be issued:
kubectl wait certificate example --for condition=Ready
Once READY is True, cert-manager has signed the certificate and stored the result in the example-tls Secret in the cert-manager namespace.
5. Delete the Component — and observe the wait
Now delete the component:
kubectl delete component cert-manager
The component does not disappear immediately. Check its state:
kubectl get component cert-manager
NAME STATE REASON ...
cert-manager Deleting DeletionBlocked ...
Why deletion is blocked
The cert-manager chart includes CRD definitions for types such as Certificate, ClusterIssuer, and CertificateRequest. component-operator recognises these as extension types it manages. The ClusterIssuer and Certificate you created in step 4 are foreign instances of those types — they exist in the cluster but are not part of the component’s own inventory.
component-operator blocks deletion until all foreign instances of managed extension types are gone. This ensures that the cert-manager controller — which processes finalizers and admission webhooks for those resources — stays alive long enough to handle their removal cleanly. Without this guard, deleting the controller before its CRD instances are gone would leave orphaned resources with stuck finalizers.
Inspect the blocking message in the component status:
kubectl describe component cert-manager
6. Remove the foreign instances and complete deletion
Delete the Certificate and ClusterIssuer. cert-manager will also clean up the associated CertificateRequest and the example-tls Secret:
kubectl delete certificate example
kubectl delete clusterissuer selfsigned
Wait a moment for cert-manager to process the deletions, then check whether any instances of the managed types remain:
kubectl get certificates,clusterissuers,certificaterequests -A
Once no instances remain, component-operator detects the change and proceeds with the component deletion. Confirm that it is gone:
kubectl get component cert-manager
Error from server (NotFound): components.core.cs.sap.com "cert-manager" not found
The component, its deployments, RBAC resources, and all other dependent objects have been fully removed from the cluster.
Not that the CRD objects themselves are still there because the cert-manager maintainers decided to set the helm.sh/resource-policy: keep annotation on the CRDs, which makes component-operator orphan them on deletion. If they wouldn’t have it done this way, and shipped the CRDs in the usual way via the chart’s ./crds subdirectory, the CRDs would have been removed here as well.