写点什么

深入浅出边缘云 | 3. 资源配置

作者:俞凡
  • 2022 年 7 月 30 日
  • 本文字数:13297 字

    阅读完需:约 44 分钟

随着技术的发展以及应用对时延、带宽、安全的追求,一个明显的技术趋势是越来越多的应用组件将会被部署到企业所管理的网络边缘。本系列是开源电子书Edge Cloud Operations: A Systems Approach的中文版,详细介绍了基于开源组件构建的边缘云的架构、功能及具体实现。


第 3 章 资源配置

资源配置是指为应用准备好随时可用的虚拟或物理资源的过程,包含人工操作组件(机架和连接设备)以及引导组件(配置资源如何引导到"就绪"状态)。资源配置发生在首次部署云的时候,即对资源进行初始化,但随着时间的推移,由于添加新资源、删除或者升级旧资源,配置的资源也会随之改变。


资源配置的目标是零接触(zero-touch),由于硬件资源天然需要人工操作,因此是不可能的(我们稍后讨论分配虚拟资源的问题)。实际上,我们的目标是尽量减少物理连接设备之外所需配置步骤的数量和复杂性,请记住,我们需要配置的是从供应商那购买的商品硬件,而不是已经准备好的即插即用设备。


当我们从虚拟资源(例如,在商业云上实例化虚拟机)构建云时,"机架和连接"步骤是通过一系列 API 调用来完成的,而不需要技术人员亲自动手。当然,我们希望将激活虚拟基础设施所需的一系列调用都自动化,这就产生了被称为"基础设施即代码(Infrastructure-as-Code)"的方法,这是第二章中介绍的"配置即代码(Configuration-as-Code)"概念的特殊情况。一般想法是用一种可"执行"的声明式格式记录基础架构到底是什么样子的以及如何配置。我们使用 Terraform 作为"基础设施即代码"的开源方法。


当云由虚拟资源和物理资源组合构建(就像 Aether 这样的混合云)时,需要一种无缝的方式来容纳两者。为此,我们的方法是首先在硬件资源上覆盖一个逻辑结构(logical structure) ,使其大致相当于我们从商业云提供商获得的虚拟资源,这将产生类似图 11 所示的混合场景。我们使用 NetBox 作为我们的开源解决方案,在物理硬件上分层这个逻辑结构,NetBox 还帮助我们解决跟踪实物库存的需求。


图 11. 混合云中的资源配置,包括物理资源和虚拟资源。


注意,图 11 中右边显示的 Provisioning API 不是 NetBox API,Terraform 不直接与 NetBox 交互,而是与 3.1 节中介绍的硬件配置流程工件交互。考虑这一问题的方法是,将硬件引导到"就绪"状态的任务涉及安装和配置若干个共同构成云平台的子系统,Terraform 基于我们在 3.1 节末尾介绍的 API 与该平台交互。


本章从提供物理基础设施开始,介绍图 11 的两个方面。我们的方法是专注于初次配置整个站点的挑战。随着更多细节的出现,我们也将讨论更简单的增量配置单个资源的问题。

3.1 物理基础设施

将硬件设备放置到机架上的过程本质上是人力密集型的,需要考虑散热及电缆管理等因素,这些问题超出了本书范围。相反,我们关注的是"物理/虚拟"的边界,从布线规划开始,技术人员将其作为施工蓝图。这种规划的细节是高度特定于部署的,但我们使用图 12 所示示例帮助说明涉及的所有步骤。该示例通过在企业中部署 Aether 集群为例,有助于强调所需的专用性设施,通过大量规划指定适当的物料清单(BOM, Bill of Materials) ,其中包括了设备型号细节,但具体设备不在讨论范围之内。


图 12. 边缘集群布线规划示例。


图 12 所示蓝图实际上包括两个共享管理交换机和管理服务器的逻辑集群。上层集群对应生产部署,包含 5 台服务器和 2x2 叶脊交换结构。下层集群用于开发,包括两个服务器和一个交换机。这种硬件资源的逻辑分组并不是 Aether 独有的,我们也可以要求商业云提供商提供多个逻辑集群,因此能够对物理资源进行相同操作是很自然的需求。


除了遵循这个蓝图,技术人员还将物理基础设施的实际数据输入数据库中,以用于后续配置步骤,后面我们将详细介绍。

