Skip to content

CLI and Plugin Reference

Cisco Virtual Kubelet exposes two command-line surfaces:

  • kubectl-ciscovk plugin — an operator tool for read-only, ad-hoc IOS-XE commands and manager-accepted topology diagnostics. The current implementation provides exec, topology graph, version, and help.
  • cisco-vk binary — the backend. Its manager subcommand starts the controller, run starts one per-device Virtual Kubelet provider, and the internal controller-worker subcommand starts one registered network- controller adapter. Helm and the manager normally own these invocations.

This reference targets v2026.11.1. The signed-archive example below pins that version; use it only after its assets appear on the Releases page. Otherwise select the published October v2026.10.0 baseline and its matching reference. November controller adapters and public-CA projection need the matching binary, CRDs and chart. Check cisco-vk <command> --help for the installed version.


kubectl-ciscovk plugin

Install with Krew

The plugin is named cisco-vk in the public Krew index, following Krew's hyphenated vendor-name guidance. Installation and upgrades are:

kubectl krew update
kubectl krew install cisco-vk

# Later releases:
kubectl krew upgrade cisco-vk

# Invoke the Krew-installed plugin:
kubectl cisco-vk version

The archive's executable remains kubectl-ciscovk, so existing manual/source installs can continue to use kubectl ciscovk. Krew exposes the conventional kubectl cisco-vk alias. Historical note: v2026.08.0 predates the signed plugin assets, and v2026.8.1 was the first plugin-bearing release. The project release workflow validates Linux and macOS on both amd64 and arm64; Windows is not advertised yet.

Install a signed release archive

Starting with v2026.8.1, each plugin-bearing GitHub release includes four deterministic archives, a signed checksum manifest, and per-archive signatures. The following example selects the local Linux/macOS architecture, verifies the signed checksum authority, and installs the plugin:

(
set -euo pipefail

# Set this to an asset-bearing release shown on the Releases page.
# November target: use only after its signed assets are published.
VERSION=v2026.11.1
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$OS" in
  darwin|linux) ;;
  *) echo "unsupported operating system" >&2; exit 1 ;;
esac
case "$(uname -m)" in
  x86_64|amd64) ARCH=amd64 ;;
  arm64|aarch64) ARCH=arm64 ;;
  *) echo "unsupported architecture" >&2; exit 1 ;;
esac
ASSET="kubectl-ciscovk_${VERSION}_${OS}_${ARCH}.tar.gz"
CHECKSUMS="kubectl-ciscovk_${VERSION}_checksums.txt"
BASE="https://github.com/cisco-open/cisco-virtual-kubelet/releases/download/${VERSION}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
cd "$WORK_DIR"

curl -fLO "${BASE}/${ASSET}"
curl -fLO "${BASE}/${CHECKSUMS}"
curl -fLO "${BASE}/${CHECKSUMS}.sigstore.json"
cosign verify-blob \
  --bundle "${CHECKSUMS}.sigstore.json" \
  --certificate-identity \
    "https://github.com/cisco-open/cisco-virtual-kubelet/.github/workflows/release.yml@refs/tags/${VERSION}" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "${CHECKSUMS}"

expected="$(awk -v name="$ASSET" '$2 == name {print $1}' "$CHECKSUMS")"
if command -v sha256sum >/dev/null 2>&1; then
  actual="$(sha256sum "$ASSET" | awk '{print $1}')"
else
  actual="$(shasum -a 256 "$ASSET" | awk '{print $1}')"
fi
if [ -z "$expected" ] || [ "$actual" != "$expected" ]; then
  echo "missing or mismatched release checksum" >&2
  exit 1
fi

tar -xzf "$ASSET"
mkdir -p "$HOME/.local/bin"
install -m 0755 kubectl-ciscovk "$HOME/.local/bin/kubectl-ciscovk"
ln -sfn kubectl-ciscovk "$HOME/.local/bin/kubectl-cisco_vk"
)

Ensure $HOME/.local/bin is on your PATH, then verify the plugin:

kubectl ciscovk version
kubectl cisco-vk version
kubectl ciscovk --help

Build from source

For development or a custom build, build the plugin from the current source tree and place it on your PATH using the standard kubectl-<name> filename:

git clone https://github.com/cisco-open/cisco-virtual-kubelet.git
cd cisco-virtual-kubelet

