Migrate a Deployer to a NamespacedDeployer
Goal
Replace a cluster-scoped Deployer with a NamespacedDeployer while the deployed objects keep running.
You’ll end up with
- A
NamespacedDeployerthat owns and reconciles the objects theDeployerdeployed - The same objects, with unchanged UIDs, no longer owned by the old
Deployer
Estimated time: ~10 minutes
Prerequisites
- OCM Controllers with the
NamespacedDeployercontroller enabled (Helm valuemanager.namespacedDeployer.enabled, on by default) - The
NamespacedDeployerlives in the namespace of theDeployer’sResource. Its OCM configuration references must point to that namespace as well, see Kubernetes Deployer.
⚠️ Never delete an active Deployer before the hand-over. Its finalizer prunes everything it deployed.
The examples use a Deployer named my-deployer that references the Resource my-resource in namespace my-team.
Steps
Create the service account
The NamespacedDeployer applies with a service account. Grant it the kinds the Deployer deploys, as shown in
Configure Custom RBAC for Deployers. Use a Role and RoleBinding in the
target namespace for objects in other namespaces, and a ClusterRole and ClusterRoleBinding only for
cluster-scoped kinds such as kro ResourceGraphDefinitions:
kubectl create serviceaccount my-deployer -n my-team
kubectl create role my-deployer -n my-team \
--verb=get,list,watch,create,update,patch,delete --resource=deployments.apps,services
kubectl create rolebinding my-deployer -n my-team --role=my-deployer --serviceaccount=my-team:my-deployerSuspend the Deployer
Stop the Deployer from reconciling, so it does not re-apply its ownership while the NamespacedDeployer takes over:
kubectl patch deployer.delivery.ocm.software my-deployer --type merge -p '{"spec":{"suspend":true}}'Release the objects from the Deployer’s ApplySet
Every deployed object carries the applyset.kubernetes.io/part-of label of the Deployer’s ApplySet, and a
deployer refuses to take over objects of another ApplySet. Look up the ApplySet ID and the kinds it contains:
kubectl get deployer.delivery.ocm.software my-deployer \
-o jsonpath='{.metadata.labels.applyset\.kubernetes\.io/id}{"\n"}{.metadata.annotations.applyset\.kubernetes\.io/contains-group-kinds}{"\n"}'You should see the ID and the kinds, for example applyset-uw2P...-v1 and Deployment.apps,Service.
Remove the label from every kind in that list. --all-namespaces also covers objects the Deployer placed in
other namespaces, and cluster-scoped kinds such as ResourceGraphDefinition.kro.run work the same way:
export APPLYSET_ID=<id from above>
kubectl label deployments.apps,services --all-namespaces \
-l applyset.kubernetes.io/part-of=$APPLYSET_ID applyset.kubernetes.io/part-of-Replace deployments.apps,services with the kinds from your list. Afterwards this should print nothing for each
kind:
kubectl get deployments.apps,services --all-namespaces -l applyset.kubernetes.io/part-of=$APPLYSET_IDCreate the NamespacedDeployer
apiVersion: delivery.ocm.software/v1alpha1
kind: NamespacedDeployer
metadata:
name: my-deployer
namespace: my-team
spec:
resourceRef:
name: my-resource
serviceAccountName: my-deployerApply it and wait until it is ready:
kubectl apply -f namespaced-deployer.yaml
kubectl wait namespaceddeployer.delivery.ocm.software/my-deployer -n my-team --for=condition=Ready --timeout=5mBoth deployers apply with the same server-side apply field manager, so the NamespacedDeployer adopts the released
objects and replaces their owner reference. Check one of them:
kubectl get deployment <name> -n my-team -o jsonpath='{.metadata.ownerReferences[*].kind}'You should see: NamespacedDeployer.
Remove the Deployer
Pick one of the two ways. Both leave the objects in place.
Mark the Deployer for deletion, then unsuspend it. It runs its finalizers before it would apply again, and its
prune finds nothing because no object carries its ApplySet ID anymore.
kubectl delete deployer.delivery.ocm.software my-deployer --wait=false
kubectl patch deployer.delivery.ocm.software my-deployer --type merge -p '{"spec":{"suspend":false}}'Remove the finalizers so no cleanup runs, then delete the Deployer without cascading to its dependents.
kubectl patch deployer.delivery.ocm.software my-deployer --type json \
-p '[{"op":"remove","path":"/metadata/finalizers"}]'
kubectl delete deployer.delivery.ocm.software my-deployer --cascade=orphanVerify the hand-over
kubectl get deployer.delivery.ocm.software my-deployer
kubectl get namespaceddeployer.delivery.ocm.software my-deployer -n my-teamYou should see: NotFound for the Deployer and Ready for the NamespacedDeployer. The deployed objects keep
their UIDs.
Reduce the controller’s permissions
Once no Deployer is left in the cluster, the controller only needs get, list and watch on the deployed kinds.
Remove the write verbs that you granted it for the Deployer, as described in
Grant the controller read access.
kubectl get deployers.delivery.ocm.softwareYou should see: No resources found.
Troubleshooting
Symptom: resource belongs to a different ApplySet
Cause: An object still carries the Deployer’s applyset.kubernetes.io/part-of label.
Fix: Repeat the release step for the kind named in the message.
Symptom: the NamespacedDeployer reports forbidden
Cause: The service account lacks a verb, kind or namespace that the manifest needs.
Fix: Add the missing rule to the service account’s Role or ClusterRole. The message names the kind and verb.
Symptom: is outside of namespace
Cause: An OCM configuration reference points to another namespace.
Fix: Copy the referenced Secret or ConfigMap into the NamespacedDeployer’s namespace and reference it there.
Symptom: the Deployer stays in Terminating
Cause: It is still suspended, so its finalizers never run.
Fix: Unsuspend it, or remove its finalizers as in the orphan tab.