最近照博文在 VirtualBox 中部署 OpenStack一步一步部署了一个 OpenStack 环境。部署规划可参照博客搭建 OpenStack 实验环境。其中值得一提的是网络规划图:
图片来源:搭建 OpenStack 实验环境。在实际部署中,IP 跟该博客不一样。
在使用 OpenStack 过程中,遇到一个计算节点的虚拟机无法获得内网 IP 的问题(控制节点的没问题)。下文将会详述问题及解决问题的过程。
网络配置
照博文在 ML2 中配置 Vlan Network将控制节点和计算节点的 ML2 配置的以下三个字段都配置如下:
1 | # /etc/neutron/plugins/ml2/ml2_conf.ini |
然后在控制节点重启 q-svc 及 q-agt 服务,在计算节点重启 q-agt 服务。
网络拓扑
参照博文创建第一个 vlan network “vlan100”及将 instance 连接到 vlan100,在本地创建了一个 Vlan 网络(包括子网)。此时总的网络拓扑如下:
在上图中,需要特别注意的是,控制节点和计算节点的 Linux Bridge 不是同一个,只是它们名字相同,所在 Vlan ID 也相同而已;它们之间的通信要通过 VirtualBox 的 Internal Network (或物理交换机)来进行。
下边解释一下这个控制节点网络拓扑。计算节点网络的重点已经包含在前者,就不再重复解释了。
控制节点网络
注意:在这里,控制节点也是网络节点。
VirtualBox Internal Network
这个网络是 VirtualBox 提供的一种网络,允许同一个宿主机(这里就是个人的物理机了)上的在同一个 Iternal Network 之间的虚拟机互相通信,可参考官方文档:
Internal Networking is similar to bridged networking in that the VM can directly communicate with the outside world. However,
the "outside world" is limited to other VMs on the same host which connect to the same internal network.
)
网络信息
Neutron 根据我们的操作在后台(或者说数据库
)创建了网络信息如下(已去掉一些不相关的):
1 | stack@ubuntu-controller:~$ neutron net-list -F "id" -F "name" -F "subnets" |
需要特别注意的是,这些只是信息,只是维护在数据中,并不能真正起到网络功能。真正能起到网络功能的是 agent(这里是 Linux Bridge)。
网桥(Linux Bridge)
在新建网络(包括子网)后,后台会创建一个网桥(命名为brq+网络ID前11个字符
)。该网桥挂接了两个设备,一个为 vlan interface eth1.100(命名为 ethX.Y,其中X为interface的序号,Y为vlan id
),一个与 dhcp 相对应的 tap 设备 tap522af231-5c(命名为tag+PortID前11个字符
),如下所示:
1 | stack@ubuntu-controller:~$ brctl show |
关于 tap 设备 tap522af231-5c 我们后文还会提到。
dnsmasq
这部分参考了如下博文:
另外一个根据我们新建子网时选择的参数(DHCP Enabled 为 Yes)而新建的是 dnsmasq 进程及其他信息。一个网络对应一个 dnsmasq(不管有多少个子网)。详细见下文。
dnsmasq 进程
用 ps 查看一下进程:
1 | stack@ubuntu-controller:~$ ps -elf | grep dnsmasq | grep bd9e86e6-43e3-42ed-92d3-5e6c6647eefb |
从以上信息我们可以看出该进程的数据文件都存放在 /opt/stack/data/neutron/dhcp/bd9e86e6-43e3-42ed-92d3-5e6c6647eefb 目录下:
1 | stack@ubuntu-controller:~$ ls /opt/stack/data/neutron/dhcp/bd9e86e6-43e3-42ed-92d3-5e6c6647eefb |
其中,有两个文件比较重要,一是 host 文件,存储的是 host 的 IP 与 MAC 的对应关系(!!!信息来源于 Neutron 数据库)
;另一个是 interface 文件,存储的是 dnsmasq 提供 DHCP 服务的 interface
:
1 | stack@ubuntu-controller:~$ cat /opt/stack/data/neutron/dhcp/bd9e86e6-43e3-42ed-92d3-5e6c6647eefb/host |
dnsmasq network namespace
我们知道租户可以新建多个网络,这些网络的子网可能会存在重叠。如果无法通过一定办法将 dnsmasq(及路由,后续博文再叙)指定为只为某个网络服务,租户将无法创建重叠的网络。具体地,Neutron 使用了 network namespace 来将 dnsmasq 隔离为只对某个网络服务。每个 dnsmasq 都位于独立的 namespace,命名为 qdhcp+network_id
,如网络 vlan100 的 namespace 就为:
1 | stack@ubuntu-controller:~$ ip netns |
我们可以查看下该 namespace 里的 interface(注意跟 dnsmasq 进程的 interface 文件对应起来):
1 | stack@ubuntu-controller:~$ sudo ip netns exec qdhcp-bd9e86e6-43e3-42ed-92d3-5e6c6647eefb ip a |
注意:ns-522af231-5c 是一个虚拟网卡,dnsmasq 是一个进程,后者只是使用了前者而已。
veth pair
宿主机(这里就是 VirtualBox 上的虚拟机了)本身也有一个 namespace,叫 root namespace,拥有所有物理和虚拟 interface device。也就是说,前文中新建的网桥 brqbd9e86e6-43 其实在 root namespace 中。
而我们知道,不同的 namespace 之间是不能够互相通信的。那 dnsmasq 又是如何实现与该网桥的通信的?就是用 veth pair。
我们前文提到该网桥上有一个 tap 设备 tap522af231-5c。就是该 tap 设备与 dnsmasq 所在的 namespace 中的 ns-522af231-5c 构成了一对 veth pair。下边我们可以简单验证一下。
dnsmasq namespace:
1 | stack@ubuntu-controller:~$ sudo ip netns exec qdhcp-bd9e86e6-43e3-42ed-92d3-5e6c6647eefb ip -d link |
宿主机:
1 | stack@ubuntu-controller:~$ ip -d link |
注:眼尖的你应该发现该 tap 设备后边加了一个 @eth0。感觉不对啊,这个 tap 设备的 veth peer 明明是 ns-522af231-5c;而且该 tap 设备挂接的网桥是连接到网卡 eth1 而不是 eth0…后来通过观察额外几个类似的案例,可以判定,现在所显示的 @eth0 是不对的,我们应该看 peer_ifindex,这才是真正正确的,不过前提是我们要确定它的 peer 所在的 namespace。或许,在 root namespace 和 dnsmasq namespace 中,eth0 和 ns-522af231-5c 的 ifindex 都为 2,所以才导致显示出错。
DHCP 工作原理
在进行真正的问题描述及分析之前,我们先来看一下 DHCP 的工作原理。
协议
鸟哥的 DHCP 运作的原理 讲的好好,特摘录如下。
客户端取得 IP 参数的程序可以简化如下:
1.客户端:利用广播封包发送搜索 DHCP 服务器的封包
若客户端网络设定使用 DHCP 协议取得 IP (在 Windows 内为‘自动取得 IP’),则当客户端开机或者是重新启动网络卡时, 客户端主机会发送出搜寻 DHCP 服务器的 UDP 封包给所有物理网段内的计算机。此封包的目标 IP 会是 255.255.255.255, 所以一般主机接收到这个封包后会直接予以丢弃,但若局域网络内有 DHCP 服务器时,则会开始进行后续行为。
2.服务器端:提供客户端网络相关的租约以供选择
DHCP 服务器在接收到这个客户端的要求后,会针对这个客户端的硬件地址 (MAC) 与本身的设定数据来进行下列工作:
- 到服务器的登录文件中寻找该用户之前是否曾经用过某个 IP,若有且该 IP 目前无人使用,则提供此 IP 给客户端;
- 若配置文件针对该 MAC 提供额外的固定 IP (static IP) 时,则提供该固定 IP 给客户端;
- 若不符合上述两个条件,则随机取用目前没有被使用的 IP 参数给客户端,并记录下来。
总之,服务器端会针对客户端的要求提供一组网络参数租约给客户端选择,由于此时客户端尚未有 IP,因此服务器端响应的封包信息中, 主要是针对客户端的 MAC 来给予回应。此时服务器端会保留这个租约然后开始等待客户端的回应。
3.客户端:决定选择的 DHCP 服务器提供的网络参数租约并回报服务器
由于局域网络内可能并非仅有一部 DHCP 服务器,但客户端仅能接受一组网络参数的租约。 因此客户端必需要选择是否要认可该服务器提供的相关网络参数的租约。当决定好使用此服务器的网络参数租约后, 客户端便开始使用这组网络参数来设定自己的网络环境。此外,客户端也会发送一个广播封包给所有物理网段内的主机,告知已经接受该服务器的租约。
此时若有第二台以上的 DHCP 服务器,则这些没有被接受的服务器会收回该 IP 租约。至于被接受的 DHCP 服务器会继续进行底下的动作。
4.服务器端:记录该次租约行为并回报客户端已确认的响应封包信息
当服务器端收到客户端的确认选择后,服务器会回传确认的响应封包,并且告知客户端这个网络参数租约的期限, 并且开始租约计时喔!那么该次租约何时会到期而被解约 (真可怕的字眼) ?你可以这样想:
客户端脱机:不论是关闭网络接口 (ifdown)、重新启动 (reboot)、关机 (shutdown) 等行为,皆算是脱机状态,这个时候 Server 端就会将该 IP 回收,并放到 Server 自己的备用区中,等待未来的使用;
客户端租约到期:前面提到 DHCP server 端发放的 IP 有使用的期限,客户端使用这个 IP 到达期限规定的时间,而且没有重新提出 DHCP 的申请时,就需要将 IP 缴回去!这个时候就会造成断线。但用户也可以再向 DHCP 服务器要求再次分配 IP 啰。
虚拟机获取 DHCP IP 过程
以下部分修改自博文获取 DHCP IP 过程分析(名称替换)。
instance 获取 IP 的过程如下:
- vlan100-vm01 开机启动,发出 DHCPDISCOVER 广播,该广播消息在整个 vlan100 网络中都可以被收到。
- 广播到达 veth tap522af231-5c,然后传送给 veth pair 的另一端 ns-522af231-5c。dnsmasq 在它上面监听,dnsmasq 检查其 host 文件,如发现有对应项,于是 dnsmasq 以 DHCPOFFER 消息将 IP(172.16.100.7)、子网掩码(255.255.255.0)、地址租用期限等信息发送给 vlan100-vm01。
- vlan100-vm01 发送 DHCPREQUEST 消息确认接受此 DHCPOFFER。
- dnsmasq 发送确认消息 DHCPACK,整个过程结束。
问题及解决
在 Horizon 新建一个虚拟机,该虚拟机被调度到了计算节点。但奇怪的是该虚拟机一直拿不到内网 IP。不过如果虚拟机被调度到控制节点,就没有这个问题。所以初步怀疑是控制节点和计算节点之间的 DHCP 通信存在问题。
下边我们来看一下问题。
描述
在 Horizon 上新建了一个虚拟机,最后可以看在页面上看到该虚拟机的状态:
但奇怪的是,进入虚拟机 shell 的时候并没有看到被绑定的内网 IP:
而且,在虚拟机详情页面中的 Log 页面还可以看到获取不到 DHCP IP 的错误:
1 | udhcpc (v1.20.1) started |
这时我们再看一下 dnsmasq 的数据文件 leases,确实也没有被租借出去的 IP(172.16.100.7):
1 | stack@ubuntu-controller:~$ cat /opt/stack/data/neutron/dhcp/bd9e86e6-43e3-42ed-92d3-5e6c6647eefb/leases |
抓包及分析
暂时在日志中找不到确切的原因(没有报错),只好上抓包工具了。
计算节点抓包
对计算节点上的 vlan interface eth1.100 抓包:
1 | stack@ubuntu-compute:~$ sudo tcpdump -i eth1.100 -s 1500 -w compute.pcap 'udp and port 67 and port 68' |
用 Wireshark 分析可得到如下结果:
只抓到 3 个包,跟虚拟机的 Console Log (Sending discover…) 还是一致的。
dnsmasq namespace 内抓包
对控制节点上的直接对 ns-522af231-5c 网卡抓包:
1 | stack@ubuntu-controller:~$ sudo ip netns exec qdhcp-bd9e86e6-43e3-42ed-92d3-5e6c6647eefb tcpdump -i ns-522af231-5c -s 1500 -w dnsmasq.pcap 'udp and port 67 and port 68' |
用 Wireshark 分析可得到如下结果:
到这里就很明显了,dnsmasq 是已经收到请求 DHCP IP 的请求,也已经发送 offer 包给该虚拟机,但就是没法发送到计算节点上(因为根据抓包,计算节点只有发出的包,没有收到包)。
疑惑
通过上边抓包,很奇怪的一点就是,计算节点可以发包给控制节点,但控制节点却是不可以发包给计算节点?
这个原因在于,计算节点发出的包是广播包(Discover):
而控制节点发出的是单播包(Offer):
到这里,我们应该比较明确了,网卡 eth1 不允许发送具体 Vlan 的数据。也就是说,eth1 在这里相当于交换机中的 access 口而不是 trunk 口。
解决
找到原因,解决就要简单多了。不过,中途还是遇到了错误的解决方法。
在说明解决方法之前,我们先来看一个知识点。
混杂模式(Promiscuous mode)
维基百科对混杂模式有一个很简明扼要的介绍:
混杂模式(英语:promiscuous mode)是电脑网络中的术语。是指一台机器的网卡能够接收所有经过它的数据流,而不论其目的地址是否是它。一般计算机网卡都工作在非混杂模式下,此时网卡只接受来自网络端口的目的地址指向自己的数据。
也就是说,我们可以利用该模式来解决我们的问题。
错误的解决方法
在控制和计算节点上将 eth1 都设为混杂模式:
1 | stack@ubuntu-x:~$ ip link set eth1 promisc on |
其实这种方法在本文案例中行不通,因为控制和计算节点的 eth1 并不是通过线缆直接相连的,而是通过 VirtualBox Internal Network 相连,所以我们不能在这两个节点设定,而是要在该 Network 设置。
正确的解决方法
如果我们看前文中对 VirtualBox Internal Network 的介绍,就会发现,其实该 Network 就是一个虚拟交换机,它跟控制和计算节点相连的两个口就相当于交换机的两个端口。我们只要把这个两个“端口”设为 trunk 模式就行了。
将控制和计算节点两个 VirtualBox 虚拟机的 eth1 网卡都如下设置(混杂模式):
不过,在我的个人环境中,重启 q-svc、q-agent 均不能使得以上配置生效,需要重新 unstack 然后 stack。
含虚拟机网络拓扑图
最后,在原拓扑图上加上虚拟机,效果如下: