深入浅出边缘云 | 5. 运行时控制
随着技术的发展以及应用对时延、带宽、安全的追求,一个明显的技术趋势是越来越多的应用组件将会被部署到企业所管理的网络边缘。本系列是开源电子书Edge Cloud Operations: A Systems Approach的中文版,详细介绍了基于开源组件构建的边缘云的架构、功能及具体实现。
第 5 章 运行时控制
运行时控制子系统提供 API,各主体(如终端用户、企业系统管理员和云运维人员)可以通过 API 对正在运行的系统进行更改,为一个或多个运行时参数指定新值。
下面以 Aether 5G 连接服务为例,假设企业系统管理员想更改一组移动设备的服务质量(Quality-of-Service)。Aether 定义了设备组(Device Group) 抽象,以便相关设备可以一起配置。然后,管理员可以修改最大上行带宽(Maximum Uplink Bandwidth) 或最大下行带宽(Maximum Downlink Bandwidth) ,甚至可以为该组选择不同的流量类(Traffic Class) 。类似的,可以设想一下运营商想为现有设备的流量类(Traffic Classes) 中添加一个新的关键任务(Mission-Critical) 选项。先不考虑这些操作的 API 调用的确切语法,运行时控制子系统需要:
验证要执行操作的主体。
确定该主体是否有执行操作的足够权限。
将新参数设置推送到一个或多个后端组件中。
记录指定的参数设置,以便保留新值。
在这个例子中,设备组(Device Group) 和流量类(Traffic Class) 是被操作的抽象对象,虽然运行时控制子系统必须理解这些对象,但对它们的改变可能涉及调用多个子系统的低级控制操作,如 SD-RAN(负责 RAN 中的 QoS)、SD-Fabric(负责交换网络的 QoS)、SD-Core UP(负责移动核心网用户平面的 QoS)和 SD-Core CP(负责移动核心网控制平面的 QoS)。
简而言之,运行时控制在后端组件集合之上定义了抽象层,将它们有效的转变成外部可见(和可控)的云服务。有时,单一后端组件实现了全部服务,在这种情况下,运行时控制可能只是增加了一个 AAA 层而已。但是,对于由分散组件组成的云来说,运行时控制是我们定义 API 的地方,可以在逻辑上将这些组件整合为一套统一的、连贯的抽象服务。这也是为底层子系统"提高抽象水平"和隐藏实现细节的机会。
请注意,由于其作用是基于一组后端组件提供端到端服务,本章介绍的运行时控制机制类似于服务编排器(Service Orchestrator) ,将电信网络中的 VNF 集合连在一起。这里可以使用任一术语,但我们选择"运行时控制(Runtime Control)"来强调问题的时间性,特别是与生命周期管理的关系。另外,"编排"也是一个有内涵的术语,在不同背景下有不同的含义。在云计算环境中,意味着装配虚拟资源,而在电信环境中,意味着装配虚拟功能。正如在复杂系统中经常出现的情况那样(特别是在促进竞争的商业模式时),你在堆栈中走得越高,对术语的共识就越少。
无论如何称呼这种机制,定义一组抽象和相应的 API 都是具有挑战性的工作。合适的工具能够帮助我们专注于任务的创造性部分,但却不能消除它。挑战部分在于判断哪些内容应该对用户可见,应该隐藏哪些实现细节,其中部分挑战在于如何处理、合并冲突的概念和术语。我们将在 5.3 节看到一个完整示例,但为了说明这一困难,请考虑 Aether 是如何在其 5G 连接服务中提及用户的。如果直接从电信行业借用术语,那指的是使用移动设备的人(subscriber),同时意味着向该设备提供服务的帐户和设置集合。事实上,用户(subscriber)是 SD-Core 实现的核心对象。但 Aether 是为支持企业部署 5G 而设计的,为此,将用户定义为具有某种规定权限级别的访问 API 或 GUI 门户的主体。该用户和核心网定义的用户(subscriber)之间不一定存在一对一的关系,更重要的是,不是所有设备都有用户,像物联网设备的情况一样,通常不与某个人相关联。
5.1 设计概述
在高层次上,运行时控制的目的是提供一个各方可以用来配置和控制云服务的 API。为此,运行时控制必须:
支持跨多个后端子系统的端到端抽象。
将控制和配置状态与抽象相关联。
支持配置状态的版本控制,可以根据需要回滚更改,并且可以检索以前配置的审计历史记录。
在如何实现这个抽象层方面采用高性能(performance) 、高可用性(high availability) 、可靠性(reliability) 和安全性(security) 的最佳实践。
支持基于角色的访问控制(Role-Based Access Control, RBAC) ,以便不同主体对底层抽象对象具有不同的可见性和控制。
具有可扩展性,能够随着时间的推移合并新的服务或者升级现有服务的新的抽象。
核心要求是运行时控制能够代表一组抽象对象,也就是说,实现一个数据模型。虽然用于表示数据模型的规范语言有几种可行的选择,但对于运行时控制,我们使用 YANG。这有三个原因,首先,YANG 是一种丰富的数据建模语言,支持对存储在模型中的数据进行强有力的验证,并且能够定义对象之间的关系。其次,对数据的存储方式没有要求(即不直接与 SQL/RDBMS 或 NoSQL 范式挂钩),从而为我们提供了广泛的工程选项。最后,YANG 被广泛用于这一目的,意味着有大量强大的基于 YANG 的工具集合作为我们构建的基础。
延伸阅读:
YANG - A Data Modeling Language for the Network Configuration Protocol. RFC 6020. October 2010.
基于这一背景,图 22 显示了 Aether 运行时控制的内部结构,其核心是 x-config(维护一组 YANG 模型的微服务)<sup>[1]</sup>。x-config 使用 Atomix(一个键值存储微服务),使配置状态持久化。因为 x-config 最初被设计为管理设备的配置状态,使用 gNMI 作为其南向接口,将配置变化传达给设备(或在我们的例子中,软件服务)。必须为任何不支持 gNMI 的服务/设备编写适配器,这些适配器在图 22 中被显示为运行时控制的一部分,但将每个适配器视为后端组件的一部分也同样正确,它负责使该组件做好管理准备。最后,运行时控制还包括工作流引擎,负责在数据模型上执行多步骤操作。例如,当一个模型的改变触发了另一个模型上的某些操作时,就会发生这种情况。下一节将详细介绍这些组件。
[1] x-config 是一个通用的、模型无关的工具。在 AMP 中,它管理云服务的 YANG 模型,但 SD-Fabric 也基于它来管理网络交换机的 YANG 模型,SD-RAN 也用它来管理 RAN 组件的 YANG 模型。这意味着在给定的 Aether 边缘集群中运行着 x-config 微服务的多个实例。
图 22. 运行时控制的内部结构,以及与后端子系统(下层)和用户门户/应用程序(上层)的关系。
Web 框架
运行时控制在云运维中扮演的角色类似于 Web 框架在 Web 服务运维中扮演的角色。如果一开始就假设某些类别的用户将通过 GUI 与系统交互(在我们的例子中边缘云就是这一系统),那么你要么用 PHP 这样的语言编写 GUI(就像早期的 web 开发者所做的那样),要么利用 Django 或 Ruby on Rails 这样的框架。这些框架提供了一种方法来定义一组用户友好的抽象(称为模型 Models),通过这种方法来在 GUI 中可视化这些抽象(称为视图 Views),以及基于用户输入对后端系统进行更改(称为控制器 Controllers)。模型-视图-控制器(MVP, Model-View-Controller)是一种被广泛理解的设计范式,这并非偶然现象。
本章介绍的运行时控制子系统采用了类似的方法,但我们不是用 Python(如 Django)或 Ruby(如 Ruby on Rails)来定义模型,而是用一种声明性语言(YANG)来定义模型,而这种语言又被用来生成可编程 API。这个 API 可以从(1)GUI 中调用,GUI 本身通常是使用另一个框架,如 AngularJS;(2)CLI;或(3)某个闭环控制程序。还有其他不同之处,例如,适配器(控制器的一种)使用 gNMI 作为控制后端组件的标准接口,持久化状态存储在键值存储中,而不是 SQL 数据库中,但最大的区别是使用声明性语言而不是命令性语言来定义模型。
运行时控制 API 是从 YANG 数据模型自动生成的,如图 22 所示,支持两类门户以及一组闭环控制应用程序,还支持 CLI(没有显示)。这个 API 可以为所有需要读取或写入 Aether 控制信息的相关方提供单一入口,从而通过中介访问控制和管理平台(Control and Management Platform)的其他子系统(不仅仅是图 22 中所示的子系统)。
这种情况如图 23 所示,其关键启示是:(1)我们希望对所有操作进行 RBAC 和审计;(2)我们希望有权威的配置状态单一来源;(3)我们希望授予每一个负责人对管理功能的有限(细粒度)访问权,而不是假设只有一个特权类别的操作者。当然,底层子系统的私有 API 仍然存在,操作人员可以直接使用。这在诊断问题时可能特别有用,但由于上述三个原因,有强力理由支持使用运行时控制 API 来代理所有控制活动。
这个讨论与第 4 章文末的"GitOps 呢?"相关。我们在本章最后将再次讨论同样的问题,但我们现在有了运行时控制选项,在键值存储中维护了系统权威配置和控制状态,为此做好了准备。这就提出了如何与实现生命周期管理的存储库"共享配置状态所有权"的问题。
一种选择是根据具体情况来决定,运行时控制维护某些参数的权威状态,配置存储库维护其他参数的权威状态。我们只需要弄清楚哪个是哪个,这样每个后端组件就知道需要响应哪个"配置路径"。然后,对于任何我们希望运行时控制来代理访问的重新维护的状态(例如,为更广泛的主体集提供细粒度访问),我们需要小心任何对重新维护的状态的后门(直接)更改的后果,例如,通过只在运行时控制的键值存储中存储该状态的一个缓存副本(作为一种优化)。
图 23. 运行时控制还代理对其他管理服务的访问。
图 23 另一个值得注意的方面是,虽然运行时控制代理了所有与控制有关的活动,但它不在所控制的子系统的"数据路径"中。例如,监控和遥测子系统返回的监测数据不经过运行时控制,被直接传递给运行在 API 之上的仪表盘和应用程序,运行时控制只参与授权对这些数据的访问。另外,运行时控制子系统和监控子系统有自己独立的数据存储,运行时控制使用 Atomix 键值存储,监控使用时间序列数据库(在第 6 章有更详细的讨论)。
总之,对统一的运行时控制 API 的价值最好的说明是能够实现闭环控制应用程序(和其他仪表板): "读取"监控子系统收集的数据,对该数据进行某种分析,也许做出某种决定采取纠正措施,然后"写入"新的控制指令,x-config 将其传递给 SD-RAN、SD-Core 和 SD-Fabric 的其中一个或某几个,有时甚至传递给生命周期管理子系统(我们将在第 5.3 节中看到后者的例子)。图 24 介绍了这种闭环情况,通过显示 j 将监控子系统和运行时控制子系统放在同一层级(而不是在它下面)而提供了不同视角,尽管两种视角都是有效的。
图 24. 运行时控制的另一个角度,说明支持闭环控制应用的统一 API 的价值。
5.2 实现细节
本节介绍运行时控制子系统中的每个组件,重点介绍每个组件在云管理中扮演的角色。
5.2.1 模型与状态(Models & State)
x-config 是运行时控制的核心,其主要工作是对配置数据进行存储和版本控制。配置通过北向 gNMI 接口推送到 x-config,存储在持久化键值存储中,并使用南向 gNMI 接口推送到后端子系统。
一组基于 YANG 的模型定义了配置状态的模式。这些模型被加载到 x-config 中,并共同定义了运行时控制负责的所有配置和控制状态的数据模型。作为示例,Aether 的数据模型(schema)在第 5.3 节中概述,但另一个例子是用于管理网络设备的 OpenConfig 模型集。
延伸阅读:
OpenConfig: Vendor-neutral, model-driven network management.
这一机制有四个重要方面:
持久化存储(Persistent Store): Atomix 是云原生键值存储,用于在 x-config 中持久化数据。Atomix 支持分布式映射抽象,实现了 Raft 共识算法,以实现容错和可伸缩性能。x-config 使用 NoSQL 数据库通用的简单 GET/PUT 接口向 Atomix 写入和读取数据。
加载模型(Loading Models): 使用模型插件(Model Plugins) 加载模型。x-config 通过 gRPC API 与模型插件通信,在运行时加载模型。模型插件是预编译的,因此在运行时不需要编译。x-config 和插件之间的接口消除了动态加载兼容性问题。
版本控制和迁移(Versioning and Migration): 所有加载到 x-config 中的模型都有版本控制,更新模型的过程触发持久状化态从一个版本到另一个版本的迁移。迁移机制支持多版本同时操作。
同步(Synchronization): 预计 x-config 控制的后端组件将周期失败并重启。由于 x-config 是这些组件的运行时真实来源,负责确保在重启时与最新状态重新同步。x-config 的模型包括反映这些组件的操作状态的变量,因此能够检测重启(并触发同步)。
有两点需要进一步阐述。首先,因为 Atomix 只要在多个物理服务器上运行就具有容错性,可以建立在不可靠的本地(每台服务器)存储之上,因此没有理由使用高可用的云存储。另一方面,为谨慎起见,运行时控制子系统维护的所有状态都要定期备份,以防因灾难性故障而需要从头开始重启。这些检查点,加上存储在 Git 仓库中的所有配置即代码文件,共同定义了(重新)实例化云部署所需的全部权威状态。
第二,模型定义集就像任何其他的配置即代码的片段,被签入代码库中并进行版本管理,就像第 4.5 节中介绍的那样。此外,指定如何部署运行时控制子系统的 HelmChart 确定了要加载的模型版本,类似于 Helm Chart 确定要部署的每个微服务(Docker 镜像)版本的方式。因为运行时控制 API 是由模型集自动生成的,这意味着 Helm Chart 有效指定了运行时控制 API 的版本,我们将在下一小节看到。所有这些都是说,云北向接口的版本控制,作为一个聚合的整体,其管理方式与对云的内部实现作出贡献的每个功能构件的版本控制完全相同。
5.2.2 运行时控制 API
API 提供了位于 x-config 和更高层门户和应用程序之间的接口包装器(interface wrapper) ,北向提供了 RESTful API,南向提供了 gNMI 和 x-config 通信。运行时控制 API 有三个主要目的:
与 gNMI(只支持 GET 和 SET 操作)不同,GUI 开发需要 RESTful API(支持 GET、PUT、POST、PATCH 和 DELETE 操作)。
API 层是实现早期参数验证和安全检查的机会,使得我们能够在离用户更近的地方捕捉错误,并生成比 gNMI 更有意义的错误消息。
API 层定义了"门控",可用于审计谁在什么时候执行了什么操作的历史记录(利用下面介绍的身份管理机制)。
从加载到 x-config 的模型集自动生成 REST API 是可行的,尽管也可以为了方便而用额外的"手工制作的"调用来增强这组模型(注意这可能意味着 API 不再是 RESTful 的)。将模型规范作为单一真实来源,并从规范中导出其他工件,如 API,这个想法很吸引人,这么做能够提高开发者生产力,并减少各层之间的不一致。例如,如果开发者希望在某个模型中添加一个字段,如果没有自动生成,下面的内容必须全部更新:
模型
API 规范
通过对模型进行操作来为 API 服务的 Stub
客户端库或开发人员工具包
可视化模型的 GUI 视图
Aether 的解决方案是使用名为oapi-codegen
的工具将 YANG 声明转换为 OpenAPI3 规范,然后使用oapi-codegen
自动生成实现 API 的 stub。
延伸阅读:
自动生成 API 并非没有缺陷。模型和 API 迅速发展成 1:1 的对应关系,意味着建模中的任何更改都会立即在 API 中可见。因此,如果要保持向后兼容性,必须小心处理建模更改。因为单个 API 不能轻易满足两组模型的需求,迁移也更加困难。
另一种方法是引入第二个面向外部的 API,并在自动生成的内部 API 和外部 API 之间建立一个小转换层(shim)。shim 层将起到减震器的作用,减轻内部 API 可能发生的频繁变更。当然,需要假设面向外部的 API 是相对稳定的,如果模型更改的首要原因是服务定义还不成熟,那么这就有问题了。如果模型由于控制的后端系统的改变而改变,那么通常情况下模型可以被区分为"低级"或"高级",只有后者通过 API 直接对客户可见。在语义版本控制术语中,对低级模型的更改可以成为向后兼容的 PATCH。
5.2.3 身份管理
运行时控制利用外部身份数据库(LDAP 服务器)存储用户数据,例如能够登录的用户的帐户名和密码,LDAP 服务器还具有将用户与组关联的功能。例如,将管理员添加到AetherAdmin
组将是在运行时控制中授予个人管理权限的一种明显的方法。
外部认证服务 Keycloak 用作数据库(如 LDAP)的前端,对用户进行身份验证,处理接受密码、验证密码的机制,并安全返回用户所属的组。
延伸阅读:
然后,使用组标识符授予对运行时控制子系统内资源的访问权,这将指向相关的问题,即确定哪类用户被允许创建/读/写/删除各种对象集合。与身份管理一样,定义这样的 RBAC 策略是很容易理解的,并得到开源工具的支持。对于 Aether,Open Policy Agent (OPA)扮演了这个角色。
延伸阅读:
5.2.4 适配器
并非每个服务以及运行时控制下面的子系统都支持 gNMI,在不支持 gNMI 的情况下,需要编写适配器来翻译 gNMI 和服务本地 API 之间的交互。以 Aether 为例,一个 gNMI->REST 适配器在运行时控制的南向 gNMI 调用和 SD-Core 子系统的 REST 北向接口之间进行转换。适配器不一定只是一个语法翻译器,还可能包括自己的语义层。这支持存储在 x-config 中的模型与南向设备/服务使用的接口之间的逻辑解耦,允许南向设备/服务和运行时控制独立发展,还允许南向设备/服务的替换不影响北向接口。
适配器不一定只支持单一服务。适配器采取的是一种跨越多个服务的抽象手段,并将其应用于每个服务中。Aether 中的一个例子是用户平面功能(SD-Core 用户平面中的主要数据包转发模块)和 SD-Core,它们共同负责确定服务质量,其中适配器将一套单一的模型应用于两个服务。需要注意处理部分失败的情况,即一个服务接受变化,但另一个不接受。在这种情况下,适配器会继续尝试失败的后端服务,直到它成功。
5.2.5 工作流引擎
图 22 中 x-config 左边的工作流引擎是实现多步工作流的地方。例如,定义新的 5G 连接或将设备与现有连接关联是一个多步骤的过程,需要使用多个模型并影响多个后端子系统。根据我们的经验,甚至可能有复杂的状态机来实现这些步骤。
有一些众所周知的开源工作流引擎(例如,Airflow),但我们的经验是,它们与 Aether 等系统典型的工作流类型不匹配。因此,当前采用了特别的实现,使用命令式代码监视目标模型集,并在它们发生更改时采取适当的行动。为工作流定义更严格的方法是正在进行中的课题。
5.2.6 安全通信
gNMI 自然适合使用双向 TLS 进行身份验证,这是使用 gNMI 的组件之间安全通信的推荐方式。例如,x-config 与其适配器之间的通信使用 gNMI,因此使用双向 TLS。在组件之间分发证书是运行时控制范围之外的问题,我们假定另一个工具将负责分发、撤销和更新证书。
对于使用 REST 的组件,HTTPS 用于保护连接,可以使用 HTTPS 协议中的机制进行身份验证(基本身份验证、令牌等)。当使用这些 REST API 时,Oauth2 和 OpenID Connect 被用作授权提供者。
5.3 连接服务建模
本节概述了 Aether 连接服务的数据模型,以说明运行时控制扮演的角色。这些模型是在 YANG 中指定的(我们为其中一个模型提供了具体示例),但由于运行时控制 API 是由这些规范生成的,它同样有效的考虑了支持 REST 的 GET, POST, PUT, PATCH 和 DELETE 操作的一组 web 资源(对象):
GET: 获取对象。
POST: 创建对象。
PUT, PATCH: 更改对象。
DELETE: 删除对象。
每个对象都是 YANG 定义的某个模型的实例,每个对象都包含用于标识对象的 id 字段。这些标识符是特定于模型的,例如,站点有 site-id,而企业有 enterprise-id。模型通常是嵌套的,例如,站点是企业的成员。对象还可以包含对其他对象的引用,这样的引用是基于对象的唯一 id 实现的。在数据库设置中,通常称为外键(foreign keys)。
除了 id 字段之外,其他几个字段对于所有模型也是通用的。这些包括:
description: 人类可读的描述,用于存储关于对象的附加上下文。
display-name: 用于在 GUI 中显示人类可读的名称。
因为这些字段对所有模型都是通用的,所以将在接下来的模型介绍中省略。在下面的例子中,我们用大写来表示模型(例如,Enterprise),用小写表示模型中的字段(例如,enterprise)。
5.3.1 企业(Enterprises)
Aether 部署在企业中,因此定义了一组具有代表性的组织抽象。其中包括 Enterprise,它形成了特定于客户的层次结构的根。Enterprise 模型是许多其他对象的父对象,并允许将这些对象限定在特定的 Enterprise 范围内,以实现所有权和基于角色的访问控制目的。Enterprise 模型包含以下字段:
connectivity-service: 为该企业实现连接性的后端子系统列表,对应 SD-Core、SD-Fabric 和 SD-RAN 的 API 端点。
Enterprises 进一步分为 Sites。站点是 Enterprise 的接入点,可以是物理的,也可以是逻辑的(例如,一个地理位置可以包含多个逻辑站点)。Site 模型包含以下字段:
imsi-definition: 如何为本站点建立 IMSI 的描述,包含以下子字段:
mcc: Mobile country code。
mnc: Mobile network code。
enterprise: 数字表示的 enterprise id。
format: 允许上述三个字段嵌入到 IMSI 中的掩码。例如,CCCNNNEEESSSSSS 将使用 3 位 MCC、3 位 MNC、3 位 ENT 和 6 位 subscriber 构建 IMSI。
small-cell: 5G 无线基站或接入点或无线电的列表,每个 small cell 有以下内容:
small-cell-id: small cell 的标识符,与其他 id 字段的用途相同。
address: small cell 的主机名。
tac: Type Allocation Code。
enable: 如果设置为 true,启用 small cell,否则为禁用。
imsi-definition 是特定于移动蜂窝网络的,对应于刻入每一张 SIM 卡的唯一标识符。
5.3.2 切片(Slices)
Aether 将 5G 连接建模为一个 Slice,表示一个隔离的通信通道(和相关的 QoS 参数),该通道将一组设备(建模为一个 Device-Group)连接到一组应用程序(每个应用程序建模为一个 Application)。每个 slice 都嵌套在某个 site 中(该 site 又嵌套在某个 enterprise 中),例如,企业可能配置一个切片承载 IoT 流量,另一个切片承载视频流量。Slice 模型有以下字段:
device-group: 可以加入此 Slice 的 Device-Group 对象列表。列表中的每个条目都包含对 Device-Group 的引用以及 enable 字段,该字段可能用于临时删除对组的访问。
app-list: 这个 Slice 允许或拒绝的 Application 对象列表。列表中的每个条目都包含对 Application 的引用以及 allow 字段,该字段可以设置 true 或者 false 以允许或拒绝应用程序接入。
template: 对用来初始化这个 Slice 的 Template 的引用。
upf: 索引用于处理该 Slice 报文的 UPF(User Plane Function),允许多个 Slices 共享一个 UPF。
sst, sd: 3GPP 定义的切片标识符,由运维团队分配。
mbr.uplink, mbr.downlink, mbr.uplink-burst-size, mbr.downlink-burst-size: 该切片所有设备的最大比特率和峰值大小。
使用选定的模板(template) 初始化与速率相关的参数,如下所述。还要注意,这个例子说明了如何使用建模来强制定义不变量,在这种情况下,UPF 和 Device-Group 的 Site 必须匹配 Slice 的 Site。也就是说,连接到切片的物理设备和实现切片的核心段的 UPF 必须被限制在一个物理位置内。
Slice 的一端是 Device-Group,标识一组允许使用切片连接到各种应用程序的设备。Device-Group 模型包含以下字段:
devices: 设备列表,每个设备都有 enable 字段,可以用来启用或禁用设备。
ip-domain: 引用 IP-Domain 对象,描述该组内终端的 IP 和 DNS 设置。
mbr.uplink, mbr.downlink: 设备组的每设备最大比特率。
traffic-class: 该组中设备使用的流量类。
Slice 的另一端是 Application 对象列表,指定程序设备通信的端点。Application 模型包含以下字段:
address: 终端的 DNS 名称或 IP 地址。
endpoint: 端点列表,每个都有以下字段:
name: 端点名称,用作 key。
port-start: 起始端口号。
port-end: 结束端口号。
protocol: 端点的协议(TCP|UDP)。
mbr.uplink, mbr.downlink: 应用程序端点的每设备最大比特率。
traffic-class: 与此应用程序通信的设备的流量类。
熟悉 3GPP 的人都知道 Aether 的 Slice 抽象类似于规范中的网络切片概念。Slice 模型定义包括 3GPP 指定的标识符(例如,sst 和 sd)的组合,以及底层实现的细节(例如,upf 表示核心网用户平面的 UPF 实现)。虽然还不是生产系统的一部分,但有一个版本的 Slice 也包括了与 RAN 切片相关的字段,其中的运行时控制子系统负责将 RAN、Core 和 Fabric 之间的端到端连接无缝整合在一起。
平台服务 API
我们使用 Connectivity-as-a-Service 作为运行时控制扮演的角色的一个示例,但也可以使用相同的机制为其他平台服务定义 API。例如,由于 Aether 中的 SD-Fabric 是用可编程交换硬件实现的,转发平面是用带内网络遥测(INT)仪表化的,北向 API 支持在运行时对每个流进行细粒度数据收集,这使得在 Aether 之上编写闭环控制应用程序成为可能。
本着类似的精神,本节中给出的与 QoS 相关的控制示例可以通过附加对象进行扩展,这些对象提供了对 SD-RAN 实现的各种无线电相关参数的可见性和控制机会。这样做将是向平台 API 迈出的一步,该 API 有助于实现一类新的行业自动化边缘云应用程序。
一般来说,IaaS 和 PaaS 产品需要支持面向应用程序和用户的 API,这些 API 超出了底层软件组件(即微服务)所使用的 DevOps 级配置文件。创建这些接口是定义有意义的抽象层的练习,如果使用声明性工具,就变成了定义高级数据模型的练习。运行时控制是管理子系统,负责为这样的抽象层指定和实现 API。
5.3.3 模板和流量类
与每个 Slice 相关联的是 QoS 相关配置文件,该配置文件管理如何处理该 Slice 携带的流量。从 Template 模型开始,定义了有效的(可接受的)连接设置。Aether 运维团队负责定义这些(他们提供的特性必须得到后端子系统的支持),企业可以选择想要应用于创建的连接性服务的任何实例的模板(例如,通过下拉菜单)。也就是说,模板被用来初始化 Slice 对象。Template 模型有以下字段:
sst, sd: 切片标识符,由 3GPP 指定。
mbr.uplink, mbr.downlink: 最大上下行带宽。
mbr.uplink-burst-size, mbr.downlink-burst-size: 最大峰值大小。
traffic-class: 链接到描述流量类的 Traffic-Class 对象。
注意,Device-Group 和 Application 模型也包含类似字段。其理念是,将 QoS 参数作为一个整体(基于所选 template)建立起来,然后连接到该切片的各个设备和应用程序可以在逐个实例的基础上分配自己的、限制性更强的 QoS 参数。
如前一节所述,Aether 将抽象 Slice 对象从端到端切片的后端段的实现细节中分离出来。这种解耦的一个原因是,支持选择一个全新的 SD-Core 副本,而不是与另一个 Slice 共享同一个 UPF。这样做是为了确保隔离,并说明了运行时控制和生命周期管理子系统之间可能的接触点: 运行时控制通过适配器,与生命周期管理一起启动必要的 Kubernetes 容器集,以实现隔离的切片。
Traffic-Class 模型指定了流量的分类,包括以下字段:
arp: 分配和保留优先级(Allocation and retention priority)。
qci: QoS 类标识符(QoS class identifier)。
pelr: 丢包率(Packet error loss rate)。
pdb: 包时延预算(Packet delay budget)。
完整起见,下面显示了 Template 模型对应的 YANG。为了简单起见,示例省略了一些介绍性的样板文件。该示例突出了模型声明的嵌套性质,包括container
字段和leaf
字段。
5.3.4 其他模型
上述介绍参考了其他模型,我们在此不做全面描述。其中包括 IP-Domain,指定 IP 和 DNS 设置,以及 UPF,指定用户平面功能(SD-Core 的数据平面组件),代表连接服务的特定实例转发数据包。UPF 模型是必要的,因为一个 Aether 部署可以运行许多 UPF 实例。有两种不同的实现方式(一种是作为服务器上的微服务运行,另一种是作为加载到交换网络中的 P4 程序运行),而且在任何时候都可以实例化多个基于微服务的 UPF,每个都隔离不同的流量。
延伸阅读:
L. Peterson, et al. Software-Defined Networks: A Systems Approach. November 2021.
5.4 重温 GitOps
正如我们在第四章末尾所做的那样,重温如何区分配置状态和控制状态的问题是有意义的,生命周期管理(和它的配置存储库)负责前者,而运行时控制(和它的键值存储)负责后者。现在我们已经更详细的了解了运行时控制子系统,很明显,关键因素是访问和更改该状态是否需要通过编程接口(加上访问控制机制)。
云运营商和 DevOps 团队完全有能力将配置变化签入配置存储库中,这可能会使人们倾向于将所有在配置文件中指定的状态视为生命周期管理的配置状态。增强的配置机制的可用性,如 Kubernetes Operators,使这种诱惑力更大。但是,任何可能被运维人员以外的人触及的状态,包括企业 IT 管理员和运行时控制应用程序,都需要通过定义明确的 API 来访问。给予企业设置隔离和 QoS 参数的能力是 Aether 的一个说明性例子。从一组模型中自动生成 API 是实现这种控制接口的一个有吸引力的方法,不考虑其他原因的话,至少它迫使接口定义与底层实现解耦(用适配器来弥补这个差距)。
在后一点上,我们很容易想象一种运行时控制操作的实现,包括将配置变更签入配置存储库中并触发重新部署。认为这样的方法是优雅的还是笨拙的这是一个品味问题,但如何做出这样的工程决策,很大程度上取决于后端组件的实现方式。例如,如果配置变更需要重新启动容器,那么可能没有什么选择。但理想情况是,微服务实现有自己明确的管理接口,可以被初始化时的操作者(启动初始化组件)或控制时的适配器(在运行时改变组件)调用。
对于与资源相关的操作上,如响应用户请求创建 Slice 或激活边缘服务而启动额外的容器,类似的实施策略是可行的。Kubernetes API 可以从 Helm(在启动时初始化微服务)或从运行时控制适配器(在运行时添加资源)调用。剩下的挑战是决定哪个子系统维护该状态的权威副本,并确保该决定作为系统的不变性而被强制执行<sup>[2]</sup>。这样的决策通常是根据情况而定的,但我们的经验是,使用运行时控制作为唯一真实来源是一个可靠的方法。
[2] 还可以维护状态的两个权威副本,并实现保持同步的机制。这种策略的困难在于如何避免绕过同步机制的后门访问。
当然,硬币有两面。提供配置参数的运行时控制也很有诱惑力,在一天结束的时候,只有云运维人员需要能够更改这些参数。配置 RBAC(例如,添加组并定义允许给定组访问哪些对象)是一个示例。除非有令人信服的理由向最终用户开放这样的配置决策,否则在配置存储库中维护 RBAC 相关配置状态(即 OPA 规范文件),在生命周期管理的权限下,是完全有意义的。
这些示例说明了运行时控制接口的核心价值主张,即扩展操作,让终端用户和闭环控制程序直接控制系统,而不需要运维团队充当中介。
用户体验方面的考虑
运行时控制涉及到云运维的一个重要方面,但往往没有得到充分重视: 考虑用户体验(UX)。如果你唯一关心的用户是云及其服务的开发人员和运营商,我们可以假设他们愿意编辑少量 YAML 文件来执行更改请求,那么也许我们可以就此停止。但是,如果我们希望最终用户有一定的能力来操纵我们正在构建的系统,还需要通过一组用户可以访问的仪表盘和按钮来"探测"我们已经实现的底层变量。
用户体验设计是一门成熟的学科。它在一定程度上是关于设计具有直观工作流程的 GUI,但 GUI 依赖于程序化的界面,定义这个界面是我们在本书中关注的管理和控制平台与我们想要支持的用户之间的接触点。这在很大程度上是一个定义抽象的练习,这使我们回到了想要表达的核心观点:是底层实现的现实和目标用户的心理模型塑造了这些抽象。正如任何读过用户手册的人所理解的那样,只考虑其中一个而不考虑另一个,将是一种灾难。
你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind
版权声明: 本文为 InfoQ 作者【俞凡】的原创文章。
原文链接:【http://xie.infoq.cn/article/9880fdb33755be4cc5fff8418】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论