3.1.1 文档基础设施

在数据库中记录物理基础设施的逻辑结构是我们跨越物理到虚拟鸿沟的方式,涉及到为所收集的信息定义模型(该模型有效的表示了图 11 中所示逻辑结构),以及输入关于物理设备的相应信息。无论这是更大规模自动化框架(如本书中描述的)的第一阶段,还只是简单记录分配给每个网络设备的 IP 地址,这个过程对于任何负责管理设备网络的人来说都很熟悉。


有几种开源工具可用于此任务,我们的选择是 NetBox。该工具支持 IPAM (IP 地址管理),能记录关于设备类型及其安装位置的库存相关信息,维护集团和站点的基础设施架构,以及维护设备连接到控制台、网络和电源的信息。在 NetBox 网站上可以找到更多介绍。


延伸阅读:

NetBox: Information Resource Modeling Application.


NetBox 的关键特性之一是能够自定义用于组织收集到的所有信息的模型集。例如,运维人员可以定义机架(Rack)站点(Site) 这样的物理分组,也可以定义像组织(Organization)部署(Deployment) 这样的逻辑分组<sup>[1]</sup>。下面我们使用图 12 所示的 Aether 网络规划作为示例,重点关注配置单个 Aether 站点时发生的情况(但请记住,如第 2 章所述,Aether 可以跨越多个站点)。


[1] 在本节中,我们用斜体表示模型和模型字段(例如,Site, Address),并将指定给模型实例的特定值作为常量(例如,10.0.0.0/22)。


第一步是为准备的站点创建记录,记录该站点所有相关元数据,包括站点(Site) 名称(Name)位置(Location) ,以及站点所属组织(Organization) 。一个组织(Organization) 可以有多个站点(Site) ,而一个站点(Site) 可以(a)跨越一个或多个机架(Rack) ,(b)托管一个或多个部署(Deployment)部署(Deployment) 是一个逻辑集群,例如ProductionStagingDevelopment。图 12 所示的网络规划包括两个这样的部署。


这也是指定分配给特定边缘部署的 VLAN 和 IP 前缀的时间。由于维护 VLAN、IP 前缀和 DNS 域名(DNS 是自动生成的)之间的明确关系非常重要,因此浏览下面的具体示例很有帮助。我们从每个站点所需的最小 VLAN 集开始:


  • ADMIN 1

  • UPLINK 10

  • MGMT 800

  • FABRIC 801


这些都是 Aether 特有的,也说明了集群可能需要的一组 VLAN。至少,人们希望在任何集群中看到一个"管理"网络(本例中为 MGMT)和一个"数据"网络(本例中为 FABRIC)。同样针对 Aether(但具有通用性),如果站点上有多个部署共享一个管理服务器,则会添加额外的 VLAN (MGMT/FABRIC 的 id 加 10)。例如,第二个Development部署可能定义为:


  • DEVMGMT 810

  • DEVFABRIC 811


然后,IP 前缀与 VLAN 相关联,所有边缘 IP 前缀都可以放入一个/22子网中,然后以与 DNS 域名管理一致的方式对该子网进行分区。例如,域名是通过将设备(Device) 名(见下文)的第一个<devname>组件与此后缀组合生成的。以10.0.0.0/22为例,有 4 个边缘前缀,目的如下:


  • ADMIN 前缀为10.0.0.0/25 (用于 IPMI)

  • 有管理服务器和管理交换机

  • 分配给 ADMIN 的 VLAN 为 1

  • 域名设置为admin.<deployment>.<site>.aetherproject.net

  • MGMT 前缀为10.0.0.128/25 (用于基础设施控制平面)

  • 有服务器管理平面、交换机管理平面

  • 分配给 MGMT 的 VLAN 为 800

  • 域名设置为mgmt.<deployment>.<site>.aetherproject.net

  • FABRIC 前缀10.0.1.0/25(用于基础设施数据平面)

  • 计算节点到 Fabric 交换机以及其他 Fabric 连接的设备(例如,eNB)的qsfp0端口的 IP 地址

  • 分配给 FABRIC 的 VLAN 为 801

  • 域名设置为fab1.<deployment>.<site>.aetherproject.net

  • FABRIC 前缀10.0.1.128/25(用于基础设施数据平面)

  • 计算节点到 fabric 交换机的qsfp1口 IP 地址

  • 分配给 FABRIC 的 VLAN 为 801

  • 域名设置为fab2.<deployment>.<site>.aetherproject.net


