User Tools

Site Tools


eve-kvm:deploying-vm

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 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.

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 (nbu<vifNum>x<appNum>).
eve-kvm/deploying-vm.txt · Last modified: by mc