OpenStack LBaaS UI实施



当我为虚拟私有云实现负载均衡器的用户界面时我不得不面对巨大的困难。 这使我反思了前端的角色,我想首先分享一下。 然后使用特定任务的示例来证明他们的想法。

我认为该问题的解决方案非常有创意,我不得不在一个非常有限的框架内寻找它,因此我认为这可能很有趣。

前端角色


我必须马上说,我不假装事实,并提出有争议的问题。 前端和网络的讽刺使我有些沮丧,因为这无关紧要。 更令人沮丧的是,有时这种情况会合理发生。 现在时尚已经睡着了,但是有一段时间,每个人都在使用框架,范式和其他实体,他们大声说所有这些都是超重要的,而且是超必要的,作为回报,他们得到了一个讽刺,即前端处理表单和表单的输出。处理单击按钮,可以“在膝盖上”完成。

现在看来,一切都或多或少恢复了正常。 没有人真的想谈论下一个框架的每个次要版本。 由于对实用程序的认识不断提高,很少有人在寻找完美的工具或方法。 但是,例如,即使这样,也不会干扰几乎不合理地责骂Electron及其上的应用程序。 我认为这是由于对前端要解决的任务缺乏了解。

前端不仅是显示后端提供的信息的手段,而且不仅仅是处理用户动作的手段。 前端是更多的东西,是抽象的东西,如果给它一个简单,清晰的定义,那么含义将不可避免地丢失。

前端处于某些“框架”中。 例如,在技术上,它介于后端提供的API和I / O设施提供的API之间。 在任务方面,UX解决的是用户界面的任务,而后端解决的是任务。 因此,获得了相当狭窄的前端专业化,即层的专业化。 这并不意味着前端提供商无法对其专业领域以外的领域施加影响,但是在这种影响不可能的时刻,真正的前端任务就出现了。

这个问题可以通过矛盾来表达。 不需要用户界面来符合数据模型和后端行为。 不需要后端的行为和数据模型来适应用户界面的任务。 然后,前端的任务就是消除这种矛盾。 后端和用户界面的任务之间的差异越大,前端的作用就越重要。 为了弄清楚我在说什么,我将举一个例子说明由于某种原因这种差异很明显。

问题陈述


我认为,OpenStack LBaaS是一种硬件-软件组合,其中包含平衡服务器之间负载的必要工具。 对我而言,重要的是其实现取决于客观因素和物理显示。 因此,API以及与该API交互的方式都有其特殊性。

开发用户界面时,主要兴趣不是后端的技术功能,而是其基本功能。 该接口是为用户创建的,用户需要一个用于管理平衡参数的接口,并且用户无需深入了解后端实现的内部功能。

后端大部分是由社区开发的,有可能在非常有限的数量上影响其发展。 对我而言,关键特性之一是后端开发人员愿意为了性能而牺牲控件的便利性和简单性,这是绝对合理的,因为这是平衡负载的问题。

还有一点微妙之处,我想立即概述一下,警告一些问题。 显然,在OpenStack及其API上,灯光并未收敛。 您始终可以开发自己的工具集或将与OpenStack API配合使用的“层”,从而生成自己的API,以方便用户执行任务。 唯一的问题是权宜之计。 如果最初可用的工具允许您按预期实现用户界面,那么产生实体有意义吗?

这个问题的答案是多方面的,对于企业而言,它将取决于开发商,他们的雇用,他们的能力,责任,支持等问题。 在我们的案例中,最便捷的方法是解决前端的某些任务。

OpenStack LBaaS的功能


我只想确定对前端有重大影响的那些功能。 为什么出现这些功能或它们依赖什么的问题已经超出了本文的范围。

我使用现成的文档,并且必须接受其功能。 那些从内部对OpenStack Octavia感兴趣的人可以熟悉官方文档 。 Octavia是旨在平衡OpenStack生态系统中的负载的一组工具的名称。

我在开发过程中遇到的第一个功能是显示平衡器状态所需的大量模型和关系。 Octavia API描述了12种模型,但客户端只需要7种模型,这些模型具有连接,通常是非规范化的,下图显示了一个近似图:



“七个”听起来并不令人印象深刻,但实际上,为了确保界面正常工作,在编写本文时,我不得不使用16个数据模型以及它们之间的大约30种关系。 由于Octavia只是一个平衡器,因此它需要其他OpenStack模块才能工作。 用户界面中只有两个页面需要所有这些。