Kubernetes 还使用其他不需要在 NetBox 中创建的边缘前缀。注意,本例中的qsfp0qsfp1表示连接交换 fabric 的收发器端口,其中 QSFP 表示 Quad(4 通道) Small Form-factor Pluggable。


记录了站点范围内的信息后,下一步是安装并记录每个设备(Device) 。包括输入<devname>,随后用于为设备生成完全合格的域名: <devname>.<deployment>.<site>.aetherproject.net。创建设备时还需要填写以下字段:


  • Site

  • Rack & Rack Position

  • Manufacturer

  • Model

  • Serial number

  • Device Type

  • MAC Addresses


注意,通常有一个主接口和一个管理接口(例如 BMC/IPMI)。Netbox 的一个便利特性是使用设备类型(Device Type) 作为模板,设置接口、电源连接和其他设备型号特定属性的默认命名。


最后,必须指定 Device 的虚拟接口,并将其 Label 字段设置为分配给它的物理网络接口。然后将 IP 地址分配给我们定义的物理和虚拟接口。管理服务器应该总是用每个前缀中的第一个 IP 地址,按照约定,按如下方式递增分配:


  • 管理服务器(Management Server)

  • eno1 - 站点提供的公网 IP 地址,如果由 DHCP 提供则为空

  • eno2 - 10.0.0.1/25 (ADMIN 的第一个),设置为主 IP

  • bmc - 10.0.0.2/25 (ADMIN 的下一个)

  • mgmt800 - 10.0.0.129/25 (MGMT 的第一个,在 VLAN 800 上)

  • fab801 - 10.0.1.1/25 (FABRIC 的第一个,在 VLAN 801 上)

  • 管理交换机(Management Switch)

  • gbe1 - 10.0.0.3/25 (ADMIN 的下一个),设置为主 IP

  • Fabric 交换机

  • eth0 - 10.0.0.130/25 (MGMT 的下一个),设置为主 IP

  • bmc - 10.0.0.131/25

  • 计算服务器(Compute Server)

  • eth0 - 10.0.0.132/25 (MGMT 的下一个),设置为主 IP

  • bmc - 10.0.0.4/25 (ADMIN 的下一个)

  • qsfp0 - 10.0.1.2/25 (FABRIC 的下一个)

  • qsfp1 - 10.0.1.3/25

  • 其他 Fabric 设备(eNB 等)

  • eth0或其他主接口 - 10.0.1.4/25 (FABRIC 的下一个)


一旦该数据输入到 NetBox 中,就可以用来生成如图 13 所示的机架图,对应于图 12 所示的布线图。请注意,图中显示了位于同一物理机架中的两个逻辑部署(ProductionDevelopment)。


图 13. NetBox 渲染机架配置。


还可以为部署生成其他有用的指标,帮助技术人员确认所记录的逻辑指标是否与实际物理表示相匹配。例如,图 14 显示了一组电缆,以及如何在我们的示例部署中连接硬件。


图 14. NetBox 电缆报告。


如果你觉得这些细节看起来过于冗长乏味,那你就了解到本节的要点了。云控制和管理自动化的一切都依赖于拥有相关资源的完整准确的数据,保持这些信息与物理基础设施的现实同步通常是此过程中最薄弱的环节。唯一可取之处是信息是高度结构化的,像 NetBox 这样的工具可以帮助我们维护这种结构。

3.1.2 配置和启动

在安装硬件并记录有关安装的相关事实之后,下一步是配置和引导硬件,以便为接下来的自动化过程做好准备。我们的目标是最小化图 12 所示物理基础设施所需的手动配置,但是零接触(zero-touch) 是一个很高的标准。为了说明这一点,完成示例部署的准备所需的引导步骤目前包括:


  • 配置管理交换机,使其知道正在使用的 VLAN 集。

  • 配置管理服务器,使其通过额外提供的 USB 密钥启动。

  • 运行必要的 Ansible 角色和剧本,在管理服务器上完成配置。

  • 配置计算服务器,从管理服务器(通过 iPXE)启动。

  • 配置 Fabric 交换机,从管理服务器(通过 Nginx)启动。

  • 配置 eNB(移动基站),使它们知道自己的 IP 地址。


