macvlan, ipvlan, host-device and cluster-wide IPAM
S1·E5Whose ticket it actually was · A customer bridge call at 02:40, day nineteen, the network team already on the line
Builds on: Multus and the NetworkAttachmentDefinition, MTU budgeting across switch, host PF and pod
Before you read: what do you already know?
3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.
After this lesson you can
- Distinguish macvlan, ipvlan and host-device by what each one gives the pod and what it takes away.
- Predict which of the three secondary CNIs a given customer requirement rules out, and cite the documented rule that rules it out.
- Explain why `host-local` IPAM produces duplicate addresses across nodes and choose between Whereabouts and nv-ipam for a cluster-wide fix.
- Map an nv-ipam `IPPool` or `CIDRPool` to the per-node ranges it actually produces.
- Give a customer the one-pod-per-NIC versus several-pods-per-NIC versus IP-level-only decision tree without over-promising RDMA.
Episode 5 — Whose ticket it actually was
Two training pods on different nodes hold the same address on the rail subnet. The network team has spent two hours proving the leaves are clean, and they have proved it: the counters are boring, which is the network lead’s favourite kind of evidence. The operator who found it has his labels in front of him; they all match. Nobody has read the NAD, because the NAD is four lines and was copied from a working example.
Those four lines are the bug. The example used host-local, and Whereabouts exists precisely because “host-local only knows how to assign IPs to pods on the same node”; people “assume it’ll work across nodes – and then wind up with IP address collisions”.[4] On the primary CNI that is invisible, because each node owns a disjoint pod CIDR. On a secondary network where eight nodes draw from one flat subnet, it is a latent outage whose symptom points at the wrong team.
That is the second half of the fast path: choosing the attachment is only half the design, and choosing the address source is the half that fails weeks later. The attachments have their own documented rules to respect - macvlan “functions like a switch that is already connected to the host interface”, ipvlan “devices all share the same MAC”, and host-device simply moves the requested device into the container.[1][2][3]
An interface is a capacity decision; an address is a bookkeeping decision. They fail on different days.
Start with what each plugin gives the pod, and what it quietly takes away.
1macvlan: a switch already connected to the host interface
The macvlan plugin describes itself in one sentence that is worth memorising, because it predicts most of its behaviour: it “functions like a switch that is already connected to the host interface”.[1] The host interface is enslaved, and each virtual interface shares the physical device while keeping a distinct MAC address - which is why existing DHCP servers keep working against macvlan children.[1]
Required fields are name, type set to "macvlan", and ipam, which may be an empty dictionary if you want an interface with no address at all.[1] The optional fields are the ones that matter in a lab: master (defaults to the default-route interface, which is almost never what you want on a GPU node), mode, mtu, and linkInContainer.[1]
Two documented facts carry weight later. First, mtu has an explicit range - zero to the master’s MTU - so a pod interface can never be raised above the PF it hangs off.[1] That is the hard cap from the MTU lesson, restated as a plugin rule rather than as arithmetic. Second, one master interface cannot be enslaved by macvlan and ipvlan at the same time, so the two plugins are mutually exclusive per PF, not per cluster.[1] Wireless cards mostly cannot be enslaved at all, which matters only as a reminder that “enslavement” is a driver capability and not a guarantee.[1]
Pod eth0 — 8950 B
9000 − 50 = 8950. network MTU − 50. Cilium documents the same 50 bytes per packet for VXLAN, which is why jumbo frames pay for themselves on either CNI.
Calico: You set it. Calico does not measure the fabric for you.
- Rule, verbatim: “Set the workload endpoint MTU and the tunnel MTUs to the same value (so all paths have the same MTU).”
- Manifest installs change it through the calico-config ConfigMap key veth_mtu, then the DaemonSet is restarted.
- Verify the tunnel with ip link show; the IP-in-IP tunnel appears as tunlx.
- Policy-only mode (CALICO_NETWORKING_BACKEND=none) is the clean answer when a different fabric owns routing — then there is no tunnel to budget for.
Workload MTU and tunnel MTU are both 8950 — Calico wants them identical so every path has the same MTU.
- ⚠ macvlan mtu has range 0 to the master's MTU. A NAD asking mtu: 9216 on a 9000-MTU master is out of range — the pod does not get a bigger MTU than the PF, ever.
- ⚠ Classic bug shape: a 9000 fabric with the pod network left at 1500. NCCL's TCP bootstrap still works and the data path crawls, so the ticket reads "training is slow", never "MTU is wrong".
- Pod eth0 (8950) and pod net1 (9000) differ by 50 bytes. That is correct — the secondary path skips the tunnel — but net1 must still equal the PF and the switch port.
kubectl patch configmap/calico-config -n kube-system --type merge -p '{"data":{"veth_mtu": "8950"}}'
kubectl rollout restart daemonset/calico-node -n kube-system # the doc says: restart the DaemonSet
ip link show # the tunnel device MTU must read 8950
kubectl exec -it <pod> -- ip link # eth0 must read 8950, net1 must read 9000
# NAD delegate: {"type": "macvlan", "master": "<pf>", "mtu": 9000, "ipam": {...}}2ipvlan: same wire, one MAC, two limits people trip over
ipvlan is the same idea with one variable changed. The documentation states it directly: “Like its cousin macvlan, it virtualizes the host interface. However unlike macvlan which generates a new MAC address for each interface, ipvlan devices all share the same MAC.”[2] Fields are master, mode (one of "l2", "l3", "l3s", defaulting to "l2"), mtu (defaulting to the kernel-chosen value rather than to the master’s), ipam, and linkInContainer.[2]
The shared MAC buys you a switch that does not have to learn a new address per pod. It costs you two things, both documented, both the source of tickets.
The first is DHCP: “Because all ipvlan interfaces share the MAC address with the host interface, DHCP can only be used in conjunction with ClientID (currently not supported by DHCP plugin).”[2] In practice that means static or cluster-wide IPAM, which is the next segment anyway.
The second is the one that surprises senior people. A container cannot reach its own host through the ipvlan interface - the plugin page says so plainly and tells you to attach another network, such as ptp, if host communication is needed.[2] Anything that assumes a pod can curl a node-local agent, a node exporter or a health endpoint over the fast interface will fail on ipvlan and work on macvlan, for a reason that has nothing to do with either being broken.
3host-device: moving the whole thing, once
host-device is the bluntest of the three: “This simple plugin will move the requested device from the host’s network namespace to the container’s.”[3] That single sentence contains the entire capacity model. The device is moved, not shared, so the node has exactly as many host-device attachments as it has devices, and the second pod that asks for the same one does not get a degraded interface - it gets nothing.
Four fields select the device: device (a name such as eth0), hwaddr (a MAC), kernelpath (for example /sys/devices/pci0000:00/0000:00:1f.6) and pciBusID (for example 0000:00:1f.6).[3] On top of those it supports the deviceID runtime capability, which takes a PCI address - that is the hook a device plugin uses to hand host-device a specific NIC at allocation time rather than having it hard-coded in the NAD.[3]
Three behaviours to know before you write the lab. CNI_IFNAME is disregarded, so the interface arrives in the pod under its own name and any manifest that expects net1 is wrong.[3] On CNI DEL the device is returned to the host’s network namespace, which is what makes this reversible and therefore lab-safe.[3] And when the device is bound to a DPDK driver - selected through pciBusID or the deviceID runtime argument - IPAM configuration is bypassed entirely, because there is no kernel interface left to address.[3]
In a Network Operator deployment you rarely write this JSON by hand. The HostDeviceNetwork CR takes networkNamespace, resourceName and ipam and generates the NAD, and the matching pod requests nvidia.com/hostdev: 1 in both requests and limits alongside the IPC_LOCK capability.[6] That resource request is what enforces the one-pod-per-device rule at scheduling time, because extended resources are integer-only, non-overcommittable, and cannot be shared between containers.[8]
4Cluster-wide IPAM: why host-local is a latent outage
The default IPAM plugin most people copy from an example is host-local, and it keeps its allocation state on the node it runs on - Whereabouts exists precisely because “host-local only knows how to assign IPs to pods on the same node”.[4] On the primary CNI that is fine, because each node owns a disjoint pod CIDR. On a secondary network where every node draws from the same flat subnet, it is a defect: Whereabouts’ own README notes that people “assume it’ll work across nodes – and then wind up with IP address collisions”, and the resulting duplicate-address symptoms on a rail fabric look exactly like a switch problem, so the ticket arrives at the wrong team.[4]
Whereabouts is “An IP Address Management (IPAM) CNI plugin that assigns IP addresses cluster-wide.”[4] Required fields are type set to whereabouts and range.[4] Optional fields cover the shapes a real subnet needs: range_start and range_end, exclude (a list of CIDRs to skip), network_name (independent allocation for two networks that use the same CIDR), gateway, routes, enable_overlapping_ranges which defaults to true, and node_slice_size, which turns on the experimental Fast IPAM mode by pre-allocating a slice per node.[4]
{
"cniVersion": "0.3.0",
"name": "whereaboutsexample",
"type": "macvlan",
"master": "eth0",
"ipam": {
"type": "whereabouts",
"range": "192.168.2.225/28",
"exclude": ["192.168.2.229/30"]
}
}[4] State lives in two CRs - IPPool stores the allocated addresses and OverlappingRangeIPReservation tracks assignments across ranges - so you can read the allocation table with kubectl instead of guessing.[4] Two known issues are worth quoting to a customer before they find them: system crashes may leave stranded allocations, which on an unstable cluster can exhaust the range over weeks; and a wide IPv6 CIDR of /64 or larger is only addressable within its first /65 because of a uint64 offset constraint.[4]
nv-ipam is NVIDIA’s answer and has three parts: ipam-controller, a controller that watches IPPool and CIDRPool CRs in a predefined namespace and assigns each node a cluster-unique range through CR status; ipam-node, a node daemon that installs the CNI binary, performs allocations and persists them to disk behind a gRPC service; and nv-ipam, the CNI binary that calls that daemon.[5] The CNI config takes poolName (comma-separated for dual-stack), poolType (ippool or cidrpool, case-insensitive), daemonSocket, daemonCallTimeoutSeconds and logLevel.[5]
The two pool kinds allocate differently and confusing them is a real mistake:
| field | 192.168.0.0/16 gives node1 | gives node2 | |
|---|---|---|---|
IPPool |
perNodeBlockSize: 24 |
192.168.0.1 - 192.168.0.24 | 192.168.0.25 - 192.168.0.48 |
CIDRPool |
perNodeNetworkPrefix: 24 |
192.168.0.0/24 | 192.168.1.0/24 |
[5] IPPool also takes subnet, gateway, exclusions, perNodeExclusions, defaultGateway and routes; CIDRPool takes cidr, gatewayIndex, exclusions, perNodeExclusions, staticAllocations, defaultGateway and routes.[5] NVIDIA’s Spectrum-X rail configuration uses CIDRPool with perNodeNetworkPrefix: 31 and gatewayIndex: 0 - one /31 per node per rail, which is the smallest thing that still has a gateway.[10] Network Operator deploys nv-ipam itself through NicClusterPolicy.spec.nvIpam, so on an operator-managed cluster it is already versioned and patched with everything else.[6]
Checks
1 error- error{"type":"rdma"} cannot be chained after ipvlan. rdma-cni requires SR-IOV capable hardware and an SR-IOV supporting CNI, and it must come after the sriov / ib-sriov plugin in the plugins array. Over ipvlan, RDMA exists only through the shared device plugin (rdma/rdma_shared_device_a). [rdma-cni prerequisites + chained-plugin order]
- warnhost-local allocates per node: pods on different nodes get the same address out of the same subnet, and on a rail fabric that reads as a switch fault. Whereabouts and nv-ipam are the two cluster-wide answers. [cluster-wide IPAM]
- warn"ipvlan does not allow virtual interfaces to communicate with the master interface. Therefore the container will not be able to reach the host via ipvlan interface." Shared MAC also means DHCP needs ClientID, which the DHCP plugin does not support. [ipvlan limitations]
- infoCalico owns eth0 only. Confirm it has not autodetected ens1f0 as its uplink: sharing one interface with the fast path works until Felix reconfigures it, and then RDMA disappears. [primary CNI must not claim the fast-path interface]
Delegate — ipvlan
Like macvlan, but every ipvlan device shares the master’s MAC address.
- Fields: master, mode ("l2" default, "l3", "l3s"), mtu (kernel-chosen by default), ipam, linkInContainer.
- "ipvlan does not allow virtual interfaces to communicate with the master interface. Therefore the container will not be able to reach the host via ipvlan interface."
- Because all ipvlan interfaces share the host MAC, DHCP works only with ClientID — currently not supported by the DHCP plugin.
- Operator route: No operator CR — hand-written NAD.
FAE angle: the host-unreachable rule surprises people who put a node-local health-check endpoint behind the secondary interface. It is documented behaviour, not a driver bug.
sourceapiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: ipvlan-net
namespace: default
spec:
config: |
{
"cniVersion": "0.3.1",
"name": "ipvlan-net",
"plugins": [
{
"type": "ipvlan",
"master": "ens1f0",
"mode": "l2",
"ipam": {
"type": "host-local",
"subnet": "10.56.217.0/24"
}
},
{
"type": "rdma"
}
]
}5The decision tree you actually give a customer
The customer question is never “compare these three CNI plugins”. It is “which one for GPUDirect”, and the answer has to survive being repeated by someone else in a meeting you are not in. Three lines:
- host-device moves the whole PF into the pod - full line rate, full RDMA, and exactly one pod per NIC per node.[3][8]
- SR-IOV slices the PF into virtual functions, so several pods share one NIC with isolation enforced in hardware.[7]
- macvlan and ipvlan give an IP-level secondary interface, and RDMA over them is available only through the shared RDMA device plugin, which over-advertises one HCA as a counted scheduling resource rather than isolating it per pod.[9]
NVIDIA’s own quick-start use-case list is organised along exactly this seam, which is useful because you can point a customer at their vendor’s page rather than at your opinion: “SR-IOV Network with RDMA” and “Host Device Network with RDMA” on one side, “IP over InfiniBand with RDMA Shared Device” and “MacVLAN Network with RDMA Shared Device” on the other, with “SR-IOV InfiniBand Network with RDMA” for the IB case.[7] The words “RDMA Shared Device” in two of those five titles are doing all the work.
Ask: six inference pods per node, each needing an address on the 192.168.2.0/24 storage subnet. The leaf enforces one MAC per server port. No RDMA required today. Two nodes now, twelve by the end of the quarter.
- How many pods per node need the interface? Six. That rules out
host-deviceimmediately - it moves the device, so the ceiling is one pod per NIC.[3] - Is hardware isolation required? No, this is IP reachability to storage. SR-IOV is available but not required, and it costs VF configuration and a device plugin. Park it.
- macvlan or ipvlan? The leaf enforces one MAC per port. macvlan gives each pod a distinct MAC and would violate that policy at pod seven.[1] So
ipvlan, whose interfaces share the host MAC.[2] - Check what ipvlan costs here. DHCP is out, because the shared MAC needs ClientID and the DHCP plugin does not support it.[2] And nothing in these pods may curl a node-local endpoint over this interface, because ipvlan cannot reach its own master.[2] Confirm both with the application team before proceeding; if either is a hard requirement, go back to step 3 and negotiate the MAC policy instead.
- Choose the IPAM. Twelve nodes drawing from one flat /24 means
host-localwill duplicate. Cluster-wide is mandatory. There is no Network Operator on this cluster yet, sowhereaboutsis the lighter answer.[4] - Bound the range and leave room. Use
rangewithrange_startandrange_end, andexcludethe switch VRRP addresses.[4] - Write it:
[2] [4]{"cniVersion":"0.3.1","type":"ipvlan","master":"ens1f0","mode":"l2", "ipam":{"type":"whereabouts","range":"192.168.2.0/24", "range_start":"192.168.2.100","range_end":"192.168.2.200", "exclude":["192.168.2.1/32"]}} - Verify:
kubectl get ippools.whereabouts.cni.cncf.io -Ashows one pool with six allocations per node and no address appearing twice.[4]
Ask: one training pod per node needs the full 400 Gb/s ConnectX-7 port with GPUDirect RDMA. Eight nodes. The cluster already runs Network Operator.
- Pods per node needing the interface: ____ . Therefore the delegate is ____ , because ____ .[3]
- What enforces “one pod per NIC” at scheduling time rather than at attach time? ____ , and it works because extended resources are ____ and ____ .[8]
- Which Network Operator CR generates this NAD, and which three fields does it take? ____ .[6]
- What must the pod request in both
requestsandlimits, and which capability must it carry? ____ .[6] - IPAM choice: ____ rather than ____ , because the cluster already runs Network Operator and the pool is then ____ .[5][6]
- If each node needs one address per rail out of a /16, which pool kind and which field? ____ and ____ .[5]
- Name one thing about the in-pod interface that will not be what the manifest expects, and why. ____ .[3]
- State the rollback for the whole configuration in one line: ____ .[3]
A Dell customer is standing up sixteen PowerEdge nodes, each with two ConnectX-7 ports. Their platform team has written one NAD - macvlan on ens1f0 with host-local IPAM over 10.10.0.0/16 - and applied it everywhere. They report three symptoms: NCCL jobs sometimes fail to connect between specific node pairs; a node-local metrics agent is unreachable from some pods but not others; and ib_write_bw between two pods fails outright although both have addresses on the fast subnet.
Produce: (a) which of the three symptoms are caused by the IPAM choice and which are not, with the documented rule behind each; (b) the configuration you would propose instead, given that four pods per node need the fast path and two of those need genuine RDMA isolation, naming the delegate and the IPAM for each class; (c) the exact evidence you would ask for before proposing anything, in the order you would ask for it; (d) one paragraph you could send to the platform team explaining why the third symptom is not a defect in the NIC, the switch or the driver.
Acceptance criteria: your answer separates addressing failures from attachment failures rather than treating all three as one bug; you name the shared-versus-exclusive RDMA distinction without claiming macvlan can be made to do exclusive RDMA; and your evidence list starts with something cheaper than a packet capture.[3][4][7][9]
Case closed
The leaves were never it. host-local allocated from the same range on every node in ignorance of the others, and the collision surfaced only when two pods landed on the wrong pair.[4] Whereabouts turns the range into one cluster-wide record; nv-ipam does the same job on a cluster that already runs Network Operator.[5] On day twenty-six the acceptance run finishes inside the window: three MTUs that agree, an annotation that resolves, one address per pod, and a rail the primary CNI never touches. Procurement gets its lead time, the network lead closes the notebook at page eleven, and row 14 of the SE’s spreadsheet stops being a promise and becomes a measurement.
Lab
Goal: on the Dell BF-3 host in NIC mode, demonstrate host-device’s capacity model directly - one pod takes the PF, the second stays Pending - and confirm the device comes back to the host on delete. This lab moves a real interface out of the host namespace, so it is mutating and every step names its rollback.
- Pre-flight inventory. Capture this to a file; step 6 diffs against it:
Expected: the PF you intend to use is present, driverip -br link | tee /tmp/pre-link.txt ip -br addr | tee /tmp/pre-addr.txt ethtool -i ens1f1 | head -3 lspci -D | grep -i mellanox kubectl get net-attach-def -A kubectl describe node | grep -A20 Allocatablemlx5_core, and you have its PCI address in0000:xx:00.yform.[3] - Choose a PF that is not carrying anything. Confirm it is not the primary CNI’s uplink (lesson 1’s check) and not the management interface. host-device removes the interface from the host, so doing this to the wrong PF drops your SSH session. If the host has only one usable PF, do not run this lab on it.
- Create the host-device NAD naming the PF by
pciBusID:
[3] Rollback:kubectl apply -f - <<'EOF' apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition metadata: { name: hd-net } spec: config: '{"cniVersion":"0.3.1","type":"host-device","pciBusID":"0000:3d:00.1"}' EOFkubectl delete net-attach-def hd-net. - Launch one pod against it, then a second identical pod:
Expected: the pod has the interface under its own host name, notkubectl run hd-a --image=nicolaka/netshoot --restart=Never \ --annotations="k8s.v1.cni.cncf.io/networks=hd-net" -- sleep 3600 kubectl exec hd-a -- ip -br link ip -br link | grep -c ens1f1 # run on the hostnet1, becauseCNI_IFNAMEis disregarded; and the host no longer lists it.[3] Rollback:kubectl delete pod hd-a- and confirm withip -br linkon the host that the interface returned, which is the documented CNI DEL behaviour.[3] - With
hd-astill running, starthd-bthe same way. Expected on this NAD as written: the second pod schedules and then fails at CNI ADD, because nothing counted the device. Record the verbatim event. Then note what would have been different with a device plugin advertisingnvidia.com/hostdevand the pod requesting it: the second pod would have stayedPendingat scheduling withInsufficient nvidia.com/hostdevand never reached the CNI layer, because extended resources are integer-only and non-shareable.[8][6] That difference - fail at attach versus refuse at schedule - is the whole argument for the device plugin.[3] - Restore and verify. Delete both pods, delete the NAD, then:
Expected: both diffs clean. If the interface came back without its addresses, re-apply them fromip -br link | diff /tmp/pre-link.txt - && echo "links restored" ip -br addr | diff /tmp/pre-addr.txt - && echo "addresses restored"/tmp/pre-addr.txt; host-device returns the device, not its configuration. - Deliverable: two verbatim outputs side by side - the in-pod
ip -br linkfrom step 4 and the host’sip -br linkfor the same moment - plus the event text from step 5. Annotate them with the one sentence from the plugin page that predicts each.
Rollback for the whole lab: kubectl delete pod hd-a hd-b --ignore-not-found, kubectl delete net-attach-def hd-net, then step 6’s diffs must be clean before you walk away from the host.
Goal: prove the cluster-wide IPAM claim by measurement rather than by reading, then demonstrate the cross-node duplicate that host-local produces. Two nodes are required - a two-node kind cluster or a two-node k3s is enough, and no NIC is involved.
- Pre-flight. Record what exists before you start, so the rollback is checkable:
Expected: two Ready nodes and no NADs. If the last command lists pools, you are on a cluster someone else is using - stop.kubectl get nodes -o wide kubectl get net-attach-def -A kubectl get ippools.whereabouts.cni.cncf.io -A 2>/dev/null || echo "whereabouts CRDs absent" - Install Multus and Whereabouts. Expected: both DaemonSets Ready and the
ippoolsandoverlappingrangeipreservationsCRDs present.[4] If not: read the Whereabouts DaemonSet logs before continuing; a missing CRD makes every later step fail identically. - Create the Whereabouts NAD with a deliberately small range and an exclusion, so exhaustion and skipping are both observable:
[1][4] Rollback:kubectl apply -f - <<'EOF' apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition metadata: { name: wa-net } spec: config: '{"cniVersion":"0.3.1","type":"macvlan","master":"eth0","mode":"bridge","ipam":{"type":"whereabouts","range":"192.168.2.225/28","exclude":["192.168.2.229/30"]}}' EOFkubectl delete net-attach-def wa-net. - Schedule ten pods against it with an anti-affinity or a plain Deployment of ten replicas, annotated
k8s.v1.cni.cncf.io/networks: wa-net. Then collect every address:
Expected: no output -kubectl get pods -o name | xargs -I{} kubectl exec {} -- ip -4 -o addr show net1 | awk '{print $4}' | sort | uniq -duniq -dprinting nothing is the proof. Expected also: no address inside 192.168.2.229/30, becauseexcludeskipped it.[4] If a duplicate appears, check that both nodes really ran the same Whereabouts version. - Read the state Whereabouts kept:
kubectl get ippools.whereabouts.cni.cncf.io -A -o yamlandkubectl get overlappingrangeipreservations.whereabouts.cni.cncf.io -A. Expected: an allocation table you can map one-to-one onto step 4’s addresses.[4] - Now break it on purpose. Delete the Deployment, edit the NAD’s
ipamblock to{"type":"host-local","ranges":[[{"subnet":"192.168.2.224/28"}]]}, and re-run steps 4 and 5. Expected:uniq -dnow prints at least one address, and the duplicate pair is always on different nodes. Rollback: restore the Whereabouts config in the NAD. - Write down the sentence you would say to a customer whose leaf is reporting duplicate IPs on a rail subnet, and which single command from this lab you would ask them to run first.
- Full rollback: delete the Deployment, delete
wa-net, and re-run step 1 -kubectl get net-attach-def -Amust return to empty and noIPPoolobjects may remain.
Retrieval check
10 questions from memory. Answer before looking anything up; misses become flashcards.
Explain it to a Dell SE
Explain to a Dell SE, in five sentences, how you would choose between macvlan, host-device and SR-IOV for a customer's second interface, and why the addressing question is a separate decision that people keep forgetting.
Sources
Facts in this lesson were checked against cni.dev macvlan / ipvlan / host-device plugin pages, k8snetworkplumbingwg/whereabouts README and Mellanox/nvidia-k8s-ipam README all re-fetched 2026-09-09; NVIDIA Network Operator v26.7.0 quick-start use-case list re-fetched 2026-09-09; deployment guide, Spectrum-X quick start, Kubernetes device-plugin and k8s-rdma-shared-dev-plugin pages as fetched 2026-09-07. Dates are when each page was fetched.
- CNI plugins - macvlan · fetched 2026-09-09
- CNI plugins - ipvlan · fetched 2026-09-09
- CNI plugins - host-device · fetched 2026-09-09
- GitHub - k8snetworkplumbingwg/whereabouts · fetched 2026-09-09
- GitHub - Mellanox/nvidia-k8s-ipam (nv-ipam) · fetched 2026-09-09
- NVIDIA Network Operator v26.7.0 - Deployment Guide with Kubernetes · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Quick Start Guide for Kubernetes · fetched 2026-09-09
- Kubernetes - Device Plugins · fetched 2026-09-07
- GitHub - Mellanox/k8s-rdma-shared-dev-plugin · fetched 2026-09-07
- NVIDIA Network Operator v26.7.0 - Spectrum-X Quick Start · fetched 2026-09-07
The same idea elsewhere
Other lessons that cover this ground, sometimes from another course's angle.