第二和第三个功能是异步和事务性Octavia。 数据模型具有一个状态字段,该字段反映对对象执行的操作的状态。
现况内容描述
活跃的物件状况良好
已删除对象已删除
失误对象已损坏
PENDING_CREATE制作对象
PENDING_UPDATE对象正在更新
PENDING_DELETE删除对象
读取对象的操作是同步发生的,没有任何限制。 但是创建,更新和删除操作可能会花费不确定的时间。 正是由于这样的事实,即数据模型具有大致的物理意义。

发送创建请求后,我们可以知道该记录已经出现,可以读取它,但是在创建操作完成之前,我们无法对该记录执行任何其他操作。 任何此类尝试都将导致错误。 仅当对象处于活动状态时,才能启动更改对象的操作;可以在活动和错误状态下发送要删除的对象。

这些状态可以通过WebSockets到达,这极大地方便了它们的处理,但是事务是一个更大的问题。 对任何对象进行更改时,所有相关模型也将参与事务。 例如,当对Member进行更改时,关联的PoolListenerLoadbalancer将被阻止。 这是从Web套接字接收到的事件来看的样子:

  • 前四个事件是将对象转移到PENDING_UPDATE状态: 目标字段包含参与事务的对象的模型名称;
  • 第五个事件只是重复事件(我不知道它与什么有关);
  • 最后四个是返回到活动状态。 在这种情况下,这是一次重量更改操作,耗时不到一秒钟,但有时会花费更多时间。

您还可以在屏幕截图中看到事件的顺序不必严格。 因此,事实证明,为了启动任何操作,不仅需要知道对象本身的状态,而且还必须知道也将参与事务的所有依赖项的状态。

用户界面功能


现在,假设您自己是一个需要了解两台服务器之间平衡的用户的位置:

  1. 必须创建一个将在其中定义平衡算法的侦听器。
  2. 创建一个池。
  3. 将池分配给侦听器。
  4. 将指向平衡端口的链接添加到池中。

每次必须等待操作完成时,这取决于所有先前创建的对象。

正如一项内部研究表明的那样,在普通用户看来,只有一个近似的认识,即平衡器必须有一个入口点,必须有出口点以及要执行的平衡参数:算法,权重等。 用户不必知道什么是OpenStack。

我不知道界面对于用户的感知应该有多么复杂,用户自己必须遵循上述后端的所有技术功能。 对于控制台,这可能是允许的,因为它的使用暗示着高度沉浸在技术中,但是对于网络,这样的界面是可怕的。

在网络上,用户希望填写一张清晰且合乎逻辑的表格,按下一个按钮,然后等待,一切都会正常进行。 也许可以争辩,但是我建议着重于影响前端实现的功能。

接口的设计涉及级联使用操作:接口中的一个动作可能涉及多个操作。 该界面并不意味着用户可以执行当前无法执行的操作,但是该界面假定用户必须理解为什么这样做。 接口是一个整体,因此,它的各个元素可以使用来自各种相关实体的信息,包括元信息。



如果我们考虑到平衡器不是某些界面特有的功能,例如开关,手风琴,选项卡,上下文菜单,并假设它们的操作原理一开始就很清楚,那么对于那些了解什么是负载平衡的用户,我不认为很难阅读上面的大多数界面并就如何管理它进行假设。 但是要突出显示接口的哪些部分隐藏在平衡器,侦听器,池,成员和其他实体的模型后面,已不再是最明显的任务。

解决矛盾


我希望能够证明后端的功能不太适合该接口,并且后端无法始终消除这些功能。 随之而来的是,接口的功能不能很好地适合后端,并且在不使接口复杂化的情况下也无法始终消除。 这些领域中的每一个都解决了自己的问题。 前端的责任是解决问题,以确保接口和后端之间必要的交互级别。

在我的练习中,我立即全神贯注地冲入泳池,甚至没有试图找出那些更高的特征,但是我很幸运或经验有所帮助(选择了正确的向量)。 我已经多次亲自注意到,在使用第三方API或库时,提前熟悉文档非常有用:越详细越好。 该文档通常彼此相似,人们仍然依赖于其他人的经验,但是在每个细节中都有对每个系统功能的描述。

如果最初我花了额外的几个小时来研究文档,而不是通过关键字来提取必要的信息,我会考虑必须要面对的问题,而这种知识可能会从一开始就对项目体系结构产生影响。 回到最开始消除错误是很令人沮丧的。 在没有完整背景的情况下,有时您必须回来几次。

作为一种选择,您可以弯腰,逐渐地“越来越多地”逐渐生成越来越多的代码,但是这些代码堆越多,最终越难完成。 当然,在设计体系结构时,不应太深究,要考虑所有可能和不可能的选择,并花大量时间在其上,找到一个平衡点很重要。 但事实证明,对文档的了解或多或少都是非常有用的,而且花费的时间并不多。

