前言
前几个星期,社区通过了一个 Patch 来解决一个遗留很久的 DHCP 相关的问题,这个 Patch 并不复杂(review 地址是 https://review.openstack.org/#/c/185486/),但为什么这么做却有一点故事,拿出和大家聊一聊。
事情从 DHCP HA 说起。我们知道 Neutron 的架构是每个“网络”(Network),都会有一个相应的 namespace 名为 qdhco-XXX(XXX 为网络的 UUID)运行在网络节点上。这个网络命名空间内会有一个 tuntap 设备,名为 tapXXX(XXX 为 port 的 ID),然后有一个 dnsmasq 进程与之绑定。
这样,每当新的虚拟机启动,自动广播 DHCP 请求,namespace 下的 dnsmasq 将收到这个广播包并根据 dhcp-hostfile 里的内容对 VM 给予回应,分配并确认其 IP 地址。这一切都挺好的,先不考虑网络规模很大时 dnsmasq 的承载能力, 不考虑将 dhcp 分布式时,it works。
DHCP HA 但如果我们想尝试将 DHCP 做 HA 呢?考虑到如果这个 dnsmasq 进程挂掉了,或者这个网络节点上的 ovs 异常了,甚至网络节点挂掉了…… 嗯,让我们先从 DHCP 的流程说起。
IPv4 版的 DHCP 是按照 RFC 2131 Dynamic Host Configuration Protocol 标准的,先撇开其具体细节,大概的 DHCP 流程是这样的:
首先在 DHCP 发现阶段,客户端发送目的地址为 255.255.255.255 的 UDP 广播包,然后服务端回复 DHCP OFFER,包含分配的 IP、默认路由、租约有效期等等。为了避免同一个网络下有多个 DHCP 服务端,而客户端只接受一个 DHCP 结果,所以客户端需要再发广播通知所有 DHCP 服务端自己接受了哪个 DHCP 分配结果,这个包里含有它自己给自己确定的 IP 地址,这个 IP 地址来自于哪个 DHCP 服务端(服务端的 IP 地址)。最后服务端广播一条 ACK 作为结束。
看这个过程,我们不难发现“先民”已经考虑了一个三层网络下有多个 DHCP Server 的情况,那我们做 HA 应该很好做了,只要在一个网络下起两个一摸一样的 DHCP Server,两个 Server 对 Client 争抢,谁抢答成功 Client 将会把谁的地址填充到 DHCP Request 中,那么另一个没抢到的 DHCP Server 收到不是自己的 DHCP Request 自然就放弃了。因为 dnsmasq 的地址分配依赖于 dhcp-hostfile,而这个文件又依赖于 DB(Neutron)的控制,所以最终的状态必然是一致的,
OK,到现在为止皆大欢喜,只需要简单的多启一个 agent 就能解决企业级应用和生产系统常常头痛的 HA 问题,是不是故事就这么结束了呢?
问题
很快我们就发现了一个问题,就是续租。
这个问题怎么产生的呢?我们知道 DHCP 只是一个租约,也就是说它是有有效期的,那么客户端需要定时发送续约的申请,否则按照协议,它将自己解除自己的 IP 地址。既然是个租约,就存在状态(客户端 IP、MAC、租约有效期、上次续约时间……),那么这些状态对于服务端来说存在在哪里?返回头看我们截下来的 shell 界面,里面有一个参数是 “–leasefile-ro”,这个参数是什么意思呢?就是说租约信息不会写入文件,官方建议是如果你不需要我来帮你做状态的存储,那么你可以通过 dhcp-script 来用一个外部存储维护。可是 Neutron 默认可不是这么用的,因为如果有文件存在在硬盘的话,维护起来需要成本,如果我们想做迁移更是还要考虑这些本地状态,所以 Neutron 一直都使用了 leasefile-ro 参数。
但是这样的话如果一旦 dnsmasq 重启了,或者该 dnsmasq 挂掉了呢?结果将是一个没有该租约信息的 DHCP Server 或者一个 request 内服务器地址不吻合的 DHCP Server 将需要回复这个续约请求,不出意料的,这次续约请求将会以失败告终,如果 Client 的实现并没有在续约失败后重新做 DHCP 发现,那么这个客户端将丢掉自己的 IP。
怎么解决呢?首先 dnsmasq 一般是不怎么出问题的,进程重启往往是因为 agent 重启而导致的,所以我们首先可以尝试让 DHCP agent 重启时不要重启 dnsmasq 进程。然而这样只是治标不治本啊,还好 dnsmasq 还留下了一个选项叫“dhcp-authoritative”。
官方的 man 文档是这么介绍这个参数的:
-K, –dhcp-authoritative Should be set when dnsmasq is definitely the only DHCP server on a network. For DHCPv4, it changes the behaviour from strict RFC compliance so that DHCP requests on unknown leases from unknown hosts are not ignored. This allows new hosts to get a lease without a tedious timeout under all circumstances. It also allows dnsmasq to rebuild its lease database with-out each client needing to reacquire a lease, if the database is lost. For DHCPv6 it sets the priority in replies to 255 (the maximum) instead of 0 (the minimum). 我们重点看黑体的那部分,这参数将允许租约信息不存在时重建这个信息!如果是这样,那我们确实就不怕一个 dnsmasq 进程重启活着挂掉了!真是完美的 active-active 模式啊,为了避免文档描述不准确,我们查 dnsmasq 的代码看下。
DHCPv4 的实现在 src/rfc2131.c 里,看里面的代码,整个逻辑都在 dhcp_reply 这一个函数里,先到 case DHCPREQUEST 里,再判断是不是续约,在注释里表达了文档一样的意思,在代码里我们可以看到如果没打开 OPT_AUTHORITATIVE (dhcp-authoritative)则回复 NAK,如果打开了呢,经过一系列繁琐的逻辑检查最终 dnsmasq 会回复 ACK。
新的问题
嗯,到这里是不是圆满了呢?为了完整的了解这个选项的意义,避免副作用,我们不妨在这个逻辑里继续搜索 OPT_AUTHORITATIVE,看看它还会带来什么,比如这个:
这里注释说,如果是在 auth mode,那么“should be faulted”,否则忽略。什么意思呢?代码告诉我们,如果没打开 OPT_AUTHORITATIVE,那么直接 return,什么都不干,否则 meassage 为“wrong server-ID”,这最终将导致发送一条 DHCPNAK。
这是代码里的逻辑,实际上会有什么影响吗?假设我们的两个 DHCP Server 全部开启了 dhcp-authoritative,那么 DHCP 发现时,两个 Server 都回复 Offer,但客户端只会选择一个发送 Request,两个 Server 都收到了 Request,正确的(被选中的) Server 会发送一条 ACK,而错误的(未被选中的) Server 过去会忽略,现在它将生命自己的存在感,发送一条 NAK……
这…… 不会酿成什么问题吧……
事实恰好相反,我们已经确认一部分版本的 dhcpd 和 dhclient 均有可能不恰当地处理 DHCPNAK,将导致 Client 反复释放并重新获取 IP,Cirros 和部分 RH 系的系统可能受到影响。
最终解决
下面再返回到这个 Patch 上,如果说我们不能再用 dhcp-authoritative 来解决 dnsmasq 重启的问题,那该怎么办?社区最终选择的方案是返回来继续用 leasefile,那么如果迁移或者原 dnsmasq 挂掉呢?那么 agent 会生成一个假的 leasefile 来骗过 dnsmasq。
最终的解决花了很长时间,而且方法还是有些 hack,更好的方案还是要期待各位的思路啦!