通过 DNS 访问 libvirt+KVM 虚拟机

通过 DNS 访问 libvirt+KVM 虚拟机

我有一台运行 KVM + Libvirt 的 Ubuntu Trusty 机器来管理小型虚拟机,并使用标准 NetworkManager 连接到常规网络。

我希望能够从主机通过 DNS 访问虚拟机。

Libvirt 使用虚拟专用子网 (192.168.122.0/24),通过我的 eth0 上的网桥 (virbr0) 进行 NAT 以访问世界其他地方。Dnamasq 为该虚拟网络提供 DHCP+DNS。

这是虚拟网络的 libvirt 配置:

<network>
  <name>default</name>
  <uuid>400c59ff-c276-4154-ab73-9a8a8d1c6be3</uuid>
  <forward mode='nat'>
    <nat>
      <port start='1024' end='65535'/>
    </nat>
  </forward>
  <bridge name='virbr0' stp='on' delay='0'/>
  <mac address='52:54:00:f4:bd:37'/>
  <domain name='kvm'/>
  <dns forwardPlainNames='no'>
    <forwarder addr='127.0.1.1'/>
    <host ip='192.168.122.1'>
      <hostname>host</hostname>
      <hostname>host.kvm</hostname>
    </host>
  </dns>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.2' end='192.168.122.254'/>
    </dhcp>
  </ip>
</network>

Libvirt 启动一个 dnsmasq 实例,监听 192.168.122.1:53,它会响应所有 .knv 请求,并将其他请求转发到我的主机。此 dnsmasq 配置由 libvirt 自动生成:

/var/lib/libvirt/dnsmasq/default.conf

##WARNING:  THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
##OVERWRITTEN AND LOST.  Changes to this configuration should be made using:
##    virsh net-edit default
## or other application using the libvirt API.
##
## dnsmasq conf file created by libvirt
strict-order
user=libvirt-dnsmasq
no-resolv
server=127.0.1.1
domain=kvm
expand-hosts
domain-needed
local=//
pid-file=/var/run/libvirt/network/default.pid
except-interface=lo
bind-dynamic
interface=virbr0
dhcp-range=192.168.122.2,192.168.122.254
dhcp-no-override
dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases
dhcp-lease-max=253
dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts

NetworkManager 有一个 dnsmasq 实例,它监听 127.0.1.1:53,并使用该实例进行所有 DNS 查询,然后再将其传递给外部 DHCP 系统为我的主机分配的 DNS 服务器。

为了让我的主机 Ubuntu 系统使用 libvirt 的 dnsmasq,我指向 NetworkManager 的 dnsmasq 使用 192.168.122.1 作为域 kvm:

/etc/NetworkManager/dnsmasq.d/libvirt.conf

server=/kvm/192.168.122.1

在大多数情况下,这是有效的......

me@host ~ $ ps aufx
...cut...
root     11010  0.2  0.0 342084  6348 ?        Ssl  10:59   0:00 NetworkManager
root     11018  0.0  0.0  10232  3732 ?        S    10:59   0:00  \_ /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs.omit.d/network-manager.dhclient-eth0.pid -lf /var/lib/NetworkManager/dhclient-b8043 
nobody   11228  0.0  0.0  32252  1564 ?        S    10:59   0:00  \_ /usr/sbin/dnsmasq --no-resolv --keep-in-foreground --no-hosts --bind-interfaces --pid-file=/run/sendsigs.omit.d/network-manager.dnsmasq.pid --listen-address=127.0.1.1 --
root     11033  1.0  0.1 513356 15160 ?        Sl   10:59   0:01 /usr/sbin/libvirtd -d
libvirt+ 11085  0.0  0.0  28208   948 ?        S    10:59   0:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf

me@host ~ $ sudo netstat -nulpd | grep dnsmasq
udp  0  0  127.0.1.1:53      0.0.0.0:*  11228/dnsmasq   
udp  0  0  192.168.122.1:53  0.0.0.0:*  11085/dnsmasq   
udp  0  0  0.0.0.0:67        0.0.0.0:*  11085/dnsmasq  

me@host ~ $ host test.kvm
test.kvm has address 192.168.122.193
;; connection timed out; no servers could be reached
;; connection timed out; no servers could be reached

但会产生大量的 dnsmasq AAAA 查询,都在等待响应。

me@host ~ $ sudo netstat -nulpd | grep dnsmasq
udp  0  0  0.0.0.0:39329  0.0.0.0:*  11228/dnsmasq   
udp  0  0  0.0.0.0:2469   0.0.0.0:*  11085/dnsmasq   
udp  0  0  0.0.0.0:14805  0.0.0.0:*  11228/dnsmasq
...cut...
udp  0  0  0.0.0.0:51569  0.0.0.0:*  11228/dnsmasq   
udp  0  0  0.0.0.0:31091  0.0.0.0:*  11085/dnsmasq   
udp  0  0  0.0.0.0:39305  0.0.0.0:*  11085/dnsmasq

me@host ~ $ sudo netstat -nulpd | grep dnsmasq | wc -l
131

tcpdump 显示它们主要是 AAAA 请求:

me@host ~ $ sudo tcpdump -vni any udp port 53
tcpdump: listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
11:04:49.453864 IP (tos 0x0, ttl 64, id 56217, offset 0, flags [none], proto UDP (17), length 55)
    127.0.0.1.58535 > 127.0.1.1.53: 31275+ A? mysql.kvm. (27)
11:04:49.453948 IP (tos 0x0, ttl 64, id 20062, offset 0, flags [DF], proto UDP (17), length 55)
    192.168.122.1.7098 > 192.168.122.1.53: 41491+ A? mysql.kvm. (27)
11:04:49.454013 IP (tos 0x0, ttl 64, id 20063, offset 0, flags [DF], proto UDP (17), length 71)
    192.168.122.1.53 > 192.168.122.1.7098: 41491* 1/0/0 mysql.kvm. A 192.168.122.193 (43)
11:04:49.454068 IP (tos 0x0, ttl 64, id 37088, offset 0, flags [DF], proto UDP (17), length 71)
    127.0.1.1.53 > 127.0.0.1.58535: 31275* 1/0/0 mysql.kvm. A 192.168.122.193 (43)
11:04:49.454321 IP (tos 0x0, ttl 64, id 56218, offset 0, flags [none], proto UDP (17), length 55)
    127.0.0.1.56040 > 127.0.1.1.53: 47999+ AAAA? mysql.kvm. (27)
11:04:49.454381 IP (tos 0x0, ttl 64, id 20064, offset 0, flags [DF], proto UDP (17), length 55)
    192.168.122.1.19631 > 192.168.122.1.53: 20542+ AAAA? mysql.kvm. (27)
...cut...
11:05:09.510237 IP (tos 0x0, ttl 64, id 20515, offset 0, flags [DF], proto UDP (17), length 55)
    192.168.122.1.19631 > 192.168.122.1.53: 35761+ MX? mysql.kvm. (27)
11:05:09.510237 IP (tos 0x0, ttl 64, id 56674, offset 0, flags [DF], proto UDP (17), length 55)
    127.0.0.1.46085 > 127.0.1.1.53: 53641+ AAAA? mysql.kvm. (27)
11:05:09.510315 IP (tos 0x0, ttl 64, id 56675, offset 0, flags [DF], proto UDP (17), length 55)
    127.0.0.1.46085 > 127.0.1.1.53: 26166+ MX? mysql.kvm. (27)
11:05:09.510334 IP (tos 0x0, ttl 64, id 20516, offset 0, flags [DF], proto UDP (17), length 55)
    192.168.122.1.19631 > 192.168.122.1.53: 4247+ AAAA? mysql.kvm. (27)
11:05:09.510407 IP (tos 0x0, ttl 64, id 56676, offset 0, flags [DF], proto UDP (17), length 55)
    127.0.0.1.46085 > 127.0.1.1.53: 49331+ AAAA? mysql.kvm. (27)
11:05:09.510433 IP (tos 0x0, ttl 64, id 20517, offset 0, flags [DF], proto UDP (17), length 55)
    192.168.122.1.19631 > 192.168.122.1.53: 63294+ MX? mysql.kvm. (27)
