Repository navigation
VMs using systemd-resolved cannot resolve *.cozy.local: DHCP does not provide DNS search domains #2085
Description
Problem
Virtual machines deployed via the vm-instance chart that use systemd-resolved (Debian 12+, Ubuntu 22.04+, Fedora 33+, etc.) cannot resolve Kubernetes service FQDNs ending in .cozy.local.
$ resolvectl query my-service.my-namespace.svc.cozy.local
my-service.my-namespace.svc.cozy.local: resolve call failed: DNSSEC validation failed: refused
All *.cozy.local lookups fail with REFUSED, making it impossible for VMs to reach cluster services by DNS name.
Root cause
The .local TLD is reserved for multicast DNS (mDNS) per RFC 6762. systemd-resolved enforces this: any query for *.local is routed to mDNS (which is typically disabled in server VMs), not to the configured unicast DNS server (CoreDNS).
KubeVirt's virt-launcher DHCP provides the CoreDNS server IP to the VM, but does not provide DNS search domains. Without search domains or a ~local routing domain hint, systemd-resolved has no reason to send .local queries to CoreDNS and falls back to mDNS → REFUSED.
Expected behavior
VMs should be able to resolve:
<service>.<namespace>.svc.cozy.local(FQDN)<service>(short name, via search domain)
Evidence inside the VM
$ resolvectl status
Link 2 (enp1s0)
Current DNS Server: 10.96.0.10 # ← CoreDNS IP (correct)
DNS Servers: 10.96.0.10
DNS Domain: ~. # ← no search domains, no ~local routing
The missing piece is DNS Domain: <namespace>.svc.cozy.local svc.cozy.local cozy.local ~local.
Affected code
packages/apps/vm-instance
templates/vm.yaml — The cloudInitNoCloud volume only has secretRef (userdata). There is no networkDataSecretRef:
- name: cloudinitdisk
cloudInitNoCloud:
secretRef:
name: {{ include "virtual-machine.fullname" . }}-cloud-init
# networkDataSecretRef is missingtemplates/secret.yaml — Only generates userdata (cloud-init #cloud-config). No network-data secret exists.
Reproduction
- Deploy a VM using the
vm-instancechart with a Debian 12 or Ubuntu 24.04 golden image - SSH into the VM
- Try to resolve any cluster service:
resolvectl query kubernetes.default.svc.cozy.local # → REFUSED - Check
resolvectl status— no search domains are configured on the DHCP link
Proposed fix
Add a network-data secret to the vm-instance chart that injects DNS search domains via cloud-init network config v1.
1. New template: templates/secret-network-config.yaml
apiVersion: v1
kind: Secret
metadata:
name: {{ include "virtual-machine.fullname" . }}-network-config
stringData:
networkdata: |
network:
version: 1
config:
- type: nameserver
address: []
search:
- {{ .Release.Namespace }}.svc.cozy.local
- svc.cozy.local
- cozy.local
- type: physical
name: enp1s0
subnets:
- type: dhcpKey points:
address: []— DNS server IP is already provided by DHCP; we only need to add search domains- The
nameserverblock is global and coexists with per-interface DHCP config - The search domains tell
systemd-resolvedto routecozy.local(and subdomains) to the unicast DNS server
2. Update templates/vm.yaml
Add networkDataSecretRef alongside the existing secretRef:
- name: cloudinitdisk
cloudInitNoCloud:
secretRef:
name: {{ include "virtual-machine.fullname" . }}-cloud-init
networkDataSecretRef:
name: {{ include "virtual-machine.fullname" . }}-network-configCompatibility
secretRef(userdata) andnetworkDataSecretRef(network config) are independent fields in KubeVirt'scloudInitNoCloud— they do not conflict- Users setting custom
cloudInit(userdata) are unaffected since network-data is a separate secret - The
nameserverblock in cloud-init network config is global and does not interfere with DHCP
Open question
The cluster domain (cozy.local) is currently hardcoded. Ideally it should come from a platform-level value (e.g., networking.clusterDomain or a global Helm value). The exact mechanism to pass this to app-level charts is left to maintainers.
Workaround
Until fixed, users can manually configure DNS inside the VM using a systemd-networkd drop-in:
sudo mkdir -p /etc/systemd/network/10-netplan-enp1s0.network.d
sudo tee /etc/systemd/network/10-netplan-enp1s0.network.d/dns-fix.conf <<EOF
[Network]
Domains=<namespace>.svc.cozy.local svc.cozy.local cozy.local ~local ~.
EOF
sudo systemctl restart systemd-networkd systemd-resolvedReplace <namespace> with the actual tenant namespace. The ~local routing domain forces systemd-resolved to send .local queries to the unicast DNS server instead of mDNS.
Note:
netplandoes not support routing domains (~prefix), sosystemd-networkddrop-ins must be used directly.
Activity
dosubot commentedon Jul 27, 2026
Hi, mattia-eleuteri. I'm Dosu, and I'm helping the cozystack team manage their backlog and am marking this issue as stale.
Issue Summary
vm-instancechart andsystemd-resolvedare unable to resolve Kubernetes service names under*.cozy.local.~localrouting hint needed bysystemd-resolved.REFUSED.networkDataSecretRefso the VM receives the required DNS domains.Next Steps
Thanks for your understanding and contribution.
mattia-eleuteri commentedon Jul 27, 2026
dosubot commentedon Jul 27, 2026
Timofei Larkin (@lllamnyp) This issue is still relevant per the reporter, who confirmed the
vm-instance/systemd-resolvedDNS resolution problem under*.cozy.localremains unresolved. Could you please take a look and help with this?lexfrei commentedon Sep 7, 2026
virt-launcher does send the search domains, so the root cause here needs a correction. At v1.9.0
StartDHCPreads the launcher pod's/etc/resolv.confand passes the parsed search list down (kubevirt/kubevirt: pkg/network/driver/common.go), and the DHCP server puts it on the wire as option 119 alongside option 15 (kubevirt/kubevirt: pkg/network/dhcp/server/server.go). The launcher pod's resolv.conf carries<namespace>.svc.cozy.local svc.cozy.local cozy.local, so those domains are already being advertised to the guest.The guest drops them.
UseDomains=defaults tonoin systemd-networkd, per systemd.network(5): "When none of them are specified, defaults to no." A netplan-managed link ignores DHCP-supplied domains until something turns that on, which fits theresolvectl statusoutput above:DNS Domain: ~.is a link that was offered domains and kept none of them.The
.localpart needs a correction too. resolved routes*.localto mDNS only after failing to match a search or routing domain. Indns_scope_good_domain()the search-domain loop runs first and returns a positive scope on a suffix match, and the.localexclusion sits below it, reached only when nothing matched. The comment on that branch, in systemd/systemd: src/resolve/resolved-dns-scope.c on current main:A plain
cozy.localsearch domain is therefore enough to route those lookups to CoreDNS.~localworks, but it is not needed, and netplan's missing routing-domain support stops being a blocker for the fix proposed here.So the chain is: KubeVirt advertises the list, the guest discards it on
UseDomains=no, resolved is left with nothing matchingcozy.local, the.localexclusion fires, REFUSED. Anything that landscozy.localon the link fixes it, which includes the network-config secret proposed above and equallydhcp4-overrides: use-domains: true.Separate caveat on the proposed shape.
networkDataSecretRefreplaces cloud-init's fallback network configuration instead of adding to it, so the secret has to configure the interface as well, and the snippet namesenp1s0literally. A guest whose first NIC comes up under another name gets a configuration for a device it does not have and loses DHCP altogether, which is a worse failure than the DNS problem and would land on every VM the chart renders. Worth settling how the interface is identified before that shape ships.mattia-eleuteri commentedon Sep 17, 2026
Aleksei Sviridkin (@lexfrei) your correction holds on both counts, and it changed the fix. I went looking for why the search domains were being dropped rather than never sent, and ended up somewhere I did not expect.
UseDomains=nois the whole of it, sodhcp4-overrides: use-domains: trueis sufficient and thenameserverblock I proposed is not needed at all. That also disposes of the open question at the bottom of the original report: the domains on the wire come from the launcher pod's/etc/resolv.conf, so they already carry whatever cluster domain the cluster actually runs, and nothing has to be hardcoded or plumbed down from a platform value. #2372 stops touching this.Your objection to the shape was right too, and for a wider reason than the one interface name.
networkDataSecretRefreplaces cloud-init's fallback configuration, and the fallback covers exactly one interface — which means the same missing piece produces a second, separate failure I had been treating as unrelated. A VM attached to a VPC subnet never configures that interface, on Debian and Ubuntu only.virt-launcherdoes serve it:newDHCPConfiguratorinpkg/network/setup/launcher/configurator.gogates only oniface.Bridge != nil, so a DHCP server runs on every bridge-bound interface, secondary ones included, andBridgeConfigGenerator.Generatehands out the address the CNI put on the pod-side interface together with that link's MTU. On a cluster here the secondary pod interface carries its Kube-OVN address andenableDHCPis unset on every VPC subnet, so the offer is virt-launcher's. Nothing in the guest asks for it. AlmaLinux and Windows come up on the same subnets, which is the tell — with no server at all, none of the three would.The part worth reporting: this used to work.
packages/apps/virtual-machineshippedtemplates/secret-network-config.yamland anetworkDataSecretRefin c63fb20 ([apps] virtual-machine: add vpc support, 2025-10-21), gated on.Values.systemDisk.image. 08a5f98 (feat(platform): remove virtual-machine application, 2026-02-12) deleted the application and took the template with it, andvm-instancenever had one —git log --all -S networkDataSecretRef -- packages/apps/vm-instance/comes back empty. The gate could not be ported as written, sincesystemDisk.imagedisappeared in the same split, which is probably why nobody did. #2166 came out of the same migration.#4306 restores it, as a draft. It matches
en*andeth*instead of naming interfaces by index, which is what the old template did and what you flagged here; it describes every interface, since it replaces the fallback; anduse-domains: truecloses this issue without a hardcoded domain. The gate is aninstanceProfileofubuntuordebian, deliberately narrow because cloud-init only passes a v2 configuration through verbatim for the netplan renderer — handing amatchstanza to a guest rendered by sysconfig would leave it with no DHCP at all. Whether that proxy is the right one is the open question on the PR; a MAC-matched v1 configuration would cover every guest but needs the chart to pin MACs, which is a larger change.fix(vm-instance): supply cloud-init network-data to netplan guests (#…