make build-plugin
mkdir -p "$HOME/.local/bin"
install -m 0755 bin/kubectl-ciscovk "$HOME/.local/bin/kubectl-ciscovk"
ln -sfn kubectl-ciscovk "$HOME/.local/bin/kubectl-cisco_vk"

make build-plugin injects the nearest Git tag, commit, and build time into the version output. Direct go build remains supported and reports a devel build instead of impersonating a release. The symlink additionally supports the same kubectl cisco-vk spelling used by Krew; the existing kubectl ciscovk spelling remains available.

The plugin invokes the kubectl binary from PATH. By default it inherits KUBECONFIG and the active context. Pass --context and/or --kubeconfig to select a target explicitly without changing the user's active context; the plugin forwards both options to pod discovery and port-forwarding.

Execute a read-only IOS-XE command

exec locates the per-device VK pod, opens a local kubectl port-forward to its admin endpoint, submits the command, prints the result, and closes the tunnel:

kubectl ciscovk exec cat9k-smoke -n cvk-system -- show version
kubectl ciscovk exec cat9k-smoke -n cvk-system \
  --context lab --kubeconfig "$HOME/.kube/lab.conf" \
  -- "show running-config | section interface"

The server applies its read-only command allowlist, and the plugin separately rejects known destructive command prefixes. exec is currently IOS-XE-only. It does not provide show, nodes, devices, logs, or config subcommands.

Flags for exec:

Flag Default Description
-n, --namespace current namespace Namespace containing the per-device VK pod.
--allow-secrets false Reserved for a future SAR-gated path; currently a no-op on the server.
--truncate-bytes 65536 Maximum output bytes per command; 0 disables truncation.
--port random free port Local port used for kubectl port-forward.
--timeout 30s Overall command timeout.
--context active context Kubeconfig context forwarded to kubectl.
--kubeconfig KUBECONFIG/kubectl default Kubeconfig path forwarded to kubectl.
--kubectl kubectl from PATH Alternate path to the kubectl executable.

The active Kubernetes identity needs permission to get/list pods in the selected namespace and create pods/portforward connections. exec requires the per-device deployment mode and its localhost diagnostic admin endpoint; it is not available in aggregator mode.

Inspect the accepted topology graph

topology graph builds a bounded, deterministic diagnostic from CiscoDevice.status.healthObservation.acceptedNetwork. It deliberately does not consume the worker-owned raw network sample. The manager must first bind, validate, and accept that evidence. The command is read-only: it does not edit labels, risk groups, rollout policy, approvals, or disruption authority.

# Read all namespaces so duplicate physical identities cannot be hidden.
kubectl ciscovk topology graph --max-age 5m

# Inspect one namespace as structured evidence.
kubectl ciscovk topology graph -n cvk-live -o json

# Emit the graph, then return nonzero when any required evidence is incomplete.
kubectl ciscovk topology graph -n cvk-live \
  --max-age 5m --require-complete

# Resolve verified protocol-local names and compare declared links from the
# existing admission-protected topology-policy ConfigMap.
kubectl ciscovk topology graph -n cvk-live \
  -l topology.cisco.vk/managed=true \
  --policy-configmap cisco-vk-system/cisco-vk-topology-policy \
  --max-age 5m -o json --require-complete

The graph keys managed devices by the manager-bound physical identity. A device without one is retained as unbound:<namespace>/<name> and makes the graph incomplete instead of disappearing. Missing or stale accepted evidence, duplicate physical identities and conflicting adjacencies are errors. Unknown CDP/OSPF peers, absent reverse-port identity and asymmetric observations remain visible as warnings. A complete graph means only that the supplied accepted observations passed these structural checks; it does not prove end-to-end path health, redundancy, traffic headroom, or permission to disrupt a device.

CDP host names are not authenticated chassis identities. If an accepted peer name cannot be resolved to a supplied manager-bound physical identity, the command reports UnknownPeer; it does not guess. An administrator can enable topology.graph in the Helm values to place bounded peerMappings and declaredLinks in the graph.json key of the existing topology-policy ConfigMap. That ConfigMap is protected by the native topology admission contract. The plugin requires its explicit namespace/name, rejects unknown JSON fields, ambiguous mappings, invalid physical identities and unqualified link declarations, and records the ConfigMap UID, resourceVersion and content hash in its output. A mapping marked external: true can name a verified non-CVK endpoint without pretending it has a manager-owned observation.