这些都是手动配置步骤,需要控制台访问或将信息输入到设备 web 界面,这样任何后续配置步骤都可以完全灵活的自动化完成。请注意,虽然这些步骤不能自动完成,但也不一定必须现场执行,可以提前将准备发送到远程站点的硬件准备好。还要注意的是,不要使用可以稍后完成的配置来覆盖这一步骤。例如,在物理安装 eNB 时,可以在 eNB 上设置各种无线电参数,不过一旦集群联机,这些参数将通过管理平台进行设置。


应该尽量减少在这个阶段所做的手动配置工作,大多数系统应该使用自动化的配置方法。例如,广泛使用 DHCP 和 MAC 预留来分配 IP 地址,而不是手工配置每个接口,从而允许管理零接触,并简化未来的重配置。


配置的自动化方面被实现为一组 Ansible 角色(role)剧本(playbook) ,按照第二章图 6 所示高层概要,对应于代表"零接触配置(系统)"的方框。换句话说,我们没有现成的 ZTP 解决方案可以使用(也就是说,必须有人编写剧本),但是通过访问 NetBox 维护的所有配置参数,可以大大简化这一问题。


总体思路如下。对于每个需要配置的网络服务(如 DNS, DHCP, iPXE, Nginx)和每个设备子系统(如网络接口,Docker),都有对应的 Ansible 角色和剧本<sup>[2]</sup>。一旦管理网络联机,上面总结的各阶段手动配置就将被应用于管理服务器。


[2] 我们将忽略 Ansible 中角色(role)剧本(playbook) 之间的区别,而将重点放在脚本(script) 这一概念上,并通过一组输入参数运行这个脚本。


Ansible 剧本在管理服务器上安装和配置网络服务。DNS 和 DHCP 的作用显而易见。至于 iPXE 和 Nginx,它们被用来引导其余的基础设施。计算服务器通过 DHCP/TFTP 方式下发 iPXE 配置,然后在 Nginx web 服务器上加载脚本安装操作系统。fabric 交换机从 Nginx 加载 Stratum OS 包。


在大部分情况下,剧本将使用从 NetBox 提取的参数,例如 VLAN、IP 地址、DNS 名称等。图 15 说明了这种方法,并填充了一些细节。例如,一个自制的 Python 程序(edgeconfig.py)使用 REST API 从 NetBox 提取数据,并输出一组相应的 YAML 文件,精心制作成 Ansible 的输入,这在管理和计算系统上创建了更多的配置。其中一个例子是 Netplan 文件,它在 Ubuntu 中用于管理网络接口。关于 Ansible 和 Netplan 的更多信息可以在它们各自的网站上找到。


延伸阅读:

Ansible: Automation Platform.

Netplan: Network Configuration Abstraction Renderer.


图 15. 使用 NetBox 数据配置网络服务和 OS 级子系统。


虽然图 15 强调了 Ansible 是如何与 Netplan 配对来配置内核级细节的,但也有一个 Ansible 剧本用于在每个计算服务器和 fabric 交换机上安装 Docker,然后启动 Docker 容器,运行"finalize"镜像。该镜像调用配置栈的下一层,有效表明集群正在运行并准备好接受进一步指令。现在我们准备介绍这一层。

3.1.3 Provisioning API

到目前为止,根据我们所介绍的步骤,可以假设每个服务器和交换机都已启动并运行,但是仍然需要做一些工作为配置栈中的下一层准备裸金属集群,本质上是在图 11 所示的混合云的左右两边之间建立对应关系。如果你问自己"谷歌会怎么做?"这就减少了为裸金属边缘云设置类似 GCP API 的任务。这个 API 主要包含了 Kubernetes API,但不仅提供了使用 Kubernetes 的方法,还包括了管理 Kubernetes 的调用。


简而言之,这个"管理 Kubernetes"的任务就是把一组相互连接的服务器和交换机变成一个完全实例化的 Kubernetes 集群。对于初学者来说,API 需要提供一种在每个物理集群上安装和配置 Kubernetes 的方法,包括指定运行哪个版本的 Kubernetes,选择正确的容器网络接口(CNI)插件(虚拟网络适配器)组合,以及将 Kubernetes 连接到本地网络(以及可能需要的任何 VPN)。这一层还需要提供一种方法来设置访问和使用每个 Kubernetes 集群的帐户(和相关凭据),以及管理部署在给定集群上的独立项目的方法(例如为多个应用程序管理名称空间)。


