This page documents how a VM (edge app instance) attaches to the network on EVE-OS in KVM (hypervisor) mode, and describes every interface binding option available when deploying the instance (ZedControl UI or Terraform).
Scope. This is the per-instance interface binding — how a VM's adapters
map onto Network Instances. It complements the NI lifecycle page (how Local and
Switch NIs are built). Field semantics below are from the zedcloudappInterface
API model; runtime behaviour is from EVEpkg/pillar(zedrouter/nireconciler).
Each VM adapter attaches in one of two modes, selected by the directattach flag:
directattach = false) — EVE creates a host-side VIF (virtio tap) and enslaves it into the NI's Linux bridge. This is the normal path; ACLs and VLAN apply here.directattach = true) — a physical adapter (whole NIC via PCI passthrough, or an SR-IOV VF) is assigned straight to the guest. No VIF, no bridge, no NI; netinstname/acls/access_vlan_id do not apply, and the physical-adapter type match is used instead.VIF = virtual interface — the host-side tap/virtio netdev EVE creates for an NI-backed adapter. Two ends exist:
enp1s0).nbu<vifNum>x<appNum> (e.g. nbu1x3), enslaved into the NI bridge (bnN or the port bridge) as a bridge port.
domainmgr hands the VIF to QEMU/KVM as the backend for the guest's virtio-net
device. (EVE-K analogue: the Multus pod-side VIF; same concept, different plumbing —
tap-into-Linux-bridge here vs. CNI there.) A direct-attach adapter has no VIF.
Fields of one interfaces { } block (Terraform) = one appInterface (API) =
one row in the Adapters & Networks step (UI):
intfname — interface name the edge app expects; matched against the interface declared in the app manifest. Logical identity, not necessarily the guest's name. (EVE: AppNetAdapterConfig.Name.)intforder — ordering of this adapter vs. all other virtual and direct-assigned adapters. Drives the order VIFs are presented to the VM, hence PCI slot and guest NIC enumeration (eth0 vs eth1).directattach — false = attach via a Network Instance (VIF on bridge); true = assign a physical adapter directly (passthrough / SR-IOV VF). See modes above.access_vlan_id — access VLAN for the VIF on a Switch NI. 0 = trunk (all VLANs), 1 = reserved by the Linux bridge, 2–4093 = access port on that VLAN. Detailed below.default_net_instance — “default instance” flag. If true the adapter binds to the device/project default NI instead of a named one; leave false when naming a NI.ipaddr — static IP for the app on this interface. Per the API: if the NI's DHCP mode is static/reserved, put the reserved /32 here; empty = DHCP assigns dynamically. Only meaningful on a Local NI (EVE runs DHCP there); inert on Switch.macaddr — pin the VIF MAC (intended for P2V and DHCP=passthrough cases; also useful for MAC-bound licensing or external DHCP reservations). Empty = EVE auto-generates a deterministic MAC.netinstname — the Network Instance to attach to (the Network Instance column in the UI). Required when directattach = false. netinstid is the by-UUID alternative.privateip — if true, DHCP cannot be assigned and a static IP must be supplied via ipaddr; false = DHCP-assigned. Pairs with ipaddr.acls — per-interface firewall ruleset (the Firewall Rules column, e.g. “Edge App Configured”). Applicable only when directattach = false.
API model doc (appInterface.accessVlanId): VLAN id of zero is treated as a
trunk port; VLAN id 1 is implicitly used by Linux bridges; min 2, max 4093.
EVE implements exactly this in nireconciler/linux_config.go:
if ul.AccessVlanID <= 1 { vlanConfig.TrunkPort = &linux.TrunkPort{AllVIDs: true} // 0 or 1 -> trunk, all VLANs } else { vlanConfig.AccessPort = &linux.AccessPort{VID: uint16(ul.AccessVlanID)} // >=2 -> access port }
Three regimes:
Two non-obvious behaviours:
NetworkInstanceType == Switch branch; on a Local NI the field is ignored. (This is why the UI Access VLAN field is active when the bound NI is Kind = Switch.)getVLANConfigForNI scans every app interface on the NI: if all are 0/1 the bridge stays a plain L2 switch (no filtering); the moment one VIF requests an access VLAN, EVE enables VLAN filtering and auto-configures the physical uplink as a trunk carrying the union of all access VIDs in use (plus the NI's VlanAccessPorts). You do not trunk the uplink separately.
A standalone Ubuntu VM with a single virtio adapter on a Switch NI, trunk mode
(access_vlan_id = 0):
resource "zedcloud_application_instance" "tf_stndlone_vm_1" {
name = "TF-STND-ATL-VM-1"
title = "TF-STND-ATL-VM-1"
project_id = zedcloud_project.demo_zededa_project_1.id
app_id = zedcloud_application.tf_atl_ub_app_1.id
activate = true
device_id = zedcloud_edgenode.demo_en_advantech_1.id
custom_config {
add = true
allow_storage_resize = true
field_delimiter = "@@@"
name = "cloud-config"
override = true
template = base64encode(templatefile("./c-init/vm-1-enp1s0.txt", { hostname = var.vm1_hostname }))
}
drives {
imagename = zedcloud_image.demo_atl_ub_image_1.name
cleartext = false
ignorepurge = false
maxsize = 20971520
preserve = false
target = "Disk"
drvtype = "HDD"
readonly = false
}
interfaces {
intfname = "enp1s0" # adapter the app expects
intforder = 1 # NIC enumeration order in guest
directattach = false # virtio VIF on an NI bridge
netinstname = zedcloud_network_instance.tf_lan_switch_1.name # which NI to attach to
access_vlan_id = 0 # 0 = trunk on the Switch NI
default_net_instance = false # use the named NI, not default
ipaddr = "" # DHCP-assigned
macaddr = var.vm1_macaddr # pinned MAC
privateip = false # DHCP (not static-required)
}
logs { access = true }
}
| ZedControl field | Terraform / API field | Notes |
|---|---|---|
| Network Adapter Name | intfname | the app's declared interface (e.g. enp3s0) |
| Network Instance | netinstname / netinstid | the NI to bind (e.g. TF-CL250-NI-1) |
| Firewall Rules | acls | “Edge App Configured” = ACLs from the manifest |
| Kind / Port / Addressing | (from the NI, read-only) | e.g. Switch / eth0 / None |
| DHCP | (from the NI) | e.g. Default |
| IP Address | ipaddr | static /32 when the NI is reserved-DHCP |
| MAC Address | macaddr | empty = EVE auto-generates |
| Access VLAN Id [2-4093] | access_vlan_id | empty box == 0 == trunk; ≥2 = access port |
An empty Access VLAN Id box is equivalent to access_vlan_id = 0 (trunk),
identical in effect to the Terraform sample above.
Via EdgeView / device debug shell:
# VIFs of running apps and their bridge membership ip -br link show | grep nbu bridge link show | grep nbu # nbu1x3 master bn1 (or the port bridge) # Is the bridge VLAN-aware, and what VIDs are programmed? bridge vlan show # access PVID per VIF, trunk VIDs on the uplink # App network status as zedrouter sees it cat /run/zedrouter/AppNetworkStatus/*.json | jq '{app:.DisplayName, vifs:.UnderlayNetworkList[]?|{name:.Name, vif:.Vif, bridge:.Bridge, mac:.Mac, vlan:.AccessVlanID, ip:.AllocatedIPv4Addr}}'
appInterface (swagger): accessVlanId (min 2 / max 4093; 0 = trunk, 1 = bridge-reserved), directattach, intfname, intforder, ipaddr, macaddr, netinstname/netinstid, defaultNetInstance, privateip.nireconciler/linux_config.go (AccessVlanID ⇐ 1 → trunk; >= 2 → access port; getVLANConfigForNI computes uplink trunk VIDs).types/zedroutertypes.go (AppNetAdapterConfig), nireconciler (nbu<vifNum>x<appNum>).