graph.json is deliberately separate from policy.json: graph changes do not alter rollout policy/approval hashes, grant mutation authority, or rewrite discovered topology. Missing declared links are errors; unexpected observed links and unused mappings remain visible diagnostics. evidenceHash covers only the normalized topology content. JSON output also carries bounded accepted sample metadata and a separate provenanceHash over device UID, worker Pod UID, producer revision, sequence, collection interval, physical identity, and device-identity hash plus the graph-policy proof when supplied. Neither hash is an approval token.

Flags for topology graph:

Flag Default Description
-n, --namespace all namespaces Limit collection to one namespace. The default is safer for detecting cross-namespace duplicate physical identities.
-l, --selector none Limit CiscoDevices with a bounded Kubernetes label selector; use an admission-protected managed-fleet selector rather than hiding individual failures by name.
--max-age 5m Maximum age of the authenticated collection start time.
-o, --output table Output format: table or json.
--require-complete false Print the result and return nonzero if the graph is incomplete.
--policy-configmap none Read strictly validated graph.json from an admission-protected topology-policy ConfigMap, as namespace/name.
--context active context Kubeconfig context forwarded to kubectl.
--kubeconfig KUBECONFIG/kubectl default Kubeconfig path forwarded to kubectl.
--kubectl kubectl from PATH Alternate path to the kubectl executable.

The Kubernetes identity needs permission to list CiscoDevice resources in the selected scope and, when --policy-configmap is used, get that exact ConfigMap. No device credential or direct device session is used. The chart creates an unbound <release>-topology-graph-viewer ClusterRole for these reads. Bind it with RoleBindings in the selected device and policy namespaces, or use a deliberately reviewed ClusterRoleBinding for a fleet-wide operator. It has no watch, status, topology-authoring, rollout, Secret, Pod, or device-session permission.

DeviceOperation CR — auditable asynchronous path

For automation that needs a durable Kubernetes object and status, create a DeviceOperation directly:

apiVersion: ops.cisco.vk/v1alpha1
kind: DeviceOperation
metadata:
  name: show-version-cat9300-1
  namespace: default
spec:
  deviceRef:
    name: cat9300-1
  operation:
    kind: ShowCommand
    commands:
      - show version
  ttlSecondsAfterFinished: 300
kubectl apply -f show-version.yaml
kubectl get deviceoperation show-version-cat9300-1 -w

# Once phase is Succeeded:
kubectl get deviceoperation show-version-cat9300-1 \
  -o jsonpath='{.status.outputs[0].output}{"\n"}'

ConfigDiff, packet-capture reads, and read-only gNOI operation kinds use the same CRD with operation-specific inputs. See the Operations Runbook for their current schema, status, RBAC, and maturity.


cisco-vk version

Use either form to identify the backend binary before an install, upgrade, or support request:

cisco-vk version
cisco-vk --version

Both print the same release provenance:

cisco-vk v2026.11.1 (commit=<full-git-commit>, built=<RFC3339-time>)

A direct development build reports devel and may report unknown metadata.

cisco-vk run

cisco-vk run starts the Virtual Kubelet provider for a single device. The manager creates one pod per CiscoDevice CR running this command; direct invocation is only needed for local development. Generic runtime flags support both XE and NXOS, selected by device.driver; IOS-XE-only settings are called out below. Controller-managed pods pass the CiscoDevice name explicitly as --nodename.

Flags

Flag Env Default Description
-c, --config — /etc/virtual-kubelet/config.yaml Path to device configuration YAML.
--nodename CISCO_VK_NODE_NAME, then VKUBELET_NODE_NAME device.nodeName, then cisco-vk-<device-address> Kubernetes virtual node name. Resolution is flag, these environment variables in order, device.nodeName, then the address-derived name; falls back to cisco-virtual-kubelet without an address.
--kubeconfig KUBECONFIG in-cluster Path to kubeconfig. Omit inside the cluster.
--log-level LOG_LEVEL info Resolution is flag, environment, device.logLevel, then info; accepts debug, info, warn/warning, or error.
--tls-cert-file — /etc/virtual-kubelet/tls/tls.crt Kubelet HTTPS certificate; when both configured files are absent, a self-signed pair is generated under /var/lib/virtual-kubelet/tls/.
--tls-key-file — /etc/virtual-kubelet/tls/tls.key Kubelet HTTPS private key; exactly one certificate/key file being present is an error.
--enable-write-class-gnoi CISCO_VK_ENABLE_WRITE_CLASS_GNOI false IOS-XE only: enable IOSXEOperationalAction reconciliation, including explicit certificate provisioning.
--enable-iosxesoftwareupgrade CISCO_VK_ENABLE_IOSXE_SOFTWARE_UPGRADE false IOS-XE only: enable IOSXESoftwareUpgrade reconciliation.
--worker-mode CISCO_VK_WORKER_MODE combined Runtime plane: combined, app-hosting, or network-management. The manager sets this for managed workers.
--worker-access CISCO_VK_WORKER_ACCESS readWrite Worker access: readOnly or readWrite. The manager sets this from its access policy.

