Troubleshooting the VMO Pack
This page provides troubleshooting guidance for common scenarios you might encounter when using the VMO pack.
Scenario - Pack Upgrade Fails with an Invalid Ownership Metadata Error
This scenario affects a limited set of environments that upgrade the virtual-machine-orchestrator pack from a version
earlier than 4.10.x. It occurs only where the cluster already carries VMO RBAC objects that lack Helm ownership
metadata, which is why the same upgrade succeeds on most clusters.
The pack now corrects this state on its own. It ships an rbac-adopt pre-upgrade hook that stamps the required Helm
ownership metadata on those objects before Helm applies the chart, so an affected cluster completes the upgrade without
manual work. Use the procedure on this page only when the upgrade still fails with this error, which indicates that the
pack version you are upgrading to predates the hook.
When the cluster is affected and you trigger the upgrade from Palette, the upgrade does not complete. The following symptoms indicate this scenario.
-
The cluster page displays the Virtual Machine Orchestrator layer in an error state.
-
Events on the workload cluster report a
ReconcileErrorfromatop-controllerthat contains the textinvalid ownership metadata. -
Retrying the upgrade produces the same error on every reconcile cycle.
Use the following command to display the events, replacing <cluster-uid> with the unique identifier of your cluster.
kubectl get events --namespace cluster-<cluster-uid>
The following text is an example of the error.
Warning ReconcileError atop-controller
failed to reconcile charts ...: helm upgrade failed for release
virtual-machine-orche-virtual-machine-orche:
Error: UPGRADE FAILED: unable to continue with update:
Role "vmo-cdi-clone-source" in namespace "default" exists and cannot be
imported into the current release: invalid ownership metadata;
label validation error: key "app.kubernetes.io/managed-by" must equal "Helm":
current value is "vmo-manager";
annotation validation error: missing key "meta.helm.sh/release-name":
must be set to "virtual-machine-orche-virtual-machine-orche";
annotation validation error: missing key "meta.helm.sh/release-namespace":
must be set to "vm-dashboard"
The object named in the error can be a Role, RoleBinding, ClusterRole, or ClusterRoleBinding, but the error
always takes the form X exists and cannot be imported into the current release: invalid ownership metadata.
This occurs because the upgrade brings a set of Kubernetes role-based access control (RBAC) objects under Helm management. Helm 3 refuses to adopt an existing object unless that object already carries the following metadata, which identifies it as part of the target release.
| Field | Required Value |
|---|---|
Label app.kubernetes.io/managed-by | Helm |
Annotation meta.helm.sh/release-name | Your Helm release name |
Annotation meta.helm.sh/release-namespace | Your Helm release namespace |
If any of the RBAC objects that the pack templates already exist with different values, such as
app.kubernetes.io/managed-by: vmo-manager from an earlier installation, or with no metadata because someone created
them manually, the ownership check fails and the upgrade stops. Helm runs this check between its pre-upgrade hooks and
the manifest apply, which is why the rbac-adopt hook can stamp the metadata in time. On a pack version that does not
include that hook, correct the metadata yourself before you trigger the upgrade.
Prerequisites
-
kubectl version 1.28 or later, configured against the workload cluster and not the Palette management cluster. Refer to Access Cluster with CLI to obtain access.
-
Permission to create ServiceAccounts, ClusterRoles, ClusterRoleBindings, and Jobs in the
defaultnamespace if you use the Kubernetes Job, or permission to patch RBAC objects cluster-wide if you use the Bash script. -
An existing cluster with the VMO pack installed. This procedure does not install the pack. It prepares an existing installation for its next upgrade.
Find Your Release Values
The default values in the following procedures match a standard Palette installation. If your release uses different names, use the following steps to look them up.
-
Display your Helm release name and namespace.
helm list --all-namespaces | grep virtual-machine-orcheExample OutputNAME NAMESPACE REVISION CHART APP VERSION
virtual-machine-orche-virtual-machine-orche vm-dashboard 2 virtual-machine-orchestrator-1.0.16 ...The first column is your release name, and the second column is your release namespace.
-
Display the golden images namespace, replacing
<cluster-uid>with the unique identifier of your cluster. The default value isvmo-golden-images.kubectl get pack virtual-machine-orchestrator \
--namespace cluster-<cluster-uid> \
--output jsonpath='{.spec.packRef.values}' | grep --after-context=1 goldenImagesNamespace
Stamp the Helm Ownership Metadata
The following procedures add the missing Helm ownership metadata to the RBAC objects that the pack templates. Refer to RBAC Objects This Procedure Modifies for the complete list. Both procedures perform the same work, so choose the one that fits your environment.
-
Kubernetes Job - Runs inside the cluster. Use this option unless the
defaultnamespace enforcesrestrictedPodSecurity. -
Bash script - Runs from a workstation that has
kubectlaccess to the cluster. Use this option when PodSecurity blocks the Job.
Both are idempotent, so you can run either one as many times as you need. Objects that already carry the correct metadata remain unchanged, objects that exist without the metadata receive it while their existing labels and annotations are preserved, and objects that do not exist yet are skipped. Helm creates those during the upgrade. No rules, subjects, or role references are modified.
The RESOURCES list in the Job and the script contains the eight objects that Spectro Cloud tested this procedure
against. If your environment includes additional custom Roles, RoleBindings, ClusterRoles, or ClusterRoleBindings that
must come under Helm management, add them to the RESOURCES list. Each entry uses the format Kind:Namespace:Name.
Leave the namespace field empty for cluster-scoped objects, such as ClusterRole::vmo-manager.
- Kubernetes Job
- Bash Script
-
Save the following manifest as
stamp-vmo-rbac-job.yaml. If your release uses non-default names, edit the threeenvvalues to match the values you found in Find Your Release Values. Theimagevalue can be any image that provideskubectl, because the Job only runskubectl labelandkubectl annotate. Keep the default in airgapped environments, where that image is already present in your registry.---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vmo-rbac-adopt
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: vmo-rbac-adopt
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vmo-rbac-adopt
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: vmo-rbac-adopt
subjects:
- kind: ServiceAccount
name: vmo-rbac-adopt
namespace: default
---
apiVersion: batch/v1
kind: Job
metadata:
name: vmo-rbac-adopt
namespace: default
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 3600
template:
spec:
serviceAccountName: vmo-rbac-adopt
restartPolicy: OnFailure
containers:
- name: adopt
image: us-docker.pkg.dev/palette-images/third-party/spectro-kubectl:1.36.2-spectro-4.9.c
imagePullPolicy: IfNotPresent
env:
- name: RELEASE_NAME
value: "virtual-machine-orche-virtual-machine-orche"
- name: RELEASE_NS
value: "vm-dashboard"
- name: GOLDEN_IMAGES_NAMESPACE
value: "vmo-golden-images"
command:
- /bin/sh
- -c
- |
set -o nounset
kctl() {
n=0
while [ "$n" -lt 3 ]; do
if out=$(kubectl "$@"); then
printf '%s' "$out"
return 0
fi
n=$((n + 1))
sleep 2
done
return 1
}
RESOURCES=$(cat <<EOF
ClusterRole::vmo-manager
ClusterRole::vmo-manager-node-agent
ClusterRoleBinding::vmo-manager
ClusterRoleBinding::vmo-manager-node-agent
Role:default:vmo-cdi-clone-source
Role:$GOLDEN_IMAGES_NAMESPACE:vmo-cdi-clone-source
RoleBinding:default:vmo-cdi-clone-source
RoleBinding:$GOLDEN_IMAGES_NAMESPACE:vmo-cdi-clone-source
EOF
)
echo "Stamping Helm ownership for release $RELEASE_NS/$RELEASE_NAME"
echo
echo "$RESOURCES" | sed 's/^[[:space:]]*//' | while IFS=: read -r kind ns name; do
[ -z "$kind" ] && continue
if [ -n "$ns" ]; then ns_flag="--namespace $ns"; target="$kind $ns/$name"
else ns_flag=""; target="$kind $name"; fi
echo " -> $target"
kctl $ns_flag annotate "$kind" "$name" \
"meta.helm.sh/release-name=$RELEASE_NAME" \
"meta.helm.sh/release-namespace=$RELEASE_NS" \
--overwrite >/dev/null \
|| echo " annotate skipped (missing or unreachable)"
kctl $ns_flag label "$kind" "$name" \
"app.kubernetes.io/managed-by=Helm" \
--overwrite >/dev/null \
|| echo " label skipped (missing or unreachable)"
done
echo
echo "Done."
securityContext:
runAsNonRoot: false
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefaultwarningOn clusters where the
defaultnamespace enforcesrestrictedPodSecurity, which applies to Kubernetes version 1.25 and later, this Pod is blocked and the Job remains in aPendingstate. Either use the Bash script instead, or setrunAsNonRoottotrueand addrunAsUser: 65532to thesecurityContext. Thespectro-kubectlimage runskubectlas a non-root user. -
Apply the manifest and wait for the Job to complete.
kubectl apply --filename stamp-vmo-rbac-job.yaml
kubectl wait --namespace default --for=condition=complete --timeout=120s job/vmo-rbac-adopt -
Review the Job logs.
kubectl logs --namespace default job/vmo-rbac-adoptThe output contains one line per resource and ends with
Done.. Resources that do not exist yet displayannotate skipped (missing or unreachable)orlabel skipped (missing or unreachable). This outcome is expected. -
Remove the Job after the upgrade succeeds.
kubectl delete --filename stamp-vmo-rbac-job.yaml
-
Save the following script as
stamp-vmo-rbac.sh.#!/usr/bin/env bash
set -o nounset
RELEASE_NAME="${1:-virtual-machine-orche-virtual-machine-orche}"
RELEASE_NS="${2:-vm-dashboard}"
GOLDEN_IMAGES_NAMESPACE="${3:-vmo-golden-images}"
kctl() {
local n=0 out=""
while [ "$n" -lt 3 ]; do
if out=$(kubectl "$@"); then
printf '%s' "$out"
return 0
fi
n=$((n + 1))
sleep 2
done
return 1
}
RESOURCES=$(cat <<EOF
ClusterRole::vmo-manager
ClusterRole::vmo-manager-node-agent
ClusterRoleBinding::vmo-manager
ClusterRoleBinding::vmo-manager-node-agent
Role:default:vmo-cdi-clone-source
Role:${GOLDEN_IMAGES_NAMESPACE}:vmo-cdi-clone-source
RoleBinding:default:vmo-cdi-clone-source
RoleBinding:${GOLDEN_IMAGES_NAMESPACE}:vmo-cdi-clone-source
EOF
)
echo "Stamping Helm ownership for release ${RELEASE_NS}/${RELEASE_NAME}"
echo
echo "$RESOURCES" | while IFS=: read -r kind ns name; do
[ -z "$kind" ] && continue
if [ -n "$ns" ]; then ns_flag="--namespace $ns"; target="$kind $ns/$name"
else ns_flag=""; target="$kind $name"; fi
echo " -> $target"
kctl $ns_flag annotate "$kind" "$name" \
"meta.helm.sh/release-name=$RELEASE_NAME" \
"meta.helm.sh/release-namespace=$RELEASE_NS" \
--overwrite >/dev/null \
|| echo " annotate skipped (missing or unreachable)"
kctl $ns_flag label "$kind" "$name" \
"app.kubernetes.io/managed-by=Helm" \
--overwrite >/dev/null \
|| echo " label skipped (missing or unreachable)"
done
echo
echo "Done." -
Make the script executable.
chmod +x stamp-vmo-rbac.sh -
Run the script. If your release uses non-default names, pass them as positional arguments in the order release name, release namespace, and golden images namespace.
./stamp-vmo-rbac.sh./stamp-vmo-rbac.sh <release-name> <release-namespace> <golden-images-namespace>The output contains one line per resource and ends with
Done.. Resources that do not exist yet displayannotate skipped (missing or unreachable)orlabel skipped (missing or unreachable). This outcome is expected.
Validate the Metadata
-
Confirm that the object named in the original error now carries the Helm ownership metadata. The following example uses the
vmo-cdi-clone-sourceRole in thedefaultnamespace. If your error named a different object, substitute its kind, namespace, and name.kubectl get role vmo-cdi-clone-source --namespace default \
--output jsonpath='{"managed-by: "}{.metadata.labels.app\.kubernetes\.io/managed-by}{"\nrelease-name: "}{.metadata.annotations.meta\.helm\.sh/release-name}{"\nrelease-ns: "}{.metadata.annotations.meta\.helm\.sh/release-namespace}{"\n"}'Example Outputmanaged-by: Helm
release-name: virtual-machine-orche-virtual-machine-orche
release-ns: vm-dashboard -
If every resource reports
skipped (missing or unreachable), includingRole default/vmo-cdi-clone-source, then either the cluster carries no legacy state, in which case the upgrade already works and you can retry it, or yourkubectlsession cannot reach the API server. Check your kubeconfig file and network path.
Trigger the Upgrade
-
Trigger the VMO pack upgrade from Palette. Refer to Update a Cluster for guidance.
-
Confirm that the pack reconciles without the
invalid ownership metadataerror. The pack transitions through the reconcile cycle before it reports a healthy status.
Perform this procedure only once. After the cluster runs pack version 4.10.x or later, the RBAC objects carry Helm ownership metadata, and later upgrades are unaffected.
If the upgrade fails again with the same error, compare the release name and namespace you used against the output of
helm list --all-namespaces and run the procedure again with the corrected values. If the upgrade fails with the same
form of error but names an object that you have not added to the RESOURCES list, add that object to the list, run the
procedure again, and retry the upgrade. If the error persists, contact
Spectro Cloud support and provide the full ReconcileError message, the Job or
script output, and the output of helm list --all-namespaces filtered to your VMO release.
RBAC Objects This Procedure Modifies
The Job and the script apply the label app.kubernetes.io/managed-by: Helm and the annotations
meta.helm.sh/release-name and meta.helm.sh/release-namespace to the following eight objects.
| Kind | Namespace | Name |
|---|---|---|
| ClusterRole | Cluster-scoped | vmo-manager |
| ClusterRole | Cluster-scoped | vmo-manager-node-agent |
| ClusterRoleBinding | Cluster-scoped | vmo-manager |
| ClusterRoleBinding | Cluster-scoped | vmo-manager-node-agent |
| Role | default | vmo-cdi-clone-source |
| Role | Golden images namespace | vmo-cdi-clone-source |
| RoleBinding | default | vmo-cdi-clone-source |
| RoleBinding | Golden images namespace | vmo-cdi-clone-source |
Scenario - Custom StorageClass Not Appearing as VM-eligible
The VMO UI shows a StorageClass on the Home > Storage > Storage Classes page, but the VM WORKLOADS
column reports Not configured and the Allow for VMs checkbox on the edit dialog is not pre-checked. VMs cannot use
the class until an administrator enables it.
This condition applies when a StorageClass is created out-of-band from the VMO UI, such as through GitOps, Terraform, or
kubectl apply, without the label VMO reads to determine VM eligibility.
Debug Steps
VMO decides which StorageClass is VM-eligible by looking for the label
vmo-manager.spectrocloud.com/vm-workload: "true" on the class. A class that does not carry the label is not surfaced
to VM workloads, regardless of its Container Storage Interface (CSI) provisioner.
To confirm the state on a specific class, issue the following command. Replace <class-name> with the name of your
StorageClass.
kubectl get storageclass <class-name> --output jsonpath='{.metadata.labels}'
The output should include "vmo-manager.spectrocloud.com/vm-workload":"true". If the label is missing, VMO treats the
class as not VM-eligible.
Resolution
Add the label to the StorageClass. Issue the following command, replacing <class-name> with the name of your
StorageClass.
kubectl label storageclass <class-name> vmo-manager.spectrocloud.com/vm-workload=true
Refresh the Storage Classes page in the VMO UI. The VM WORKLOADS column reports Healthy and the Allow for
VMs checkbox is pre-checked on the edit dialog. Existing VMs and new VM workloads can now use the class.
To bake the label into a StorageClass manifest so it is present at creation time, include the label under
metadata.labels.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: <class-name>
labels:
vmo-manager.spectrocloud.com/vm-workload: "true"
provisioner: <your-csi-provisioner>
# ... rest of your StorageClass definition
The label is CSI-agnostic. It applies to Piraeus, Rook-Ceph, Portworx, and any other CSI provisioner. VMO reads the label only, not the CSI or the class name.