kubernetes/kops · error
cluster ownerRef not set on CAPI cluster
Error message
cluster ownerRef not set on CAPI cluster %v/%v
What it means
getKopsClusterFromCAPICluster finds the kops Cluster that owns a CAPI Cluster by scanning capiCluster's ownerReferences for one with Kind "Cluster" and APIVersion prefixed "kops.k8s.io/". This error is thrown when no such owner reference exists, meaning the CAPI Cluster is not linked to any kops.k8s.io Cluster and the kops Cluster name cannot be derived.
Solutions
- Ensure the CAPI Cluster is created by the kops CAPI machinery so it sets the kops.k8s.io Cluster ownerRef; delete and recreate through the normal flow
- Manually add the correct ownerReference (apiVersion: kops.k8s.io/v1alpha2, kind: Cluster, name: <kops-cluster>) to the CAPI Cluster
- Verify the kops Cluster exists in the same namespace and its name matches the ownerRef
- Check for API version changes across kops upgrades that break the strings.HasPrefix(ownerRef.APIVersion, "kops.k8s.io/") match
Example fix
# before: CAPI Cluster without kops ownerRef
metadata:
name: my-capi-cluster
ownerReferences: []
# after
metadata:
name: my-capi-cluster
ownerReferences:
- apiVersion: kops.k8s.io/v1alpha2
kind: Cluster
name: my-kops-cluster
uid: <uid>
controller: true Defensive patterns
Strategy: validation
Validate before calling
func hasKopsOwnerRef(capiCluster *unstructured.Unstructured) bool {
for _, ref := range capiCluster.GetOwnerReferences() {
if ref.Kind == "Cluster" && strings.HasPrefix(ref.APIVersion, "kops.k8s.io/") {
return ref.Name != ""
}
}
return false
}
// skip reconciliation until hasKopsOwnerRef(capiCluster) is true Type guard
func kopsOwnerClusterKey(capiCluster *unstructured.Unstructured) (types.NamespacedName, bool) {
for _, ref := range capiCluster.GetOwnerReferences() {
if ref.Kind == "Cluster" && strings.HasPrefix(ref.APIVersion, "kops.k8s.io/") && ref.Name != "" {
return types.NamespacedName{Namespace: capiCluster.GetNamespace(), Name: ref.Name}, true
}
}
return types.NamespacedName{}, false
} Prevention
- Never create cluster.x-k8s.io Cluster objects manually; let the kops CAPI controller create them with the ownerRef
- Preserve ownerReferences when exporting/importing or editing Cluster manifests
- After kops upgrades, verify the ownerRef APIVersion still matches kops.k8s.io/*
- Use admission policies to reject CAPI Clusters lacking a kops.k8s.io ownerRef
When it happens
Trigger: Reconcile -> getKopsClusterFromCAPICluster where the CAPI Cluster object has an empty ownerReferences list, or owner references exist but none matches Kind=Cluster with APIVersion kops.k8s.io/* (e.g. owner is another kind, or a different API group).
Common situations: CAPI Cluster created manually instead of by the kops control-plane controller that normally sets the kops ownerRef; ownerRef stripped by copy/edit of the manifest; wrong owner kind or API version (e.g. kops.k8s.io vs another group) after a version migration.
Related errors
- building bootstrap data
- building cloud
- building kubernetes client
- building kubernetes scheme
- cannot get pod health for
AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05).
Data as JSON: /api/errors/485eb61fd338385b.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/controllers/clusterapi/utils.go:99
}
return capiCluster, nil
}
func getKopsClusterFromCAPICluster(ctx context.Context, kube client.Client, capiCluster *unstructured.Unstructured) (*kopsapi.Cluster, error) {
var clusterKey types.NamespacedName
for _, ownerRef := range capiCluster.GetOwnerReferences() {
if ownerRef.Kind == "Cluster" && strings.HasPrefix(ownerRef.APIVersion, "kops.k8s.io/") {
clusterKey = types.NamespacedName{
Namespace: capiCluster.GetNamespace(),
Name: ownerRef.Name,
}
}
}
if clusterKey.Name == "" {
return nil, fmt.Errorf("cluster ownerRef not set on CAPI cluster %v/%v", capiCluster.GetNamespace(), capiCluster.GetName())
}
cluster := &kopsapi.Cluster{}
if err := kube.Get(ctx, clusterKey, cluster); err != nil {
return nil, fmt.Errorf("error fetching cluster %v: %w", clusterKey, err)
}
return cluster, nil
}
func getKopsControlPlaneFromCAPICluster(ctx context.Context, kube client.Client, capiCluster *unstructured.Unstructured) (*v1beta1.KopsControlPlane, error) {
capiClusterKey := types.NamespacedName{
Namespace: capiCluster.GetNamespace(),
Name: capiCluster.GetName(),
}
name, _, _ := unstructured.NestedString(capiCluster.Object, "spec", "controlPlaneRef", "name")
if name == "" {
return nil, fmt.Errorf("controlPlaneRef.name not set for %v", capiClusterKey)View on GitHub (pinned to 4c8573c808)