Managed workers also receive --device-tls-ca-projection from the controller. It binds the public-CA projection contract and is not an operator configuration option. Use spec.tls.caSecretRef with a matching chart, manager and worker version instead.

Additional environment variables

Variable Purpose
VK_DEVICE_PASSWORD Non-empty value overrides the config-file password. With credentialSecretRef, the controller injects it through secretKeyRef and keeps it out of the ConfigMap.
CISCO_VK_GNOI_INSECURE IOS-XE only: 1 or true forces the legacy plaintext gNOI listener for non-opt-in configurations and does not change REST/config TLS. It is rejected with explicit gnoi.transportSecurity: tls; legacy Basic metadata may expose credentials.
CISCO_VK_GNOI_PORT IOS-XE only: override the gNOI port. It overrides spec.gnoi.port but does not by itself select TLS. Legacy inference uses 50052 for plaintext, 9339 for TLS, or an existing nonstandard device port. Invalid values fail gNOI setup instead of falling back.
CISCO_VK_GNOI_DISABLED IOS-XE only: 1 or true skips gNOI client construction entirely.
CISCO_VK_UPGRADE_MAX_IMAGE_BYTES Positive base-10 byte limit for one URL or ConfigMap software-upgrade image. Defaults to 8 GiB. Helm value: gnoi.softwareUpgrade.maxImageBytes. Plan pod ephemeral storage for one cached image plus normal overhead before raising it.
CISCO_VK_UPGRADE_ALLOW_INSECURE_SSH Custom/local deployments only: true, together with ?insecureSkipHostKey=true (or ?insecure=true) on that SCP/SFTP URL, enables the lab-only host-key bypass. The stock Helm chart intentionally does not expose it.
CONFIG_LEASE_NAMESPACE Namespace that stores config-arbitration and shared device-mutation Leases; defaults to the worker namespace.
CONFIG_YANG_VALIDATION IOS-XE config-driver YANG validation: disabled (default), warn, or strict. NX-OS always applies its structural/DME validation.

IOS-XE example one-liners for development or a single ad-hoc run are shown below. Outside a cluster, also pass --kubeconfig or set a valid KUBECONFIG. Prefer device.gnoi.transportSecurity: tls and device.gnoi.port: 9339 in a local config (or spec.gnoi in a CiscoDevice) for IOS-XE 17.18.x. Explicit TLS must use the system root pool, verified shared trust, dedicated device.gnoi.tls.caFile, or IOS-XE provisioning-derived trust. Kubernetes uses spec.gnoi.tls.secretRef.name instead of host paths. The environment variables are retained for deployment-level compatibility. A valid port override takes precedence; the insecure override cannot downgrade explicit transportSecurity: tls.

# Force the legacy insecure gNXI listener (lab/dev only):
CISCO_VK_GNOI_INSECURE=1 cisco-vk run --config /etc/cisco-vk/cat9300-1.yaml

# Enable the software upgrade reconciler:
CISCO_VK_ENABLE_IOSXE_SOFTWARE_UPGRADE=1 cisco-vk run --config /etc/cisco-vk/cat9300-1.yaml

# Disable gNOI client entirely (device has no gNxI listener):
CISCO_VK_GNOI_DISABLED=1 cisco-vk run --config /etc/cisco-vk/cat9300-1.yaml

cisco-vk manager

Starts the Kubernetes controller manager. It watches CiscoDevice resources and creates their per-device VK ConfigMaps and Deployments, watches NetworkController resources and creates an isolated, namespace-scoped worker for each registered adapter type, and runs the IOSXEConfigBundle fan-out controller. The manager performs no product-controller API calls. Unknown or unregistered types produce no worker Deployment. If a separately supplied worker image has a different descriptor, its process exits before adapter setup or an external controller request. Worker Pod arguments also bind the NetworkController generation used to build its credential and CA projections; a stale Pod rejects a live generation mismatch before adapter setup.

