Schedule application workloads
For application operators. Place device-hosted application Pods with the normal Kubernetes scheduler. This does not schedule an IOS-XE OS upgrade.
Before you schedule
Complete managed enrollment. Verify the device Node is
Ready, its protected labels are complete, and the app-hosting plane is read-write.
Choose a package and network configuration supported by the target platform.
For a first application, use Getting started; in managed
mode, replace direct nodeName placement with the scheduler rules below.
Workload scheduling
Use the default scheduler. Do not set schedulerName, and avoid direct
spec.nodeName assignments. The
devices-and-workload.yaml
example shows one fleet member plus drain-qualified soft site preference and
spread. It also includes a matching policy/v1
PodDisruptionBudget and the opt-in operations.cisco.vk/drain-safe=true
template label. The label alone grants no eviction authority. With the
default BlockIfRunning campaign policy, CVK does not evict Pods or consume
the PDB and fails closed while either Kubernetes or the device reports a live
workload.
Hard placement uses node affinity:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.cisco.vk/site
operator: In
values: [berlin-campus]
Use weighted preferences when one site or rack is desirable but a qualified fallback is acceptable. Missing preferred labels do not make a Pod unschedulable:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: topology.cisco.vk/site
operator: In
values: [berlin-campus]
- weight: 50
preference:
matchExpressions:
- key: topology.cisco.vk/rack
operator: In
values: [berlin-rack-7]
Availability spreading uses the standard topology-spread API:
topologySpreadConstraints:
- maxSkew: 1
minDomains: 2
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
nodeTaintsPolicy: Honor
labelSelector:
matchLabels:
app: edge-agent
Use DoNotSchedule for a hard failure-domain requirement and
ScheduleAnyway for a preference. Add a PodDisruptionBudget for replicated
applications. It affects CVK only when both administrator policy and an exact
campaign opt into the drain procedure.
Hard node affinity, selectors and spread remain valid with BlockIfRunning.
The v2026.11.1 preview Drain contract can also preserve these constraints in
an immutable placement digest, but must observe a currently feasible replacement
Node before protection or Eviction. Required Pod affinity and anti-affinity remain
unsupported. This feasibility check does not reserve capacity: the scheduler is
still authoritative, and the replacement must become Ready within the bounded
recovery window. Before opting in, qualify alternative capacity and actual
application portability. Soft preferences and ScheduleAnyway remain hints,
not placement guarantees. See the complete drain eligibility.
Important native scheduler behavior:
- incomplete Nodes do not receive the required projected labels and retain the
topology.cisco.vk/uninitialized=true:NoScheduleguard; - an active device mutation adds
cisco.vk/device-maintenance=gnoi:NoSchedule; both guards block new scheduler placements but do not evict an existing Pod; - broad tolerations or direct
nodeNamecan bypass a scheduler taint; and - changing labels does not move already-bound Pods.
The workload example deliberately contains no toleration for either CVK-owned
guard. Application manifests should not add one: tolerating a maintenance
guard makes scheduler placement possible while the device is fenced. Inspect
the effective state with kubectl get node <name> -o jsonpath='{.spec.taints}'
when diagnosing a Pending Pod.
The rollout leaf therefore repeats its workload check after it obtains the device fence. A drain campaign additionally rechecks for newly bound Pods and in-flight writes before promoting to gNOI mutation. These post-fence checks are required even when scheduling policy is correct.
Verify placement
Inspect the Pod's assigned Node and events, then compare that Node's protected labels and taints with the intended affinity/spread. A Pending Pod is the correct result when a hard constraint cannot be met. Changing a label does not relocate an already-bound Pod, and Pod Ready alone does not establish service reachability.
kubectl get pods -n WORKLOAD_NAMESPACE -o wide
kubectl describe pod POD_NAME -n WORKLOAD_NAMESPACE
kubectl get node NODE_NAME --show-labels
Replace the namespace and object placeholders with your application. Verify the service independently on its supported network path before declaring placement successful.
Next step
Use observability to follow the application. If a later OS campaign requires evacuation, separately review the drain eligibility and gates before changing its workload policy. The optional Kubernetes 1.37 Workload/PodGroup/TAS lane is experimental and is not required for this placement workflow.