例如,Aether 目前使用 Rancher 管理裸金属集群上的 Kubernetes, Rancher 的一个集中化实例负责管理所有边缘站点。这将产生如图 16 所示的配置,为了强调 Rancher 的范围,显示了多个边缘集群。虽然图中没有显示,但 GCP 提供的 API,就像 Rancher 一样,也跨越了多个物理站点(例如,us-west1-aeurope-north1-basia-south2-c,等等)。


图 16. 混合云中的配置,包括一个用于管理运行在多个裸金属集群上的 Kubernetes 的 API 层。


最后需要指出的是,虽然我们经常把 Kubernetes 当作全行业标准来对待,但事实并非如此,每个云供应商都提供自己的定制版本:


  • Microsoft Azure 提供了 Azure Kubernetes Service (AKS)

  • AWS 提供了 Amazon Elastic Kubernetes Service (EKS)

  • Google Cloud 提供了 Google Kubernetes Engine (GKE)

  • Aether 边缘云运行 Rancher 认证的 Kubernetes 版本 (RKE)


虽然 CNCF(云原生计算基金会,负责管理 Kubernetes 项目的开源组织)对这些版本和其他版本的 Kubernetes 进行了认证,但这只是建立了一致性基线。每个版本都可以在此基础上进行改进,这些改进通常以附加特性的形式提供并控制 Kubernetes 集群。我们在云管理层的工作是为运营商提供一种管理这种异构性的方法。正如我们将在第 3.2 节中看到的,这是基础设施即代码层解决的主要挑战。

3.1.4 配置虚拟机

通过考虑提供虚拟机(VM)的含义,我们结束了对配置物理机所需步骤的讨论。当我们向 AKS、EKS 或 GKE 请求 Kubernetes 集群时,这是"幕后"发生的事情,因为超大规模云服务商可以选择将 Kubernetes 服务分层放在他们的基础设施即服务(IaaS)之上。我们正在构建的边缘云也需要类似的东西吗?


不一定。因为我们的目标是支持一组精心策划的边缘服务,为企业用户提供价值,而不是支持不受信任的第三方启动他们想要的任何应用程序的容器即服务(Container-as-a-Service),所以不需要"作为服务"来管理 VM。但是,我们仍然可能希望使用 VM 作为一种将 Kubernetes 的工作负载隔离在有限数量的物理服务器上的方法。这可以作为配置的一个步骤,类似于连接和引导物理机,但使用 KVM 和 Proxmox 等虚拟化机制来完成,而不需要类似 OpenStack 这样成熟的 IaaS 机制。然后,这些 VM 将被记录为 NetBox 和本节介绍的其他工具中的一级云资源,与物理机器没有区别。


考虑到 Kubernetes 允许我们在单个集群上部署多个应用程序,为什么要这样做呢?这个问题没有固定答案。一个原因是支持细粒度的资源隔离,从而可以(a)确保每个 Kubernetes 应用能够获取完成工作所需的处理器、内存和存储资源,(b)减少应用程序之间的信息泄露风险。例如,假设除了 SD-Fabric、SD-RAN 和 SD-Core 工作负载(默认情况下)运行在每个边缘站点之外,我们还想运行一个或多个其他边缘应用,比如在 2.3 节中介绍的 OpenVINO 平台。为了确保这些应用程序之间不存在干扰,可以为每个应用专门部署一个物理服务器子集。物理分区是共享物理集群的粗粒度方法。通过实例化 VM,能够在多个应用之间"分割"一个或多个服务器,这为运维人员分配资源提供了更大的灵活性,通常意味着更少的总体资源需求。请注意,还有其他方法可以指定如何在应用程序之间共享集群资源(我们将在第 4.4 节中看到),但是配置(provisioning)层是可以解决这个问题的一个选项。

3.2 基础设施即代码(Infrastructure-as-Code)

