Network as Code — Configuration and Drift Detection
Beta
IOSXEConfig, NXOSConfig, and the Network as Code feature set are Beta
(v1alpha1). The family API, CRD schema, and drift semantics are
functional and integration-tested. Schema fields may change between
releases. Evaluate in non-production environments before broader rollout.
Cisco Virtual Kubelet extends Kubernetes with declarative device configuration
management. You express network intent as plain YAML (IOSXEConfig or
NXOSConfig CRs), and CVK reconciles the device to match — detecting drift,
applying changes, and verifying observed state after apply. IOS-XE has the
broadest family coverage and revision/apply-log history; NX-OS starts with a
per-device NX-API slice for system, feature, feature_set, vlan, and
interface_ethernet.
Controller-centric Network as Code intent uses the separate
NetworkController and NetworkControllerConfig extension boundary. See the
Network Controller Extension Guide for its
resolved-source, model-version, adapter, ownership, and worker contracts. The
shipped adapters are documented in Catalyst SD-WAN and
Nexus Dashboard.
Concepts
Families
A family is one independently managed slice of device configuration:
vlan, bgp, ospf, aaa, prefix_list, and so on for IOS-XE, and
system, feature, feature_set, vlan, and interface_ethernet for the
initial NX-OS slice. Each
family has its own writer that knows how to fetch, diff, and patch the relevant
device state.
Each IOSXEConfig or NXOSConfig lists the families it owns via
managedFamilies. CVK only touches those families; everything else on the
device is left untouched.
Intent hierarchy
CVK resolves configuration through a layered hierarchy before any YANG operation reaches the device:
IOSXEConfigDefaults (cluster-wide baseline)
↓
IOSXEDeviceGroupConfig (group of devices, e.g. all access switches)
↓
IOSXEInterfaceGroupConfig (shared interface templates, e.g. trunk ports)
↓
IOSXETemplate (Jinja2 / Go text-template fragments)
↓
IOSXEConfig (per-device final intent)
Lower layers override higher ones. The resolver merges all applicable layers and produces a single intent tree that is handed to family writers.
Drift detection
After each successful apply, CVK fetches the live state of each managed
family from the device and compares it to the resolved intent. Divergence
triggers a Drifted phase. The driftPolicy field controls what happens:
driftPolicy |
Behaviour |
|---|---|
report |
Log the diff and surface it in status; do not patch. |
revert |
Re-apply the intended family config immediately. |
pause |
Stop reconciliation until the CR is changed or the pause is removed. |
driftDetectInterval sets how frequently CVK polls for drift (default
5m).
Quick start
1. Write your first IOSXEConfig
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXEConfig
metadata:
name: access-vlans
namespace: default
spec:
deviceRef:
name: cat9300-floor1
managedFamilies:
- vlan
driftPolicy: report
writeStartup: true
source:
inline:
vlan:
vlans:
- id: 100
name: DATA
- id: 200
name: VOICE
- id: 300
name: MGMT
Watch the reconciliation progress:
$ kubectl get iosxeconfig -w
NAME DEVICE PHASE DRIFT AGE
access-vlans cat9300-floor1 InSync none 12s
2. Inspect the apply log
$ kubectl get iosxelog -l config.cisco.vk/config=access-vlans
NAME DEVICE FAMILIES RESULT AGE
access-vlans-20260530 cat9300-floor1 vlan Applied 12s
$ kubectl describe iosxeconfigapplylog access-vlans-20260530
...
Status:
Applied At: 2026-05-30T10:00:12Z
Duration: 0.8s
Op Count: 3
Result: Applied
Revision Ref:
Name: access-vlans-v1
3. Introduce drift manually and observe
SSH to the device and rename VLAN 100 from DATA to something-else.
Within the next driftDetectInterval cycle:
$ kubectl get iosxeconfig
NAME DEVICE PHASE DRIFT AGE
access-vlans cat9300-floor1 Drifted [vlan] 5m
$ kubectl describe iosxeconfig access-vlans
...
Status:
Family Statuses:
- Family: vlan
Phase: Drifted
Drift Summary: |
observed vlan 100 name: "something-else"
desired vlan 100 name: "DATA"
Phase: Drifted
Events:
Type Reason Age Message
---- ------ --- -------
Warning Drifted 30s family vlan: 1 leaf divergence (driftPolicy=report)
Switch to driftPolicy: revert to have CVK automatically remediate:
Multiple families
You can manage several families in a single CR:
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXEConfig
metadata:
name: cat9300-floor1-network
namespace: default
spec:
deviceRef:
name: cat9300-floor1
managedFamilies:
- vlan
- spanning_tree
- snmp_server
- logging
driftPolicy: revert
writeStartup: true
source:
inline:
vlan:
vlans:
- id: 100
name: DATA
- id: 200
name: VOICE
spanning_tree:
mode: rapid-pvst
extend:
system-id: true
snmp_server:
community:
- name: public
ro: true
location: "Floor 1 IDF"
contact: "noc@example.com"
logging:
buffered: 65536
trap: informational
Each family is reconciled independently. A failure in one family does not block others.
IOSXEConfigBundle — fan-out to many devices
When the same config should apply to a group of devices, use
IOSXEConfigBundle instead of creating one IOSXEConfig per device:
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXEConfigBundle
metadata:
name: access-layer-vlans
namespace: default
spec:
selector:
matchLabels:
tier: access
template:
spec:
managedFamilies:
- vlan
driftPolicy: revert
writeStartup: true
source:
inline:
vlan:
vlans:
- id: 100
name: DATA
- id: 200
name: VOICE
The IOSXEConfigBundle controller watches CiscoDevice objects matching
the selector and creates one IOSXEConfig per device. Changes to the
bundle template propagate to all child CRs automatically.
Label your devices:
kubectl label ciscodevice cat9300-floor1 tier=access
kubectl label ciscodevice cat9300-floor2 tier=access
$ kubectl get iosxeconfig -l config.cisco.vk/bundle=access-layer-vlans
NAME DEVICE PHASE DRIFT AGE
access-layer-vlans-cat9300-floor1 cat9300-floor1 InSync none 2m
access-layer-vlans-cat9300-floor2 cat9300-floor2 InSync none 2m
Rollback via IOSXEConfigRevision
Each successful apply snapshots the resolved intent as an
IOSXEConfigRevision. List them:
$ kubectl get iosxeconfigrevision -l config.cisco.vk/config=cat9300-floor1-network
NAME CONFIG AGE
cat9300-floor1-network-v1 cat9300-floor1-network 1h
cat9300-floor1-network-v2 cat9300-floor1-network 30m
cat9300-floor1-network-v3 cat9300-floor1-network 5m
Roll back by patching the IOSXEConfig to reference an older revision:
kubectl patch iosxeconfig cat9300-floor1-network \
--type=merge \
-p '{"spec":{"rollbackTo":"cat9300-floor1-network-v2"}}'
The reconciler re-applies the snapshotted intent and creates a new revision reflecting the rolled-back state.
Intent hierarchy in practice
IOSXEConfigDefaults
Cluster-wide defaults apply to every device unless overridden:
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXEConfigDefaults
metadata:
name: global
spec:
source:
inline:
snmp_server:
location: "Data Centre"
contact: "noc@example.com"
logging:
buffered: 32768
IOSXEDeviceGroupConfig
Per-group overrides for a set of devices:
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXEDeviceGroupConfig
metadata:
name: campus-access
spec:
selector:
matchLabels:
tier: access
source:
inline:
spanning_tree:
mode: rapid-pvst
IOSXETemplate (Jinja2)
Reusable templates with parameterisation:
apiVersion: config.cisco.vk/v1alpha1
kind: IOSXETemplate
metadata:
name: standard-vlans
spec:
template: |
vlan:
vlans:
{% for v in vlans %}
- id: {{ v.id }}
name: {{ v.name }}
{% endfor %}
parameters:
vlans:
- id: 100
name: DATA
- id: 200
name: VOICE
Reference the template from an IOSXEConfig:
spec:
source:
templateRef:
name: standard-vlans
parameters:
vlans:
- id: 100
name: USERS
- id: 300
name: IOT
Transactional apply
For families that support NETCONF confirmed-commit, set
spec.transactional: true. This wraps all family operations in a single
NETCONF transaction that is automatically rolled back if the timer expires
before a confirm is sent:
Use transactional mode when the risk of partial apply leaving the device in an inconsistent state is unacceptable.
Startup config persistence
writeStartup: true causes CVK to call the IOS-XE copy running-config
startup-config equivalent after every successful apply, ensuring the
intent survives a device reload. Leave it false (default) during
iterative development to keep changes transient.
NX-OS quick start
Use NXOSConfig for NX-OS devices. It uses the same common status and
drift-policy fields as IOSXEConfig, but the first runtime slice is scoped to
system, feature, feature_set, vlan, and interface_ethernet over
NX-API REST/DME.
NXOSConfig accepts a direct resolved family map. For compatibility, a native
CVK-authored source that omits modelSource may also use a full nxos:
NetAsCode envelope; CVK can resolve global, matching device_groups, the
selected devices entry, model templates, variables, and
interface_groups. That compatibility resolver is not the strict import path.
When modelSource.resolved: true is declared, the source must already be
flattened per-device canonical data and retain interfaces.ethernets; CVK only
normalizes that section to its internal interface_ethernet writer family.
apiVersion: config.cisco.vk/v1alpha1
kind: NXOSConfig
metadata:
name: nexus9300v-system
namespace: default
spec:
deviceRef:
name: nexus9300v-01
managedFamilies:
- system
- interface_ethernet
driftPolicy: report
modelSource:
format: netascode-nxos
modelVersion: 0.3.0
schemaDigest: sha256:5d5482679fb28e751d34cdc49342f8434914a7714966ba8244923b95d678698d
resolved: true
exporter: example-customer-exporter@0123456789abcdef0123456789abcdef01234567
sourceRevision: 0123456789abcdef0123456789abcdef01234567
source:
inline:
system:
hostname: nexus9300v-01
interfaces:
ethernets:
- id: 1/1
mtu: 9216
shutdown: false
description: CVK uplink
$ kubectl get nxosconfig
NAME DEVICE PHASE DRIFT AGE
nexus9300v-system nexus9300v-01 InSync report 30s
When modelSource is present, NX-OS reconciliation fails closed unless the
payload identifies an exact supported conformance contract. The initial
contract identifies Network as Code module 0.3.0 at revision
706c1b390b7c23f8950714788129b1c51233de6a, the normalized NX-OS schema
subtree at qualified schema snapshot
9e45ad51227a2e534c5ded8f3258c4feb9a53c5d, and was qualified with NX-OS
provider 0.13.1 and utils provider 2.0.0. These exact provider versions are
the official tested baseline; the module constraints themselves permit a
range. schemaDigest is the SHA-256 of jq -cS '.properties.nxos' schema.json
including the command's trailing LF. It is a compatibility label, not a
payload-integrity digest or a substitute for schema validation. exporter
must identify an immutable name@version or name@digest, and
sourceRevision identifies the immutable customer intent revision. Native
CVK-authored payloads may omit modelSource; declaring it opts the CR into
strict flattened-source and model/device contract validation before
configuration fetch or write.
The supported slice is guarded by checked-in golden artifacts generated from
the pinned Terraform plan and NX-OS provider DME bodies. Conformance compares
device path, DME class ancestry, object identity, attributes, and values while
ignoring only empty attributes containers and operation batching. In
particular, VLAN payloads do not add a provider-external pcTag, and Ethernet
payloads retain the provider-derived adminSt, layer, and
userCfgdFlags values.
CVK adds a fail-closed interface safety boundary around those defaults. A
strict import cannot implicitly convert an observed Layer-3 interface to
Layer-2, and it cannot bring up an observed shutdown interface unless
shutdown: false is explicit. Native CVK sources preserve omitted admin/layer
properties on description or MTU-only updates. These protections deliberately
narrow the upstream provider behavior to avoid accidental connectivity
changes.
NX-OS runtime coverage
NX-OS uses the Network as Code nxos model shape, but CVK only writes the
fields listed below today. Unsupported fields fail closed instead of being
silently ignored. If spec.managedFamilies names a family outside this
supported table, the common runtime marks the CR Failed before contacting the
device; planned families remain documentation until their writer and verify
fixtures are present. CiscoDevice.spec.configPrereqs uses the same NX-OS
family gate when it creates an owned NXOSConfig, so unsupported explicit or
source-derived families fail at CiscoDevice reconciliation time.
| Family | Supported fields | Not supported in this slice |
|---|---|---|
system |
system.hostname, system.mtu |
broader Ethernet defaults, boot, clock, NX-API, SSH, and platform subtrees |
feature |
NetAsCode feature booleans: analytics, bash_shell, bfd, bgp, dhcp, evpn, fabric_forwarding, grpc, hsrp, interface_vlan, isis, lacp, lldp, macsec, netflow, ngmvpn, ngoam, nv_overlay, nxapi, ospf, ospfv3, pim, private_vlan, ptp, scp_server, security_group, service_acceleration, sflow, sftp_server, ssh, tacacs, telemetry, telnet, udld, vn_segment_vlan_based, vpc |
provider aliases such as hmm, pvlan, and vn_segment; disabling nxapi, ssh, scp_server, sftp_server, or tacacs through NXOSConfig is rejected to avoid management lockout |
feature_set |
feature_set.fex, feature_set.mpls, feature_set.virtualization |
none in the NetAsCode boolean slice |
vlan |
vlan.vlans[].id, vlan.vlans[].name |
VNI / VXLAN leaves such as vni or vn_segment; prune deletes are supported for CR-owned VLANs except VLAN 1 |
interface_ethernet |
interfaces.ethernets[].id, description, shutdown, mtu |
switchport, IP/IPv6, channel-group, OSPF, PIM, ACL/NAT attachments; physical interface deletion/prune is intentionally unsupported |
Strict NetAsCode imports require Boolean values for every feature.* and
feature_set.* leaf, matching the pinned schema. Native CVK sources may still
use the writer's provider-state string compatibility forms. CVK also
deliberately refuses to disable nxapi, ssh, scp_server, sftp_server, or
tacacs; that management-lockout protection is a CVK safety extension to the
upstream module rather than identical behavior.
Create/update payload parity does not imply Terraform lifecycle parity. An omitted CVK field normally means “leave unchanged,” while the Terraform provider can emit unset markers or child deletes when an optional field is removed from its state. Destructive removal support therefore remains family-specific and fail-closed until ownership and live cleanup tests exist.
Planned NX-OS family waves are tracked in Production Readiness. The parity target is the current Network as Code NX-OS data-model stripe, which is split into entity, device, and interface sections.
Entity/source pattern coverage:
| Pattern | Status |
|---|---|
Direct per-device family data, including canonical interfaces.ethernets |
Required when modelSource.resolved: true declares strict imported provenance. |
devices, device_groups, global |
Native compatibility only when modelSource is omitted; CVK performs expansion. |
variables, templates with type: model, interface_groups |
Native compatibility only when modelSource is omitted; rejected in strict resolved imports. |
managed_devices, managed_device_groups |
Planned for a future source/orchestration layer; one NXOSConfig reconciles one selected device today. |
yaml_files, yaml_directories, file templates, write_model_file |
Deferred; render these outside the controller and submit resolved intent. |
ordered cli_templates |
Deferred for config reconciliation; use DeviceOperation for explicit NX-API CLI execution. |
If an owned source-derived NXOSConfig prereq CR is externally deleted during
teardown, the controller cannot reconstruct prior owned keys from the vanished
status. It records PrereqTeardownObserved=True with a warning event instead
of blocking device deletion indefinitely; operators should inspect the device
for any orphaned prereq state.
Full family parity is not claimed until each family has DME mapping, Fetch -> Diff -> Apply -> Verify behavior, fake transport tests, and an optional guarded live write test. The current target set is:
| Wave | Families |
|---|---|
| Management/base | aaa, banner, cdp, clock, dns, lldp, logging, ntp, nxapi, snmp, ssh, system, udld |
| L2 and interfaces | arp, interface_ethernet, interface_loopback, interface_management, interface_port_channel, interface_subinterface, interface_vlan, spanning_tree, vlan, vpc |
| L3 and routing | bfd, bgp, dhcp, hsrp, ip_route, ipv6_route, isis, nd, ospf, ospfv3, pim, ptp, vrf |
| Policy/security | community_list, hypershield, ip_access_list, ip_prefix_list, ipv6_access_list, ipv6_prefix_list, key_chain, qos, route_map, security_group, span |
| Fabric and telemetry | analytics, evpn, fabric_forwarding, interface_nve, netflow, sflow, telemetry |
NX-OS REST/DME semantics
NXOSConfig writes through NX-API REST/DME and reports transport kind
nxapi. It does not use NETCONF candidate datastores, gNMI
transactions, or confirmed commit. That means:
spec.transactionalcannot make an NX-OS DME apply atomic.spec.confirmTimeoutSecondscannot provide device-native auto-revert.- CVK relies on Fetch -> Diff -> Apply -> Verify for every supported family.
writeStartup: trueis safe only after every managed family verifies.revisionHistoryLimit,rollbackTo, and declarative rollback are rejected for NX-OS today; recovery requires an explicitly reviewed compensating configuration or device-native operational procedure.
When a CR requests confirmed-commit behavior that the selected transport cannot
provide, CVK records a warning event and a stable status entry under
status.transportFallbacks, for example:
status:
transportFallbacks:
- type: ConfirmedCommit
reason: non-transactional reconcile
message: spec.confirmTimeoutSeconds set but auto-revert path is unavailable on this transport
Available configuration families
For a complete reference of all available IOS-XE families and their YAML shapes, see the Family Reference. For YANG version compatibility and the version override architecture, see YANG Version Support.
Key families available today:
| Category | Families |
|---|---|
| L2 | vlan, spanning_tree, interface_switchport |
| L3 interfaces | interface_loopback, interface_vlan, interface_ethernet, interface_port_channel |
| Routing | ospf, bgp, static_route, vrf |
| Security | aaa, access_list_standard, access_list_extended, prefix_list, ip_community_list, ip_as_path_access_list |
| Crypto | crypto_ikev2_profile, crypto_ipsec_profile, crypto_ipsec_transform_set |
| Policy | route_map, class_map, policy_map |
| Management | snmp_server, logging, ntp, banner, clock, system |
| Multicast | pim, ipv6_pim |
| NAT | interface_ethernet (NAT ip access-group leaves) |
| App/Automation | event_manager |