TL; DR :我们内部配置管理工具QControl的客户端-服务器体系结构。
在它的基础上,有一个两层的传输协议可以处理gzip压缩的消息,而无需在端点之间进行解压缩。 分布式路由器和端点接收配置更新,并且协议本身使安装中间本地化中继成为可能。 它基于
差异备份 (“近期稳定”,进一步说明)设计,并使用JMESpath查询语言和Jinja模板进行配置呈现。
Qrator Labs在并维护着全球分布的缓解网络。 我们的网络是任意广播的,基于通过BGP宣布子网的信息。 作为BGP任意播网络,物理上分布在地球上的多个区域,这使我们能够处理和过滤更接近Internet主干网(第1层运营商)的非法流量。
另一方面,成为地理上分散的网络会遇到困难。 对于安全提供商而言,网络存在点(PoP)之间的通信对于所有网络节点具有一致的配置并及时且有凝聚力地进行更新至关重要。 因此,为了向客户提供最佳服务,我们必须找到一种在不同大洲之间可靠地同步配置数据的方法。
最初,Word ...迅速成为需要升级的通信协议。
QControl存在的转折点以及花费大量时间和精力来构建我们这样的协议的主要原因是需要获得权威的配置源,并最终使我们的PoP与它同步。 存储只是QControl开发的几个必需功能之一。 除此之外,我们还需要与现有和未来的外围设备集成,智能(可自定义)数据验证以及访问差异化。 此外,我们希望该系统通过命令而不是通过手动文件修改来管理事务。 在QControl之前,数据或多或少是手动发送到存在点的。 如果当前无法使用某些PoP,而我们又忘记了对其进行更新,则配置将不同步,并且需要耗时的故障排除才能使其恢复同步。
这是我们想到的系统:

配置服务器负责数据验证和存储; 路由器具有不同的端点,可以从客户端,支持团队到服务器以及从服务器到PoP接收和中继配置更新。
全世界的Internet连接质量仍然非常不同-为了说明这一点,让我们直观地看到一条从布拉格,捷克共和国到新加坡以及香港的简单路由。

从布拉格到新加坡的地铁

与traceroute到香港的第二张截图相同
高延迟数意味着速度较差。 此外,还有很高的丢包率。 带宽数字不能弥补这个问题,在构建分散式网络时应始终将其考虑在内。
完整的PoP配置是相当大量的数据,需要通过不可靠的连接传输到许多不同的接收器。 幸运的是,尽管配置经常更改,但它会以较小的增量更改。
近期稳定的设计
基于增量更新构建分布式网络是一个非常简单的决定。 尽管差异存在很多问题,但它们很难正确构建:我们必须将所有参考点之间的差异保存在某个地方,以便在有人丢失某些东西时能够重新发送它们。 每个目的地都应连贯地应用它们。 如果有多个目的地,则整个网络可能要花费很多时间。 收件人也应该能够请求丢失的部分,并且当然,中心部分必须准确地回答此类请求,仅发送丢失的数据。
最终,我们构建的东西是相当多的-我们只有一个参考层,固定的“稳定”层,只有一个比较层,“最近”。 每个最近的版本都基于最新的稳定版本,足以重建配置数据。 当新近到达目的地时,旧的就可以使用了。
由于最近的配置过大,因此有时需要发送新的稳定配置。 另外,这里重要的一点是,我们可以通过广播/多播更新来完成所有这些操作,而不用担心接收目标组装这些文件的能力。 一旦我们检查了每个人都拥有正确的稳定点,就给他们提供了最新的消息。 我们应该说它有效吗? 是的 马able会缓存在配置服务器和接收方,并在需要时重新创建马recent。
两层运输架构
为什么我们要从两层中构建出运输工具? 答案很简单:我们想从应用程序中分离出路由,从OSI模型及其传输层和应用程序层中汲取灵感。 因此,我们已将传输协议(Thrift)与高级命令(msgpack)序列化格式分开。 这就是为什么路由器(进行多播/广播/中继)既不在msgpack内部查看,也不提取或压缩有效载荷并且仅进行传输的原因。
节俭
维基Apache Thrift允许您在简单的定义文件中定义数据类型和服务接口。 将该文件作为输入,编译器将生成用于轻松构建跨编程语言无缝通信的RPC客户端和服务器的代码。 无需编写大量样板代码来序列化和传输对象并调用远程方法。我们采用Thrift框架是因为它同时具有RPC和多种语言支持。 与往常一样,容易构建的部分也很容易:客户端和服务器。 但是,路由器很难破解,部分原因是当时缺乏现成的解决方案。

还有其他一些选择,例如protobuf / gRPC,尽管当我们开始我们的项目时,gRPC不成熟,我们犹豫要使用它。
当然,我们可以(并且应该!)创建自己的轮子。 为我们所需的协议以及路由器创建自定义协议会更容易,因为与使用Thrift制作可工作的路由器相比,客户端-服务器更容易编程。 但是,传统上对自定义协议和流行库的实现持否定态度,并且始终存在“我们以后如何将其移植到其他语言中”的问题。 因此,我们决定放弃那些笨拙的想法。
Msgpack
说明 :
MessagePack是一种有效的二进制序列化格式。 它使您可以在JSON之类的多种语言之间交换数据。 但是它更快,更小。 小整数被编码为一个字节,典型的短字符串除字符串本身外仅需要一个额外的字节。在第一层,我们有一个Thrift,其中包含路由器发送消息所需的最少信息。 在第二层,我们压缩了msgpack结构。
我们投票支持msgpack,因为它比JSON更快,更紧凑。 但是,更重要的是它支持自定义数据类型,并提供了一些令人兴奋的功能,例如“断电”和原始二进制数据传输。
JmespathJMESPath是JSON的查询语言。这是我们从官方JMESPath文档获得的唯一描述,但实际上,不仅限于此。 JMESPath允许搜索和过滤任意树状结构,甚至可以即时应用数据转换。 我们使用此查询语言从大型配置Blob获取相关信息。
虽然整个配置具有树状结构,但我们提取了针对不同配置目标的相关子树。
它也足够灵活,可以独立于配置模板或其他配置插件来更改子树。 为了使它更好,JMES Path易于扩展,并允许编写自定义过滤器和数据转换例程。 但是,它需要一些智力。
金佳对于某些目标,我们需要将配置呈现到文件中,因此我们需要一个模板引擎,其中Jinja是一个明显的选择。 Jinja正在根据模板和在目的地接收到的数据生成配置文件。
要渲染配置文件,我们需要一个jmespath请求,路径和目标文件的模板,配置本身的模板。 另外,此时,最好指定文件访问权限。 幸运的是,所有这些都组合在一个文件中-在配置模板之前,我们放置了一个YAML标头,以描述其余内容。 例如:
---
selector: "[@][?@.fft._meta.version == `42`] | items([0].fft_config || `{}`)"
destination_filename: "fft/{{ match[0] }}.json"
file_mode: 0644
reload_daemons: [fft]
...
{{ dict(match[1]) | json(indent=2, sort_keys=True) }}
要为新的外围设备进行配置,我们添加了一个新的模板文件,无需更改源代码和PoP软件。
实施QControl配置管理工具后发生了什么变化?
首先,我们在整个网络上获得了一致且可靠的配置更新。
其次,我们将功能强大的工具用于配置验证和更改,交付给我们的支持团队和客户。
通过使用最近稳定的设计来简化配置服务器与配置接收者之间的通信,使用两层通信协议来支持与负载无关的路由器,以及通过实现基于Jinja的配置渲染引擎来支持多种多样的配置,我们已经实现了这一点各种外围设备的配置文件集。