刚刚介绍的 Kubernetes 配置接口包含可编程 API、命令行接口(CLI)和图形用户界面(GUI)。如果你尝试了本书推荐的任何教程,可能会使用后两种教程中的一种。然而,对于运维部署来说,让运维人员与 CLI 或 GUI 交互是有问题的,不仅因为人类容易出错,还因为几乎不可能始终如一地重复一系列配置步骤。能够持续重复这个过程是下一章所介绍的生命周期管理的核心。


解决方案是以声明式语法定义基础架构,包含需要实例化的 Kubernetes 集群信息(例如,一部分运行在裸金属上的边缘集群,一部分在 GCP 中实例化),以及相关配置信息,然后自动化调用可编程 API。这是"基础设施即代码"的本质,正如前面说的,我们使用 Terraform 作为开源示例。


由于 Terraform 规范是声明式的,所以理解的最佳方法是浏览特定示例。这样做的目的不是记录 Terraform(对更详细的内容感兴趣的人可以使用在线文档和循序渐进的教程),而是建立关于该层在管理云方面所扮演角色的直觉。


延伸阅读:

Terraform Documentation.

Terraform Getting Started Tutorials.


为了理解示例,关于 Terraform 配置语言,其主要内容在于提供了一种方法(1)为不同类型的资源指定模板(这些是.tf文件),(2)为这些资源模板的特定实例填充变量(这些是.tfvars文件)。然后给定一组.tf.tfvars文件,Terraform 实现两阶段过程。第一阶段,基于执行的前一个计划以来发生的变化构建执行计划。第二阶段,Terraform 执行一系列任务,使底层基础设施符合最新定义的规格说明。请注意,目前我们的工作是编写这些规格文件,并将它们签入配置存储库(Config Repo)。在第 4 章中,Terraform 将作为 CI/CD 流水线的一部分被调用。


现在来看具体的文件。在最上层,运维人员定义了计划合并到基础设施中的供应商(provider) 集合。我们可以认为每个供应商对应于一个云后端,提供了图 16 中介绍的相应配置 API。在我们的示例中,只展示两个供应商: Rancher 管理的边缘集群和 GCP 管理的集中式集群。注意,示例文件为每个供应商声明了一组相关变量(例如urlaccess-key),这些变量由下面介绍的特定实例的变量文件"填充"。


terraform {  required_version = ">= 0.13"  required_providers {    rancher2 = {      source  = "rancher/rancher2"      version = "= 1.15.1"    }    google = {      source  = "hashicorp/google"      version = "~> 3.65.0"    }    null = {      source  = "hashicorp/null"      version = "~> 2.1.2"    }  }}
variable "rancher" { description = "Rancher credential" type = object({ url = string access_key = string secret_key = string })}
variable "gcp_config" { description = "GCP project and network configuration" type = object({ region = string compute_project = string network_project = string network_name = string subnet_name = string })}
provider "rancher2" { api_url = var.rancher.url access_key = var.rancher.access_key secret_key = var.rancher.secret_key}
provider "google" { # Provide GCP credential using GOOGLE_CREDENTIALS environment variable project = var.gcp_config.compute_project region = var.gcp_config.region}
复制代码


下一步是为我们希望配置的实际集群集填充详细信息(定义值)。让我们来看两个示例,对应于刚才指定的两个供应商。第一个显示了由 GCP 托管的集群(名为amp-gcp),托管 AMP 工作负载。(类似的有一个sdcore-gcp托管 SD-Core 实例。)Terraform 通过给特定集群分配相关标签(例如,env = "production")和管理堆栈的其他层(根据相关标签有选择的采取不同的操作)之间建立联系,我们将在第 4.4 节中看到使用这些标签的示例。


cluster_name = "amp-gcp"cluster_nodes = {  amp-us-west2-a = {    host        = "10.168.0.18"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  },  amp-us-west2-b = {    host        = "10.168.0.17"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  },  amp-us-west2-c = {    host        = "10.168.0.250"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  }}cluster_labels = {  env          = "production"  clusterInfra = "gcp"  clusterRole  = "amp"  k8s          = "self-managed"  backup       = "enabled"}
复制代码


第二个示例展示了一个在 Site X 上实例化的边缘集群(名为ace-X)。在示例代码中可以看到,这是一个由 5 个服务器和 4 个交换机(两个叶交换机和两个脊交换机)组成的裸金属集群。每个设备的地址必须与 3.1 节中介绍的硬件配置阶段分配的地址相匹配。理想情况下,该节中介绍的 NetBox(以及相关的)工具链将自动生成 Terraform 变量文件,但在实践中,通常仍然需要手动输入数据。


cluster_name  = "ace-X"cluster_nodes = {  leaf1 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.133"    roles       = ["worker"]    labels      = ["node-role.aetherproject.org=switch"]    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]  },  leaf2 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.137"    roles       = ["worker"]    labels      = ["node-role.aetherproject.org=switch"]    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]  },  spine1 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.131"    roles       = ["worker"]    labels      = ["node-role.aetherproject.org=switch"]    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]  },  spine2 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.135"    roles       = ["worker"]    labels      = ["node-role.aetherproject.org=switch"]    taints      = ["node-role.aetherproject.org=switch:NoSchedule"]  },  server-1 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.138"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  },  server-2 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.139"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  },  server-3 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.140"    roles       = ["etcd", "controlplane", "worker"]    labels      = []    taints      = []  },  server-4 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.141"    roles       = ["worker"]    labels      = []    taints      = []  },  server-5 = {    user        = "terraform"    private_key = "~/.ssh/id_rsa_terraform"    host        = "10.64.10.142"    roles       = ["worker"]    labels      = []    taints      = []  }}cluster_labels = {  env          = "production"  clusterInfra = "bare-metal"  clusterRole  = "ace"  k8s          = "self-managed"  coreType     = "4g"  upfType      = "up4"}
复制代码


