====== EVE-KVM: Deploying a VM and configuring its network ====== 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 zedcloud ''appInterface'' > API model; runtime behaviour is from EVE ''pkg/pillar'' (''zedrouter'' / ''nireconciler''). ===== Two ways an interface attaches ===== Each VM adapter attaches in one of two modes, selected by the ''directattach'' flag: * **Network-Instance-backed** (''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. * **Direct-attach** (''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. ===== What is a VIF ===== **VIF = virtual interface** — the **host-side** tap/virtio netdev EVE creates for an NI-backed adapter. Two ends exist: * Inside the guest: the NIC the VM sees (e.g. becomes ''enp1s0''). * On the EVE host: the VIF, named ''nbux'' (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**. ===== Interface binding — parameter reference ===== 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''. ===== access_vlan_id in depth ===== 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: * **0** — VIF is a **trunk port**: the VM sees all VLANs and frames keep their 802.1Q tags. The VM does its own tagging. * **1** — reserved/native on the Linux bridge; behaves like the trunk/untagged case, not a usable access VLAN. * **2–4093** — VIF is an **access port** on that VLAN: the VM sends/receives untagged frames and the bridge tags/untags with the VID. This is how you segment multiple VMs on one Switch NI into different VLANs. Two non-obvious behaviours: * **Switch NI only.** The VLAN code path is inside the ''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.) * **Bridge becomes VLAN-aware only when a VIF uses VID ≥ 2.** EVE's ''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. ===== Terraform example (NI-backed VM) ===== 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 } } ===== UI mapping (Adapters & Networks step) ===== ^ 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. ===== Inspecting on a live EVE-KVM node ===== 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}}' ===== Source references ===== * Per-interface model — zedcloud ''appInterface'' (swagger): ''accessVlanId'' (min 2 / max 4093; 0 = trunk, 1 = bridge-reserved), ''directattach'', ''intfname'', ''intforder'', ''ipaddr'', ''macaddr'', ''netinstname''/''netinstid'', ''defaultNetInstance'', ''privateip''. * VLAN application — ''nireconciler/linux_config.go'' (''AccessVlanID <= 1'' → trunk; ''>= 2'' → access port; ''getVLANConfigForNI'' computes uplink trunk VIDs). * App adapter config / VIF naming — ''types/zedroutertypes.go'' (''AppNetAdapterConfig''), ''nireconciler'' (''nbux'').