不过,从一开始,就已经看到大量模型,我意识到有必要在保留所有连接的情况下建立后端状态到客户端的映射。 在设法在客户端上显示所有必要的信息以及所有连接等之后,有必要组织一个任务队列。

数据是异步更新的,操作的可用性由多种条件决定,并且当需要级联操作时,在这种条件下无需放弃任何队列。 简而言之,也许这就是我的解决方案的整个体系结构:存储反映了后端状态和任务队列。

解决方案架构


由于模型和关系的数量不确定,我通过使用工厂来将可伸缩性放入存储库的结构中,该工厂返回了存储库集合的声明性描述。 该集合有一个服务,一个带有CRUD的简单模型类。 可以在模型中进行链接的描述,例如在RoR或旧版本的Backbone中进行描述,但这将需要更改大量代码。 因此,关系的描述位于模型类的旁边:



总共,我得到了两种连接类型:一对一,一对多。 反馈也可以描述。 除类型外,还指示依赖项的集合,找到的依赖项所附加的字段以及从中读取依赖对象的ID的字段(在一对多通信的情况下,将读取ID列表)。 如果一个对象的通信条件比简单的对象链接更为复杂,则在工厂中可以描述测试两个对象的功能,其结果将确定连接的存在。 看起来有点像“自行车”,但它的工作原理完全没有不必要的依赖。

该存储库具有一个模块,用于等待添加和删除资源,从本质上讲,它是通过条件检查和Promis接口处理一次性事件。 订阅时,将传递事件的类型(添加,删除),测试功能和处理程序。 当某个事件发生且测试结果为肯定时,将执行处理程序,然后停止跟踪。 同步订阅时可能会发生一个事件。

通过使用这种模式,可以在模型之间自动附加任意复杂的关系,并将其放在一个位置。 我把这个地方称为追踪器。 将对象添加到存储库时,它开始跟踪其关系。 等待模块使您能够响应事件并检查受监视对象与存储中对象之间的连接。 如果对象已经在存储库中,则等待模块将立即调用处理程序。

这样的存储设备使您可以描述任意数量的集合及其之间的关系。 添加和删​​除对象时,存储区会自动放置或重置带有从属对象内容的属性。 这种方法的优点是所有关系都被明确描述,并由一个系统监视和更新。 缺点-实现和调试的复杂性。

通常,这样的存储库相当琐碎,我自己做,因为将现成的解决方案集成到现有代码库中会更加困难,但是将任务队列附加到现成的解决方案上会更加困难。

所有任务(如集合)都具有声明性描述,并由工厂创建。 描述中的任务可以包含启动条件以及当前任务完成后需要添加到队列中的任务列表。


上面的示例描述了创建池的任务。 在依赖项中,指示了平衡器和侦听器,默认情况下,将对ACTIVE状态进行检查。 平衡器的对象已锁定,因为队列中的处理任务可以同步发生,所以锁定可以避免发送执行请求但状态未更改时发生冲突,但是假定状态会发生变化。 如果池是由任务级联创建的,而不是PARENT ,则ID将自动替换。

创建池后,任务将添加到队列中以创建可用性监视器并创建该池的所有成员。 输出是可以完全转换为JSON的结构。 这样做是为了能够在发生故障时还原队列。

根据任务描述,队列独立监视存储库中的所有更改并检查运行任务必须满足的条件。 正如我已经说过的那样,状态是通过Web套接字传递的,并且为队列生成必要的事件非常简单,但是如果需要的话,附加计时器数据更新机制也不会是一个问题(这最初是在体系结构中规定的,因为Web套接字是由于各种原因可能无法非常稳定地工作)。 任务完成后,队列会自动通知存储库有关更新指定对象中的链接的需求。

结论


对可伸缩性的需求导致了一种声明式方法。 显示模型及其之间的关系的需求导致了一个单一的存储库。 处理依赖对象的需求导致了排队。

就实现而言,结合这些需求可能不是最容易的任务(但这是一个单独的问题)。 但是就架构而言,该解决方案非常简单,可以消除后端任务和用户界面之间的所有矛盾,建立它们之间的交互作用,并为各方的其他可能功能奠定基础。

从Selectel 控制面板的侧面看平衡过程非常简单明了,这使服务客户不必在平衡器的独立实现上花费资源,同时还能保持灵活管理流量的能力。

立即试用我们的平衡器,并在评论中写下您的评论。

Source: https://habr.com/ru/post/zh-CN455523/


All Articles