Alpha controller extension

The generic API and worker boundary are Alpha. Two adapters are registered: sdwan (Catalyst SD-WAN Manager, fixture-qualified, applies only in apply mode, no remote prune or delete) and nexus-dashboard (health, inventory and opt-in CiscoDevice adoption). No Catalyst Center, APIC, Meraki, or other controller is contacted, and a NetworkController whose type has no adapter starts no worker.

Flags

Flag Env Default Description
--metrics-bind-address — :8080 Prometheus metrics endpoint.
--health-probe-bind-address — :8081 /healthz and /readyz probes.
--leader-elect — false Enable leader election for HA deployments.
--vk-image — ghcr.io/cisco/virtual-kubelet-cisco:latest Container image for per-device VK pods.
--vk-image-pull-policy — image-tag default Per-device VK image pull policy: Always, IfNotPresent, or Never; the default image ending in :latest resolves to Always.
--controller-worker-image — ghcr.io/cisco/virtual-kubelet-cisco:latest Adapter-bearing image for isolated NetworkController workers. Its registered descriptor must match the manager's descriptor digest.
--controller-worker-image-pull-policy — IfNotPresent Worker image pull policy: Always, IfNotPresent, or Never.
--vk-service-account — cisco-virtual-kubelet Service account injected into VK Deployments.
--enable-config-aggregator — false Run IOSXEConfig reconciliation in-process rather than in each VK pod (experimental).
--enable-managed-topology — false Enable manager-owned Node identity/topology. Requires native admission policies, --leader-elect, and the administrator policy reference below.
--topology-policy-namespace — empty Namespace of the administrator topology policy ConfigMap; required with managed topology.
--topology-policy-name — empty Name of the administrator topology policy ConfigMap; required with managed topology.
--app-hosting-service-account — cisco-vk-app-hosting Namespace-shared ServiceAccount for managed app-hosting workers.
--app-hosting-access-mode — readWrite Managed app-hosting access: disabled, readOnly, or readWrite.
--network-management-service-account — cisco-vk-network-management Namespace-shared ServiceAccount for managed network-management workers.
--network-management-access-mode — readOnly Managed network-management access: disabled, readOnly, or readWrite.
--log-level LOG_LEVEL info debug, info, warn, or error.
--controller-info-log-rate-limit — 100 Max info log lines/sec from the reconciler.

The image values above are binary direct-invocation fallbacks. The Helm chart passes explicit values with separate meanings:

  • controllerImage selects both the manager Deployment and the isolated network-controller worker image. This guarantees the worker contains the same adapter registrations as the manager; the descriptor digest provides a second fail-closed check.
  • controllerImage.pullPolicy becomes --controller-worker-image-pull-policy as well as the manager Pod's pull policy.
  • vkImage selects only per-device VK Deployments created for CiscoDevice. It does not select a network-controller worker image.
  • vkImage.pullPolicy becomes --vk-image-pull-policy for those per-device Deployments.
  • The shared image is the fallback for both overrides. Its published default is ghcr.io/cisco-open/cisco-virtual-kubelet:<chart-appVersion>.

When starting cisco-vk manager outside Helm, explicitly set --vk-image, --vk-image-pull-policy, --controller-worker-image, and --controller-worker-image-pull-policy for the runtime images and pull behavior you intend. The manager-generated network-controller worker does not currently propagate imagePullSecrets; use a registry the cluster can pull anonymously for this Alpha scaffold. Private-registry worker support requires a future explicit credential-propagation contract.


Helm chart values

Install or upgrade with the bundled chart:

helm upgrade --install cvk ./charts/cisco-virtual-kubelet \
  --namespace cvk-system \
  --create-namespace \
  --set controller.leaderElect=true

The image tag defaults to the chart's appVersion. controllerImage always applies to both the manager and its network-controller workers; vkImage applies only to per-device VK pods. Use the overrides when those runtime families must run different images.

Key values:

image:
  repository: ghcr.io/cisco-open/cisco-virtual-kubelet
  tag: "" # empty selects the chart appVersion
  pullPolicy: IfNotPresent

controller:
  leaderElect: false
  metricsBindAddress: ":8080"
  healthProbeBindAddress: ":8081"

aggregator:
  enabled: false

gnoi:
  insecure: false
  port: ""
  disabled: false
  enableSoftwareUpgrade: false
  enableWriteClass: false