Getting Started
This guide walks you through deploying Cisco Virtual Kubelet against a Kubernetes cluster and running your first pod on a Cisco IOS-XE device.
You'll install the Helm chart, which deploys a Kubernetes controller. The controller watches CiscoDevice custom resources and stands up a Virtual Kubelet pod for each one — this is the supported way to run Cisco Virtual Kubelet.
NX-OS (Beta)
This walkthrough uses an IOS-XE device, the production-focused path. Cisco Nexus (NX-OS) switches are also supported in Beta — the install and pod workflow is the same, with platform-specific device settings, credentials, node name and package path. See the substitutions in Deploying to NX-OS below and the NX-OS Family Reference.
Prerequisites
| Requirement | Notes |
|---|---|
| Kubernetes cluster | v1.28+ recommended |
| Helm 3.21+ or 4.2+ | For installing the chart |
| Docker (or compatible) | Optional; only needed to build a custom VK image |
| Container registry | Optional for custom images; the published image is on GHCR |
| Cisco IOS-XE device | Catalyst 8000V (17.15.4c+), Catalyst 9000 (17.18.2+), IR1100 Series (17.12.4+), or IE3500 Series (17.18.2+) |
kubectl |
Configured to talk to your cluster |
On the device you need:
ioxconfigured — Cisco's on-device container runtime.restconfenabled on the HTTPS listener.- App-hosting support (standard on Catalyst 8000V and 9000).
- A signed container tar on device flash (e.g.
flash:/hello-app.iosxe.taron Cat8000V/Cat9000/IE3500, orbootflash:/hello-app.iosxe.taron IR1100) unless you explicitly allow unsigned packages.
See the platform-specific install guide for the exact IOS-XE CLI snippets: Catalyst 8000V, Catalyst 9000, IR1100 Series, or IE3500 Series.
1. Install the controller
The controller watches CiscoDevice custom resources and creates a Virtual Kubelet deployment for each one.
The release chart needs no source checkout, image build, or private registry.
These commands target November v2026.11.1; run them only after that version
and its artifacts appear on the
Releases page.
If it is not listed, use the published October 2026.10.0 chart with its
matching documentation. Do not use the rejected, unpublished v2026.11.0
artifacts. The chart selects its matching signed image automatically.
helm install cvk oci://ghcr.io/cisco-open/charts/cisco-virtual-kubelet \
--version 2026.11.1 \
--namespace cvk-system --create-namespace
This pulls the signed ghcr.io/cisco-open/cisco-virtual-kubelet image by default. The chart --version matches the release's SemVer-compatible CalVer without the leading v (for example, v2026.11.1 → 2026.11.1); see Releases for the current one.
The chart installs:
- All shipped
cisco.vk,config.cisco.vk, andops.cisco.vkCRDs - The controller
Deployment(single replica by default; leader election is opt-in) - RBAC for the manager and per-device VK pods
Both the controller pod and each VK pod it spawns use this image. To use different images for the two, set controllerImage.* and vkImage.* instead.
The sdwan and nexus-dashboard adapters and managed public-CA Secret
projection are introduced in v2026.11.1. October installations need the
full upgrade procedure before using them;
applying a November example alone does not update the CRDs or running image.
For local changes, see custom builds.
Verify:
If the controller pod is not Running, see Troubleshooting → CiscoDevice stuck in Provisioning.
2. Create a credential Secret
Device credentials are injected into the VK pod via a Kubernetes Secret — the controller never stores them in the ConfigMap. The Secret must be in the same namespace as the CiscoDevice and have a key named exactly password.
Save this as secret.yaml, replacing the password placeholder:
apiVersion: v1
kind: Secret
metadata:
name: cat9000-1-creds
namespace: default
type: Opaque
stringData:
password: <device-password>
Need multiple devices with different credentials? See Security → Managing credentials across multiple devices.
3. Create a CiscoDevice
The CiscoDevice CR describes how to reach the device and how containers should be networked on it.
Save this as ciscodevice.yaml, adapting the address, username and networking
to your device:
apiVersion: cisco.vk/v1alpha1
kind: CiscoDevice
metadata:
name: cat9000-1
namespace: default
spec:
driver: XE
address: "192.168.1.100"
port: 443
username: admin
credentialSecretRef:
name: cat9000-1-creds # matches the Secret from step 2
tls:
enabled: true
insecureSkipVerify: false
# allowUnsignedApps: true # uncomment for unsigned packages (own builds /
# test images). CVK disables device signing via
# RESTCONF and relaxes the reconciler guard.
xe:
networking:
interface:
type: AppGigabitEthernet
appGigabitEthernet:
mode: trunk
vlanIf:
dhcp: true
vlan: 200
guestInterface: 0
This Catalyst 9000 example uses the app-hosting VLAN and DHCP setup from the
platform guide. For Catalyst 8000V, use its
VirtualPortGroup configuration instead.
The device HTTPS certificate must be trusted by the worker's system CA store
and match spec.address (an IP SAN when using an IP address). For a private CA,
see the managed Secret alternative below; October deployments need a custom
verified CA mount as described in Shared TLS.
This quick-start configuration leaves gNOI in backward-compatible auto mode.
Before invoking a gNOI operation, configure
explicit verified gNOI TLS.
Private CA support introduced in v2026.11.1
spec.tls.caSecretRef requires the November CRD, admission policies, manager
and workers. Upgrade those components together when moving from
v2026.10.0; see the projection and rollback
requirements.
Create a Secret from an independently trusted public CA bundle in the same
namespace as the CiscoDevice:
kubectl -n default create secret generic switch-https-public-ca \
--from-file=ca.crt=/path/to/trusted-device-ca.crt
Replace the device's tls block with:
The controller projects only validated public CA certificates. This does not install a certificate on the device or configure dedicated gNOI trust.
Within a few seconds you should see:
$ kubectl get ciscodevice
NAME DRIVER ADDRESS PHASE AGE
cat9000-1 XE 192.168.1.100 Ready 15s
$ kubectl get nodes
NAME STATUS ROLES AGE
cat9000-1 Ready agent 15s
If PHASE stays on Provisioning or shows Error, see Troubleshooting.
Deploying to NX-OS (Beta)
For a Cisco Nexus (NX-OS) switch, use the same controller installation in step 1.
In step 2, name the credential Secret nexus9300v-01-creds in namespace
default and supply that switch's password. Set driver: NXOS and use an
nxos: block instead of xe:. App-hosting is driven over NX-API CLI; the only
supported host-side attachment today is the Management interface.
apiVersion: cisco.vk/v1alpha1
kind: CiscoDevice
metadata:
name: nexus9300v-01
namespace: default
spec:
driver: NXOS
address: "192.168.1.120"
port: 443
username: admin
credentialSecretRef:
name: nexus9300v-01-creds
tls:
enabled: true
insecureSkipVerify: false # requires a certificate trusted by the worker
nxos:
networking:
interface:
type: Management
management:
guestInterface: 0
In the Pod manifest from step 4, replace spec.nodeName with nexus9300v-01
and spec.containers[0].image with the path to an NX-OS-compatible package, for
example bootflash:/hello-app.nxos.tar. Stage that package on the switch first.
The IOS-XE example's flash: prefix is not accepted: NX-OS accepts bootflash:
paths or supported HTTP(S) package URLs. Keep the
pod's namespace consistent with your credential and device examples (default).
Declarative NX-OS device configuration (VLANs, interfaces, features, and so on)
is managed separately through the NXOSConfig CRD over NX-API REST/DME — see
the NX-OS Family Reference for the supported
families and an example. NX-OS is a Beta runtime slice; the
Production Readiness page tracks its current scope.
4. Deploy a pod
Schedule a pod directly onto the virtual node.
apiVersion: v1
kind: Pod
metadata:
name: hello-app
spec:
nodeName: cat9000-1
tolerations:
- key: virtual-kubelet.io/provider
operator: Exists
containers:
- name: hello
image: flash:/hello-app.iosxe.tar # path on device flash (use bootflash: on IR1100)
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
The image reference is a path on the device's flash storage, not a container registry reference — the container tar must already be on the device, or be served over HTTP(S). To serve images from a registry, see Image Distribution.
If the pod stays in Pending for more than a minute or ends up Failed, see Troubleshooting → Pod stuck in Pending or PackagePolicyInvalid false positives.
5. Verify on the device
You should see the container transition through DEPLOYED → ACTIVATED → RUNNING. Typical output of show app-hosting list:
Upgrading an existing release
Helm installs CRDs during the first install but does not upgrade them. Before
upgrading to 2026.11.1, confirm publication as described above, pull that
chart, and back up the live definitions and custom resources. November refreshes
18 existing CRD manifests, with substantive schema changes in four and no new
kinds. Preserve reviewed Helm values, inspect the server-side diff, and apply the reviewed schemas with an
explicit Helm field-ownership handoff:
helm pull oci://ghcr.io/cisco-open/charts/cisco-virtual-kubelet \
--version 2026.11.1 --untar
kubectl get customresourcedefinitions.apiextensions.k8s.io -o yaml \
> cvk-crds-before-upgrade.yaml
kubectl diff --server-side --force-conflicts \
--field-manager=cvk-crd-upgrade -f cisco-virtual-kubelet/crds/
kubectl apply --server-side --force-conflicts \
--field-manager=cvk-crd-upgrade -f cisco-virtual-kubelet/crds/
kubectl wait --for=condition=Established --timeout=60s \
-f cisco-virtual-kubelet/crds/
helm upgrade cvk oci://ghcr.io/cisco-open/charts/cisco-virtual-kubelet \
--version 2026.11.1 \
--namespace cvk-system
kubectl rollout status deployment/cvk-cisco-virtual-kubelet-controller \
--namespace cvk-system
kubectl diff returns status 1 for expected differences. The force flag is
limited to the exact CVK CRD files passed with -f; review the diff and keep
the backup before continuing.
The September release adds NetworkController and
NetworkControllerConfig. If either CRD is absent, the manager keeps existing
device reconcilers running but disables the Alpha controller scaffold for that
process. Apply both CRDs and restart the manager. See
Upgrading CRDs for verification and rollback
guidance.
Optional: install the kubectl plugin with Krew
The client-side kubectl-ciscovk plugin is optional; the controller and normal
Kubernetes workflows do not depend on it. It provides read-only, ad-hoc IOS-XE
commands and manager-accepted topology graph diagnostics. Graph output is
observational and never grants rollout authority; optional declared mappings
and links live in an admission-protected ConfigMap key separate from rollout
policy hashes. It is available in the
public Krew index; install and upgrade it with:
kubectl krew update
kubectl krew install cisco-vk
kubectl cisco-vk version
# Later releases
kubectl krew upgrade cisco-vk
Historical note: v2026.08.0 predates the plugin assets, and v2026.8.1 was
the first plugin-bearing release. The
CLI & Plugin Reference documents Krew, signed-archive, and
source-build paths plus the plugin's current IOS-XE-only scope.
Optional: build a custom image
Use a custom build only when you need code changes or are evaluating a development feature. For a released baseline, select an explicit tag from the published Releases page. Do not infer publication from the greatest Git tag: draft and rejected candidates can also have tags.
git clone https://github.com/cisco-open/cisco-virtual-kubelet.git
cd cisco-virtual-kubelet
VERSION=v2026.11.1 # use only after publication; otherwise select a published baseline
git checkout "$VERSION"
IMAGE_REPOSITORY=ghcr.io/your-org/cisco-vk # replace with your registry path
docker build -t "$IMAGE_REPOSITORY:$VERSION" .
docker push "$IMAGE_REPOSITORY:$VERSION"
helm install cvk ./charts/cisco-virtual-kubelet \
--namespace cvk-system --create-namespace \
--set image.repository="$IMAGE_REPOSITORY" \
--set image.tag="$VERSION"
For a development build, check out the intended commit and use a unique image tag for it; install the chart and CRDs from that same checkout. This is an evaluation deployment with its own provenance, not a release artifact. For KIND or minikube, you can load the built image locally instead of pushing it:
kind load docker-image "$IMAGE_REPOSITORY:$VERSION" --name kind
# Or:
minikube image load "$IMAGE_REPOSITORY:$VERSION"
What's next
- Configuration — Every field with defaults and validation rules
- Observability — Enable OpenTelemetry topology traces and scrape Prometheus metrics
- Security — TLS, credential rotation, and RBAC
- Troubleshooting — If anything above did not work