并非如此动态的更新
通常,当网络在我的控制之下时,我喜欢我的服务器拥有静态 IP 地址。无论是真正的静态 IP(硬编码到主机上的网络配置文件中),还是我配置 DHCP 服务器进行静态分配,当您知道服务器始终拥有相同的 IP 时,都会方便得多。不幸的是,在默认的 Amazon EC2 环境中,您无法控制您的 IP 地址。当您在 EC2 中启动服务器时,Amazon 的 DHCP 服务器会分配一个有些随机的 IP 地址。即使在重启后,服务器也会保持该 IP 地址,只要主机不关闭。如果您停止机器,下次启动时,它很可能会在不同的硬件上,并且会有一个新的 IP。
dhclient 覆盖为了应对这种不可预测的 IP 地址情况,多年来,我严重依赖于 EC2 环境中的动态 DNS 更新。当主机首次启动并配置,或任何时候 IP 发生变化时,主机都会使用新的 IP 更新内部 DNS 服务器。总的来说,这种方法对我来说效果很好,但它有一个复杂之处。如果我控制了 DHCP 服务器,我会使用我的 DNS 服务器的 IP 地址来配置它。由于 Amazon 控制 DHCP,我必须配置我的主机来覆盖它们从 DHCP 获取的 DNS 服务器,使用我自己的 DNS 服务器。我使用 ISC DHCP 客户端,这意味着需要在基于 Debian 的系统上的 /etc/dhcp/dhclient.conf 文件中添加三行
supersede domain-name "example.com";
supersede domain-search "dev.example.com", "example.com";
supersede domain-name-servers 10.34.56.78, 10.34.56.79;
有了这些选项,一旦网络重启(或机器重启),这些设置将最终出现在我的 /etc/resolv.conf 中
domain example.com
search dev.example.com. example.com
nameserver 10.34.56.78
nameserver 10.34.56.79
p我甚至更进一步,在 /etc/dhcp/dhclient-exit-hooks.d/ 下添加了一个 bash 脚本,它会在我获得新租约后触发。为了容错,我有多个 puppetmaster,如果您对 puppet 主机名执行 DNS 查询,您将获得多个 IP。这些退出钩子脚本执行 DNS 查询以尝试识别离它最近的 puppetmaster,并在 resolv.conf 中添加一个鲜为人知的设置,称为 sortlist。sortlist 设置告诉您的解析器,当查询返回多个 IP 时,优先选择此行中的特定 IP 或子网。例如,如果我想使用的 puppetmaster 的 IP 是 10.72.52.100,我会将以下行添加到我的 resolv.conf
sortlist 10.72.52.100/255.255.255.255
下次我查询返回多个 A 记录的主机名时,它总是会优先选择这个 IP,即使它返回多个 IP。如果您使用 ping,您可以测试这一点,并看到它始终 ping 您在 sortlist 中指定的主机,即使 dig 或 nslookup 返回的多个 IP 顺序随机。如果第一个主机宕机,如果您的客户端正确支持多个 A 记录,它将故障转移到列表中的下一个主机。
dhclient 并非如此动态这种在 EC2 这样动态的环境中整理秩序的方法总体上对我来说效果很好。尽管如此,它并非没有一些复杂性。这种系统的主要挑战是我的 DNS 服务器本身的 IP 可能会发生变化。您可能会说没问题。由于我使用配置管理系统控制我的 dhclient.conf,我可以只推送新的 dhclient.conf。这种方法的唯一问题是,我一直未能找到 dhclient 提供任何在不重启 dhclient 本身(这意味着重启网络)的情况下重新加载 dhclient.conf 配置文件的任何方法。你看,如果您控制了 DHCP 服务器,您可以更新 DHCP 服务器的 DNS 设置,当客户端请求下一个租约时,它会推送给客户端。在我的情况下,DNS 服务器 IP 更改意味着在整个环境中产生网络闪断。
我通过惨痛的教训发现了这个需求。我重新启动了一台 DNS 服务器,并将新的 IP 推送到我所有服务器上的 dhclient.conf。作为一种双重保险的方法,我还确保我的配置管理系统更新了 /etc/resolv.conf 文件,以显示新的 IP。更改推送出去,一切看起来都很棒,所以我关闭了 DNS 服务器。在那之后不久,灾难降临了。
我开始注意到一台主机的内部健康检查超时;主机变得缓慢且无响应,并且在我对 resolv.conf 的更改应该已经到达主机很久之后,它似乎又在更新该文件。当我检查故障系统上的 resolv.conf 时,我注意到它配置了旧的 IP 方案,即使包含该信息的 DNS 服务器早已消失。我最终意识到,即使我更新了 dhclient.conf,dhclient 脚本本身也从未获取这些更改,因此几个小时后,当它续订租约时,它用它配置的旧 DNS IP 覆盖了 resolv.conf!
响彻世界的闪断我意识到,基本上这个环境中的每台主机都将在接下来的几个小时内续订其租约,因此需要在每台主机上重启网络以接受新的 dhclient.conf。我的团队争先恐后地分批在服务器组上进行更改。真正的问题不是 dhclient 更改需要网络闪断,而是网络闪断更像是持续几秒钟的中断。我们的数据库集群不喜欢网络被移除。至少,它们(理所当然地)将其视为主机上的故障,并立即触发数据库集群的故障转移和恢复。主机重启网络似乎花费的时间比它们应该花费的时间要长得多,触发了集群故障转移,并且在某些情况下,需要一些手动干预才能解决问题。
幸运的是,这个问题只影响了开发环境,但我们也需要在生产环境中重新启动 DNS 服务器,并且绝对无法在那里处理这种中断。我开始研究这个问题,在确认没有办法在不重启网络的情况下更新 dhclient.conf 后,我转而研究为什么重启网络需要这么长时间。我的 dhclient-exit-hook 脚本是罪魁祸首。在脚本内部,我休眠了五秒钟以确保网络已启动,然后执行了 dig 请求。这意味着在重启网络时,为旧 IP 配置的 DNS 查询将超时,并导致主机在网络启动之前暂停。我的解决方法是将 sleep 和 dig 查询替换为一个包含简单 echo 的模板,以将我的 sortlist 条目附加到 resolv.conf。我的配置管理系统将自行执行 DNS 查询并更新模板。有了新的、更快的脚本,我看到我的网络重启几乎没有造成任何网络闪断。一旦我部署了新的退出钩子,我就能够在任何主机上重启网络而不会产生任何不良影响。最终的证明是,我在生产环境中推送 DNS 服务器 IP 更改时没有任何问题。