CLI and Plugin Reference
Cisco Virtual Kubelet exposes two command-line surfaces:
kubectl-ciscovkplugin — an operator tool for read-only, ad-hoc IOS-XE commands and manager-accepted topology diagnostics. The current implementation providesexec,topology graph,version, and help.cisco-vkbinary — the backend. Itsmanagersubcommand starts the controller,runstarts one per-device Virtual Kubelet provider, and the internalcontroller-workersubcommand 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:
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:
Both print the same release provenance:
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:
controllerImageselects 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.pullPolicybecomes--controller-worker-image-pull-policyas well as the manager Pod's pull policy.vkImageselects only per-device VK Deployments created forCiscoDevice. It does not select a network-controller worker image.vkImage.pullPolicybecomes--vk-image-pull-policyfor those per-device Deployments.- The shared
imageis the fallback for both overrides. Its published default isghcr.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