最后一块拼图是填写关于如何实例化每个 Kubernetes 集群的其余细节。本例中,我们只展示用于配置边缘集群的特定于 RKE 模块,如果你理解 Kubernetes,那么其中大部分细节都很简单。例如,该模块指定每个边缘集群应该加载calicomultus CNI 插件,还定义了如何调用kubectl根据这些规范配置 Kubernetes。也许对 SCTPSupport 的引用会比较陌生,这表明特定的 Kubernetes 集群是否需要支持 SCTP, SCTP 是一种面向电信的网络协议,并不包含在普通 Kubernetes 部署中,但 SD-Core 需要。


terraform {  required_providers {    rancher2 = {      source  = "rancher/rancher2"    }    null = {      source  = "hashicorp/null"      version = "~> 2.1.2"    }  }}
resource "rancher2_cluster" "cluster" { name = var.cluster_config.cluster_name
enable_cluster_monitoring = false enable_cluster_alerting = false
labels = var.cluster_labels
rke_config { kubernetes_version = var.cluster_config.k8s_version
authentication { strategy = "x509" }
monitoring { provider = "none" }
network { plugin = "calico" }
services { etcd { backup_config { enabled = true interval_hours = 6 retention = 30 } retention = "72h" snapshot = false }
kube_api { service_cluster_ip_range = var.cluster_config.k8s_cluster_ip_range extra_args = { feature-gates = "SCTPSupport=True" } }
kubelet { cluster_domain = var.cluster_config.cluster_domain cluster_dns_server = var.cluster_config.kube_dns_cluster_ip fail_swap_on = false extra_args = { cpu-manager-policy = "static" kube-reserved = "cpu=500m,memory=256Mi" system-reserved = "cpu=500m,memory=256Mi" feature-gates = "SCTPSupport=True" } }
kube_controller { cluster_cidr = var.cluster_config.k8s_pod_range service_cluster_ip_range = var.cluster_config.k8s_cluster_ip_range extra_args = { feature-gates = "SCTPSupport=True" } }
scheduler { extra_args = { feature-gates = "SCTPSupport=True" } }
kubeproxy { extra_args = { feature-gates = "SCTPSupport=True" proxy-mode = "ipvs" } } } addons_include = ["https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/release-3.7/images/multus-daemonset.yml"] addons = var.addon_manifests }}
resource "null_resource" "nodes" { triggers = { cluster_nodes = length(var.nodes) }
for_each = var.nodes
connection { type = "ssh"
bastion_host = var.bastion_host bastion_private_key = file(var.bastion_private_key) bastion_user = var.bastion_user
user = each.value.user host = each.value.host private_key = file(each.value.private_key) }
provisioner "remote-exec" { inline = [<<EOT ${rancher2_cluster.cluster.cluster_registration_token[0].node_command} \ ${join(" ", formatlist("--%s", each.value.roles))} \ ${join(" ", formatlist("--taints %s", each.value.taints))} \ ${join(" ", formatlist("--label %s", each.value.labels))} EOT ] }}
resource "rancher2_cluster_sync" "cluster-wait" { cluster_id = rancher2_cluster.cluster.id
provisioner "local-exec" { command = <<EOT kubectl set env daemonset/calico-node \ --server ${yamldecode(rancher2_cluster.cluster.kube_config).clusters[0].cluster.server} \ --token ${yamldecode(rancher2_cluster.cluster.kube_config).users[0].user.token} \ --namespace kube-system \ IP_AUTODETECTION_METHOD=${var.cluster_config.calico_ip_detect_method} EOT }}
复制代码