^C
934 packets captured
1857 packets received by filter
0 packets dropped by kernel

我尝试降低 AAAA 记录的优先级/etc/gai.conf

precedence ::ffff:0:0/96  100

甚至尝试完全禁用 IPv6/etc/sysctl.conf

# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1

但 AAAA 请求仍在发送,名称解析变得难以忍受的慢。

有没有办法让 libvirt 或 NetworkManager 忽略或对这些请求做出负面回应,这样我就不必等待所有请求超时后再使用已经收到的 A 记录?

答案1

如果配置了转发器,dnsmasq 将转发所有没有明确数据的 DNS 查询。这包括没有活动租约的已配置静态 DHCP 客户端的记录、除非明确定义了 IPv6 地址的 AAAA 记录等等。

有几种方法可以避免这种情况:

不配置转发器

只需在网络定义中省略转发器条目即可。除非虚拟网络是真的隔离。据我所知,这是 libvirt 目前支持的唯一可能性(2014 年 12 月)。

dnsmasq.conf 中的本地域

在 dnsmasq 中将域配置为“本地”:

 domain=local.net,192.168.10.0/24
 local=/local.net/
 local=/10.168.192.in-addr.arpa/

理论上,这可以缩写为domain=local.net,192.168.10.0/24,local,但是dnsmasq 的一个错误最近才被修复导致失败。

libvirt 不支持此功能。为了使用此配置,您需要在操作系统中手动设置桥接并配置 libvirt 网络,如下所示:

   <network>
     <name>local</name>
     <forward mode='bridge'/>
     <bridge name='br0'/>
   </network>

您根本不必在此配置中创建 libvirt 虚拟网络,只需<interface 'type=bridge'>在您的 VM 定义文件中使用即可。

dnsmasq.conf 中的身份验证区域

auth-zone参数的作用与 类似local。但是它还有其他含义,我并不完全理解。我认为如果虚拟网络中的名称应该从外部解析,则此配置是理想的。

domain=local.net
auth-zone=local.net

libvirt 也不支持此设置,因此必须采用与上述相同的步骤来设置桥接器。

答案2

我认为这样的 DNS 设置有两个积极的目标:

  • 在主机(和客户机)上解析name.vm应该返回名为name
  • example.com在客户机(和主机)上解析应该返回 example.com 的 IP 地址

直接将主机解析器指向客户网络的 dnsmasq 实例作为转发器,并将客户网络的 dnsmasq 指向主机解析器,将导致未知主机来回反弹并挂起。

这表明需要避免两个问题:

  • 解析不存在的虚拟机(或存在的仅 IPv4 虚拟机的 AAAA 记录)应立即返回 NXDOMAIN/nodata
  • 解析不存在的主机名 (nonexistent.example.com) 也应立即返回 NXDOMAIN/nodata

完全解析name.vm是通过输入<domain name='vm' localOnly='yes'/>libvirtd 网络配置(virsh net-edit default或其他配置)来实现的。我相信name='vm'(a) 搜索域通过 DHCP 分发,并且 (b) 附加到 DHCP 主机名以生成用于dnsmasq将虚拟机放入 DNS 的 FQDN。设置localOnly意味着如果名称未出现在本地区域中,dnsmasq则不会询问上游服务器——这是防止dig name.vm AAAA循环的关键设置。

name.vm从主机进行解析取决于主机的解析器配置。如果使用systemd-resolved,请尝试resolvectl dns virbr0 192.168.122.1; resolvectl domain virbr0 vm; resolvectl default-route virbr0 no'。第一部分告诉systemd-resolved使用来宾网络的 dnsmasq 作为解析器。第二部分表示与该接口关联的搜索域是vm。仅使用这些部分,主机就可以成功解析name.vmexample.com。但是,第三部分告诉systemd-resolved仅使用来宾网络 DNS 服务器来配置域 - 如果没有这个,查找nonexistent.example.com将在主机和来宾网络 DNS 解析器之间循环。

(FWIW,另一种实现name.vm~resolve 的方法是使用 libvirt NSS 模块https://libvirt.org/nss.html,尽管它并不使用 DNS 而且我还没有尝试过。)

相关内容