Network as Code — Configuration and Drift Detection
Beta
IOSXEConfig 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 IOS-XE
configuration management. You express network intent as plain YAML
(IOSXEConfig CRs), and CVK reconciles the device to match — detecting
drift, applying changes, and maintaining a revision trail.
Concepts
Families
A family is one independently managed slice of IOS-XE configuration —
vlan, bgp, ospf, aaa, prefix_list, and so on. Each family has
its own writer that knows how to fetch, diff, and patch the relevant YANG
paths on the device.
Each IOSXEConfig 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. |
apply |
Re-apply the intended family config immediately. |
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: apply 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: apply
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: apply
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":{"revisionRef":{"name":"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.
Available configuration families
For a complete reference of all available 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 |