还有其他一些松耦合端点需要绑定,例如定义用于连接边缘集群到 GCP 中对应节点的 VPN,但是上面示例足以说明基础设施即代码在云管理堆栈中所扮演的角色。关键是 Terraform 处理的所有事情都可以由人工运维人员在后端配置 API 上通过一系列 CLI 命令(或 GUI 点击)来完成,但经验表明,这种方法容易出错,而且难以重复。从声明式语言开始并自动生成正确的 API 调用序列是克服这个问题的一种经过验证的方法。


最后请注意这样一个事实: 虽然我们现在为云基础设施定义了一个声明性规范,我们称之为 Aether 平台,但这些规范文件是我们在配置存储库中签入的一个软件工件。这就是我们所说的"基础架构即代码": 基础架构规范被签入到存储库中,并像任何其他代码一样接受版本控制。这个存储库反过来为下一章介绍的生命周期管理流水线提供了输入。3.1 节中介绍的物理配置步骤发生在流水线的"外部"(这就是为什么我们不只是将资源配置加入生命周期管理),但将资源配置看作生命周期管理的"阶段 0"是比较公平的定义。

3.3 平台定义

定义系统架构(在我们的例子中是混合云的管理框架)的艺术在于决定在平台中包含什么以及在平台上运行的应用程序之间划清界限。对于 Aether,我们决定在平台中包含 SD-Fabric(以及 Kubernetes),而 SD-Core 和 SD-RAN 被视为应用程序,尽管这三者都是作为基于 Kubernetes 的微服务实现的。这个决定的后果是 SD-Fabric 被初始化为本章介绍的配置系统的一部分(与 NetBox、Ansible、Rancher 和 Terraform 扮演角色共同完成),而 SD-Core 和 SD-RAN 是基于第 4 章介绍的应用级机制部署。


可能还有其他边缘应用作为 Kubernetes 工作负载运行,这使情况变得更加复杂,因为从他们的角度来看,所有 Aether 组件(包括 SD-Core 和 SD-RAN 实现的 5G 连接)都假定是平台的一部分。换句话说,Aether 划了两条线,一条划分了 Aether 基础平台(Kubernetes 加上 SD-Fabric),另一条划分了 Aether PaaS(包括运行在平台上的 SD-Core 和 SD-RAN,加上管理整个系统的 AMP)。"基础平台"和"PaaS"之间的区别很细微,但本质上分别对应于软件堆栈和托管服务。


从某些方面来说,这只是一个术语问题,当然也很重要,不过与我们的讨论相关的是,由于有多个重叠机制,因此我们有不止一种方法来解决遇到的每个工程问题,从而很容易对可分离关注点实现不必要的合并而结束。明确、一致的界定什么是平台、什么是应用是健全的整体设计的先决条件。同样重要的是要认识到内部工程决策(例如,使用什么机制来部署给定组件)和外部可见的体系架构决策(例如,通过公共 API 公开什么功能)之间的区别。


你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。

微信公众号:DeepNoMind

发布于: 2022 年 07 月 30 日阅读数: 62
用户头像

俞凡

关注

公众号:DeepNoMind 2017.10.18 加入

俞凡,Mavenir Systems研发总监,关注高可用架构、高性能服务、5G、人工智能、区块链、DevOps、Agile等。公众号:DeepNoMind

评论

发布
暂无评论
深入浅出边缘云 | 3. 资源配置_架构_俞凡_InfoQ写作社区