使用 OpenVPN 构建多源基础设施
您是否曾经需要在多个提供商处扩展您的托管服务器,并允许应用程序像在同一 LAN 上一样通信,可能需要跨越多组防火墙和 NAT 层?或者,也许您想从一个托管服务迁移到另一个,以利用更低的价格或更好的正常运行时间,但更希望逐步完成而不是一次性完成(以及一个周末的维护窗口)?或者,也许您已经考虑过使用 Amazon EC2 云来托管部分而非全部基础设施?如果您对上述任何一个问题的回答是肯定的,那么您想要的本质上就是一个多源基础设施。
Amazon EC2
Amazon EC2(弹性计算云)是一种 Web 服务,允许用户在几分钟内在 Amazon 托管的虚拟化基础设施中配置新机器,使用公开可用的 API。用户获得完全 root 访问权限,并且可以在其 Amazon 机器映像中安装几乎任何操作系统或应用程序。Web 服务 API 允许用户远程重启其实例,并在必要时通过添加数十甚至数百台机器来快速扩展容量。此外,没有预先的硬件设置成本——Amazon 只对您实际使用的容量收费;没有最低费用。随着越来越多的应用程序进入 Amazon 的虚拟计算环境,系统管理员正在寻找方法,以便在 Amazon EC2 中的新机器和其常规数据中心中的旧机器之间,通过公共互联网提供安全连接。本文介绍了一种这样的技术——如何基于 OpenVPN 构建多源基础设施。
让我们来看一个简单的分布式应用程序,它由多个服务组成,即 LAMP 堆栈。传统上,您会从单台服务器上的 Apache 和 MySQL 开始。随着您的站点增长,您将从您的提供商处配置另一台服务器,并添加第二个 Apache 实例。稍后,您可能希望配置另一台机器作为专用数据库服务器以提高性能。这是一个典型的单源基础设施——所有服务都在由单个提供商控制和支持的单个物理环境中运行。
相比之下,使用多源基础设施,您不再局限于一个提供商或一个数据中心。您可以自由地混合和匹配来自不同提供商的托管计划,以更好地适应您的业务和架构,并且您可以使用任意数量的提供商。您的应用程序仍然可以相互通信,但不再是物理 LAN,而是一个位于公共互联网链路之上的虚拟 LAN。您可以水平扩展您的服务,并同时实现更好的地理冗余和容错能力,所有这些都无需对您的应用程序进行重大更改。如果它在单源物理 LAN 中有效,那么它很可能在多源虚拟 LAN 中也有效。
此外,您可以利用特定提供商的优势,仅用于您服务的一个子集。回到 LAMP 堆栈作为我们的示例,使用 Amazon EC2,您可以快速配置许多 Apache 实例以响应当前负载;尽管您可能更喜欢在其他地方的裸机上运行 MySQL,而不是在 EC2 虚拟机中运行。
最后,这种方法允许您将您的企业基础设施扩展到当前数据中心之外,或者允许外部服务使用您企业数据中心中的应用程序。考虑一个按小时租用的远程托管数据处理集群,它使用您的企业数据仓库系统作为其输入。正如您所看到的,多源基础设施更加灵活,可以适应各种场景和需求。
在本文中,我描述了我们在 CohesiveFT (www.cohesiveft.com) 使用 OpenVPN 开发的多源基础设施概念的一个特定实现,该实现自 2007 年仲夏以来一直在我们的生产环境中运行。我们选择 OpenVPN 主要是因为它使用标准的 OpenSSL 加密,可以在多种操作系统上运行,并且不需要内核补丁或额外的模块。后一个优点至关重要。许多虚拟专用服务器 (VPS) 托管解决方案目前提供出色的服务,其定价通常优于其他形式的托管。这些提供商构建专门为其环境和虚拟化方法量身定制的访客操作系统内核。因此,您可能希望尽可能避免在您的 VPS 上重建 Linux 内核。并非不能这样做,但是如果您不这样做,您可以节省一些时间,并且可能会获得更快的技术支持。
在 OpenVPN 的替代方案中,有 Openswan,它是原始 FreeS/WAN 项目的代码分支,但根据其维基 (wiki.openswan.org/index.php/Openswan/Install) 所示,它需要内核补丁来支持 NAT 遍历。
OpenVPN 协议也对防火墙友好,因为它可以将所有流量通过单个 UDP 隧道(默认端口为 1194)。此功能与 SSL 加密相结合,使得当数据包通过公共互联网时,此解决方案非常难以攻击。
事实证明,OpenVPN 是一个绝佳的选择,它为我们提供了我们期望的所有功能,除了一个非常重要的功能,即容错能力。当您使用 VPN 为远程用户提供企业网络访问时,解决方案非常简单——您部署多个 OpenVPN 服务器,并将每个服务器配置为具有自己的网段(例如,服务器 10.5.0.0 255.255.0.0和服务器 10.6.0.0 255.255.0.0)。在典型场景中,分配给远程用户的动态 IP 地址并不重要,只要您配置防火墙、应用程序和服务以允许两个子网即可。
但是,当您构建多源基础设施时,除非您希望服务器不时更改其 IP 地址,否则这不是一个可接受的解决方案。为了满足冗余和容错要求,我们需要一对主动-主动的 OpenVPN 服务器来共享一个公共地址空间——所有主机都必须始终能够通过静态 IP 地址相互访问,无论哪个 OpenVPN 服务器在通信的任一端提供连接。然后,如果我们丢失一个 OpenVPN 服务器,另一个服务器将提供所有连接。并且,如果它们都启动,则两者都将接受来自客户端的连接以分担负载。此功能在 OpenVPN 源代码分发中不可用,因此我们开发了一个独立的动态路由守护程序,以促进主动-主动负载均衡。您可以在 www.cohesiveft.com/multisourced-infra 找到其源代码,以及有用的链接、用例场景和邮件列表。
您需要两台机器以服务器模式运行 OpenVPN 守护程序(我们将其称为 vpnsrvA 和 vpnsrvB,并假设它们在您网络中的物理 IP 地址分别为 192.168.7.1 和 192.168.17.1)和两个新的私有子网:数据(例如,10.100.100.0/24)和管理 (10.200.200.0/24)。您的所有应用程序和服务都将在数据子网中运行,而 vpnsrvA 和 vpnsrvB 将在管理子网中交换运行时状态和路由信息。将这两台机器视为您的虚拟 LAN 的虚拟网络交换机。另请注意,这些子网不必是 C 类;您可以选择更大的数据网络,特别是如果您计划连接大量主机。
列表 1a. vpnsrvA 的 OpenVPN 服务器配置
server 10.100.100.0 255.255.255.0 ifconfig 10.100.100.1 10.100.100.2 push "route 10.100.100.0 255.255.255.0" push "route 10.200.200.0 255.255.255.0" dev tun proto udp user nobody persist-key persist-tun dh keys/dh1024.pem ca keys/ca.crt cert keys/vpnsrvA-1.crt key keys/vpnsrvA-1.key comp-lzo verb 3 keepalive 10 60 client-config-dir ccd management tunnel 5656 /etc/openvpn/pass
列表 1b. vpnsrvB 的 OpenVPN 服务器配置
mode server tls-server ifconfig 10.100.100.10110.100.100.102 ifconfig-pool 10.100.100.410.100.100.251 route 10.100.100.0255.255.255.0 push "route 10.100.100.0255.255.255.0" push "route 10.200.200.0255.255.255.0" dev tun proto udp user nobody persist-key persist-tun dh keys/dh1024.pem ca keys/ca.crt cert keys/vpnsrvB- 1.crt key keys/vpnsrvB-1.key comp-lzo verb 3 keepalive 10 60 client-config-dir ccd management tunnel 5656 /etc/openvpn/pass
将 vpnsrvA 和 vpnsrvB 配置为数据子网的 OpenVPN 服务器(列表 1a 和 1b)。您可以根据需要添加更多配置选项。请注意,配置文件中的“server”行是一个快捷方式,不能同时用于 vpnsrvA 和 vpnsrvB。它实际上会扩展为一组命令,这些命令会将 10.100.100.1 分配给两个服务器(有关更多详细信息,请参阅 OpenVPN 手册页)。我们想要一个主动-主动配置;因此,我们需要 vpnsrvA 和 vpnsrvB 位于同一子网中,但具有不同的 IP 地址。为了实现这一点,我们显式扩展了 vpnsrvB 的服务器定义,并为其分配了 10.100.100.101 IP 地址。
另一个重要的注意事项是,客户端配置目录(通常称为 ccd)和密钥目录(称为 keys)需要在 vpnsrvA 和 vpnsrvB 上相同。实现此目的的最简单方法之一是使用 rsync。rsync 使我们能够保持简单并避免混合使用额外的变量。此外,我们始终可以切换 rsync 的方向,并将任一服务器提升为主服务器。现在,让我们假设 vpnsrvA 是主服务器,并且 vpnsrvB 使用 rsync 镜像来自 vpnsrvA 的 ccd 和 keys 目录。您将创建密钥(最好使用 OpenVPN 附带的 easy-rsa 包)并更新主服务器上的 ccd 条目。
列表 2. OpenVPN 客户端配置
# Note: "remote" must point to servers' physical # (not virtual) IP addresses client remote 192.168.7.1 remote 192.168.17.1 dev tun proto udp user nobody persist-key persist-tun keepalive 10 60 comp-lzo ca keys/ca.crt cert keys/client-1.crt key keys/client-1.key ns-cert-type server
此时,您可以将网络上的多个主机配置为 OpenVPN 客户端(列表 2)。每个主机都将有自己的证书/密钥对,并且此主机的 ccd 条目中的 ifconfig-push 指令将设置其 IP 地址(有关如何设置的详细说明,请参阅资源中的 OpenVPN HOWTO 链接)。我们将虚拟 IP 地址绑定到主机,基于其证书/密钥对,这与在 DHCP 配置中您将 IP 地址绑定到主机,基于其以太网 MAC 地址的方式非常相似。因此,每个客户端都必须有自己唯一的证书/密钥对。
请注意,我们使用 OpenVPN 的内置功能在多个服务器之间进行轮询,并在连接失败后重新连接,这由 keepalive 选项控制。完成此操作后,您应该能够启动 OpenVPN 客户端,并且它们至少应该能够与其当前的 OpenVPN 服务器通信,并通过 IP(10.100.100.1 或 10.100.100.101)引用它。如果您的客户端连接到 vpnsrvA,并且您关闭了 vpnsrvA 上的 openvpn 守护程序,则客户端将检测到它并自动重新连接到 vpnsrvB。
关于防火墙的简要说明——在虚拟 LAN 中,您的主要数据接口将被称为 tun0。因此,您在单源配置中用于定义接口 eth0 的所有规则都需要为 tun0 重新定义。但是,以太网接口将需要额外的规则,以允许从客户端机器到 vpnsrvA 和 vpnsrvB 的端口 1194 (OpenVPN) 上的 UDP。
我们已经完成的设置在某种程度上是容错的。如果 vpnsrvA 变得不可用,所有客户端将重新连接到 vpnsrvB,并且连接将恢复。换句话说,这是主-备冗余。但是,如果 vpnsrvA 和 vpnsrvB 都启动会发生什么?让我们假设 host1 和 host2 在客户端模式下运行 openvpn 守护程序。host1 连接到 vpnsrvA 并被分配了 10.100.100.25;host2 连接到 vpnsrvB 并被分配了 10.100.100.41。vpnsrvA 上的路由表如列表 3 所示。在这种情况下,当 host1 尝试 ping 10.100.100.101 时,其传出的数据包将首先路由到 vpnsrvA,然后将返回到同一 tun0 接口,因为 vpnsrvA 不知道 vpnsrvB 的存在。同样,当 host1 尝试 ping host2 时,vpnsrvA 也将发送这些数据包返回,如 10.100.100.0/24 路由所示。因此,这两个操作都将失败。
列表 3. vpnsrvA 上路由表的部分内容
10.100.100.2 0.0.0.0 255.255.255.255 UH 0 0 0 tun0 10.100.100.0 10.100.100.2 255.255.255.0 UG 0 0 0 tun0
为了解决这个问题,我们开发了一个名为 cube-routed 的动态路由守护程序(从 www.cohesiveft.com/multisourced-infra 下载)。它在 vpnsrvA 和 vpnsrvB 之间共享路由信息,并根据哪个客户端连接到哪个服务器来近乎实时地调整路由表。其内部结构不是很复杂。一个线程通过其管理界面(请参阅 OpenVPN 配置文件中的 management 选项)连接到本地 OpenVPN 守护程序进程,并定期运行 status 命令以更新本地连接的客户端列表。另一个线程为 cube-routed 的远程实例发布此信息。第三个线程定期从 cube-routed 的远程实例读取连接的客户端列表。最后,第四个线程根据以下两个规则调整本地路由表:1) 为每个连接到远程 OpenVPN 服务器的主机添加主机路由,以及 2) 删除每个连接到本地 OpenVPN 服务器的主机的主机路由。
cube-routed 实例将通过我们之前选择的管理子网交换信息。在 vpnsrvA 和 vpnsrvB 之间创建第二个隧道 tun1。vpnsrvA 可以是 IP 为 10.200.200.1 的服务器,而 vpnsrvB 是 IP 为 10.200.200.5 的客户端。您可以使用列表 1 和 2 中的配置文件作为基础,但请记住调整 IP 地址并选择不同的端口——例如,您可以添加端口 11940到服务器和客户端。启动两个 OpenVPN 守护程序,并使用ping 10.200.200.1和ping 10.200.200.5来验证它们之间的连通性。
现在,在 vpnsrvA 和 vpnsrvB 上为 cube-routed 创建配置文件,如列表 4a 和 4b 所示,并以 root 身份启动两个实例,并将配置文件的路径作为唯一参数(请注意,OpenVPN 必须已经在运行,并且 vpnsrvA 和 vpnsrvB 上的 tun0/tun1 接口必须已启动)。
列表 4a. vpnsrvA cube-routed 配置文件
vpnsrvA mgmt_interface = tun1 data_interface = tun0 remote_mgmt_ip = 10.200.200.5 remote_data_ip = 10.100.100.101 openvpn_mgmt_pass_file = /etc/openvpn/pass openvpn_mgmt_port = 5656 cube_routed_port = 5657
列表 4b. vpnsrvB cube-routed 配置文件
mgmt_interface = tun1 data_interface = tun0 remote_mgmt_ip = 10.200.200.1 remote_data_ip = 10.100.100.1 openvpn_mgmt_pass_file = /etc/openvpn/pass openvpn_mgmt_port = 5656 cube_routed_port = 5657
一旦您启动所有内容,并在几分钟的初始收敛时间后,即使 host1 和 host2 连接到不同的 OpenVPN 服务器,它们也能够相互通信。因此,您已经实现了完全容错的虚拟 LAN 连接,并将数据流量加密作为额外的奖励。
此实现并非没有其局限性。首先,使用广播或多播的应用程序将无法与 OpenVPN 的 tun 设备一起使用。您可以使用此处描述的相同网络布局,但可以使用 OpenVPN 的 tap 设备代替 tun 来解决此问题。其次,通过公共互联网的网络链路的延迟明显高于以太网。如果这是您的应用程序的固有要求,您可能应该将您基础设施的这一部分保留为单源。第三,由于我们使用基于 UDP 的隧道,OpenVPN 链路将比以太网更容易频繁地启动和关闭,尤其是在网络拥塞期间。您可以实施数据缓存,避免长时间的 TCP 连接,专注于网络异常处理逻辑,并尝试使用 TCP 隧道来减少负面影响。最后,在此设置中只有两个 OpenVPN 服务器。这通常应该足够了,因为它不影响您连接到多源基础设施的实际主机的数量。如果由于某种原因您需要两个以上,则在 cube-routed 实例之间实现路由共享会变得更加困难。在这种情况下,您可能需要考虑使用消息传递系统而不是原始套接字(例如,RabbitMQ)。总而言之,在我们的案例中,我们发现多源基础设施的总体优势远远超过了这些限制造成的问题,特别是如果您在设计架构时考虑到这些限制。
多源基础设施是其单源前身的逻辑扩展,类似于分布式面向服务的架构,它出现在单体应用程序之后,并实现了更大的灵活性、更快的开发周期和更高的可用性。它可以帮助您设计更智能的架构,并避免锁定到单个托管提供商,这都建立在经过时间考验的标准开源 OpenVPN 之上。
资源
OpenVPN: openvpn.net
OpenVPN 2.0 操作指南: openvpn.net/howto.html
Hans-Cees Speel 撰写的“Meet OpenVPN”: www.linuxjournal.com/article/7949
David Bogen 撰写的“OpenVPN 简介”: www.osnews.com/story.php/5803/Introduction-to-OpenVPN
Openswan: www.openswan.org
cube-routed: www.cohesiveft.com/multisourced-infra
Amazon EC2: aws.amazon.com/ec2
RabbitMQ: www.rabbitmq.com
Dmitriy Samovskiy 在 CohesiveFT (www.cohesiveft.com) 工作,CohesiveFT 是一家创新的定制虚拟化应用程序堆栈制造商,他专注于开源技术、分布式应用程序、系统集成、Python 和 Ruby。您可以通过 dmitriy.samovskiy@cohesiveft.com 联系他。