使用 GitOps 的多群集环境中的工作负荷管理

开发现代云原生应用程序通常包括跨一组 Kubernetes 群集生成、部署、配置和提升工作负载。 随着 Kubernetes 群集类型的日益多样化以及应用程序和服务的多样化,该过程可能会变得复杂且不可缩放。 企业组织可以通过建立良好定义的结构来组织人员及其活动,并通过使用自动化工具,在这些工作中取得更大的成功。

本文将指导你完成一个典型的业务方案,其中概述了组织在多群集环境中管理云原生工作负载时经常面临的相关角色和主要挑战。 它还提出了一种体系结构模式,可以简化这一复杂过程,并增强其可观测性和可伸缩性。

场景概述

本文介绍了开发云原生应用程序的组织。 任何应用程序都需要计算资源来处理工作。 在云原生环境中,此计算资源是 Kubernetes 群集。 组织可能具有单个群集,或者更常见的是多个群集。 因此,组织必须决定哪些应用程序应在哪些群集上运行。 换句话说,他们必须在群集之间调度应用程序。 此决策或调度的结果是一个模型,表示群集在其环境中应达到的目标状态。 当它们具有该模型时,他们需要将应用程序传送到分配的群集,以便它们可以将所需状态转换为现实,或者换句话说,对其进行协调。

每个应用程序都会经历一个软件开发生命周期,以将它提升到生产环境。 例如,生成一个应用程序,部署到开发环境,测试并提升到阶段环境,经过测试,最后交付到生产环境。 对于云原生应用程序,应用程序在其整个生命周期中需要并面向不同的 Kubernetes 群集资源。 此外,应用程序通常需要群集来提供一些平台服务(例如 Prometheus 和 Fluentbit)以及基础结构配置(如网络策略)。

根据应用程序,部署应用程序的群集类型可能非常多样化。 具有不同配置的同一应用程序可以托管在云中的托管群集、本地环境中的连接群集、工厂生产线或军用无人机半连接边缘设备上的一组群集上,以及星际飞船上的气隙群集上。 另一个复杂性在于,开发人员通常会在开发和质量保证等早期生命周期阶段管理群集,而组织的客户可能会负责将这些群集与实际生产群集对接。 在后一种情况下,开发人员可能只负责跨不同圈推广和计划应用程序。

规模化挑战

在具有单个应用程序和少量作的小型组织中,可以使用少量脚本和管道手动处理这些过程。 但对于运营规模更大的企业组织来说,这可能是一个真正的挑战。 这些组织通常会生成数百个应用程序,这些应用程序面向数百种群集类型,并由数千个物理群集提供支持。 在这些情况下,使用脚本手动处理此类作不可行。

若要在多群集环境中大规模执行这种类型的工作负荷管理,需要以下功能:

  • 对计划和协调进行关注点的分离
  • 通过一系列环境推进多集群状态
  • 复杂、可扩展且可替换的调度程序
  • 根据其性质和连接性,为不同的群集类型灵活使用不同的协调程序
  • 大规模平台配置管理

情境角色

在描述方案之前,我们首先澄清涉及到哪些角色、他们承担的职责,以及角色之间如何相互交互。

平台团队

平台团队管理托管应用程序团队生成的群集。

平台团队的主要职责包括:

  • 定义过渡环境(Dev、QA、UAT、Prod)。
  • 定义群集类型及其跨环境分布。
  • 配置新群集。
  • 跨集群管理基础设施配置。
  • 维护应用程序使用的平台服务。
  • 在群集上计划应用程序和平台服务。

应用程序团队

应用程序团队管理其应用程序的软件开发生命周期(SDLC)。 它们提供 Kubernetes 清单,以描述如何将应用程序部署到不同的目标。 他们负责管理 CI/CD 管道,以创建容器映像和 Kubernetes 清单,并在不同环境阶段中推广部署工件。

通常,应用程序团队不知道要部署到的群集。 他们不知道其他团队执行的多群集环境、全局配置或任务的结构。 应用团队将其应用程序推出的成功主要理解为流水线阶段的成功。

应用程序团队的主要职责包括:

  • 开发、生成、部署、测试、推广、发布和支持其应用程序。
  • 维护和贡献其应用程序的源和清单存储库。
  • 定义和配置应用程序部署目标。
  • 与平台团队沟通,申请所需的计算资源,以便顺利进行SDLC操作。

高级工作流

此图显示了平台和应用程序团队角色在执行常规活动时如何相互交互。

示意图显示了角色之间的交互方式。

整个过程的主要概念是分离关注点。 过程中包括工作负载,例如应用程序和平台服务,还包括运行这些工作负载的平台。 应用程序团队负责工作负载(对象),而平台团队则专注于平台(即位置)。

应用程序团队对其应用程序运行 SDLC 操作,并跨环境推动更改。 他们不知道应用程序会在每个环境中部署在哪个群集上。 相反,应用程序团队使用部署目标的概念进行操作,而该概念只是环境中被命名的抽象概念。 例如,部署目标可以是开发上的集成、QA 上的功能测试和性能测试、早期采用者、Prod 上的外部用户等。

应用程序团队为每个推出环境定义部署目标,并且知道如何配置其应用程序以及如何为每个部署目标生成清单。 此过程自动进行,并且存在于应用程序存储库空间中。 这将为每个部署目标生成清单,并存储在清单存储中,例如 Git 存储库、Helm 存储库或 OCI 存储。

平台团队对应用程序有有限的了解,因此它们不涉及应用程序配置和部署过程。 平台团队负责按群集类型分组的平台群集。 他们通过配置值说明群集类型,例如 DNS 名称、外部服务的终结点等。 平台团队为各种群集类型分配或计划应用程序部署目标。 通过使用此方法,物理群集上的应用程序行为由部署目标配置值和群集类型配置值的组合决定。

平台团队使用单独的平台存储库,其中包含每种群集类型的清单。 这些清单定义应在每个群集类型上运行的工作负荷,以及应应用哪些平台配置值。 群集可以使用首选协调器从平台存储库中提取该信息,然后应用清单。

群集将其与平台和应用程序存储库的符合性状态报告到部署可观测性中心。 平台和应用程序团队可以查询此信息,以跨群集分析历史工作负载部署。 此信息可以在仪表板、警报和部署管道中使用,以实现渐进式推出。

解决方案体系结构

让我们看看高级解决方案体系结构并了解其主要组件。

显示解决方案体系结构的示意图。

控制面板

平台团队在控制平面中为多聚集环境建模,该环境旨在面向人,易于理解、更新和评审。 控制平面使用抽象作,例如群集类型、环境、工作负载、计划策略、配置和模板。 自动化过程通过将部署目标和配置值分配给群集类型来处理这些抽象,然后将结果保存到平台 GitOps 存储库。 尽管可能有数千个物理群集,但平台存储库在更高级别运行,将群集分组到群集类型。

控制平面存储的主要要求是提供可靠且安全的事务处理功能,而不是针对大量数据处理复杂的查询。 可以使用各种技术来存储控制平面数据。

此体系结构设计建议将 Git 存储库与一组管道配合使用,以跨环境存储和提升平台抽象。 此设计提供以下几个优势:

  • GitOps 原则的所有优势,例如版本控制、变更审批、自动化以及基于拉动的对帐。
  • Git 存储库(如 GitHub)提供开箱即用的分支功能、安全性和 PR 评审功能。
  • 使用类似的协调器或 GitHub Actions 工作流轻松实现推广工作流。
  • 无需维护和公开单独的控制平面服务。

提升和计划

控制平面存储库包含两种类型的数据:

  • 跨环境推广的数据,例如已接入的工作负载列表和各种模板。
  • 特定于环境的配置,例如包含的环境群集类型、配置值和计划策略。 此数据不会被推广,因为它特定于每个环境。

要推广的数据存储在 main 分支中。 特定于环境的数据存储在相应的环境分支中,例如 devqa以及 prod。 将数据从控制平面转换为 GitOps 存储库结合了推广和调度流。 变更流程横向传递更改跨越不同的工作环境。 计划流执行计划,并为每个环境垂直生成清单。

示意图显示了推广流程。

main 分支的提交将启动发布流程,从而逐一触发每个环境的调度流程。 计划流程会取用基础清单,并从相应环境分支应用配置值,然后在平台的 GitOps 仓库中创建包含结果清单的 PR。 在此环境中成功完成部署后,提升流程会继续并在下一个环境中执行相同的流程。 在每个环境中,流会提升 main 分支的同一提交 ID,从而确保来自 main 的内容只有在成功部署到上一个环境后才会进入下一个环境。

向控制平面存储库中的环境分支进行提交会启动此环境的调度流程。 例如,可以在 QA 环境中配置 cosmo-db 终结点。 你只想更新平台 GitOps 存储库的 QA 分支,而不涉及任何其他内容。 调度将提取与提升到当前环境的最新提交 ID 对应的main内容,应用配置,并将生成的清单提升到平台的 GitOps 分支。

工作负载分配

在平台 GitOps 存储库中,群集类型的每个工作负载分配由包含以下项的文件夹表示:

  • 适用于此类型群集上此环境中的此工作负载的专用命名空间。
  • 限制工作负载权限的平台策略。
  • 整合的平台配置映射,其中包含工作负载可以使用的参数值。
  • 协调器资源,指向存储实际工作负载清单或 Helm 图表的工作负载清单存储区。 例如,Flux GitRepository 和 Flux Kustomization、Argo CD 应用程序、Zarf 描述符等。

群集类型和协调器

每个群集类型都可以使用不同的对账器(例如 Flux、Argo CD、Zarf、Rancher Fleet 等)从工作负载清单存储库分发清单。 群集类型定义是指一个协调器,该协调器定义了清单模板的集合。 计划程序使用这些模板生成协调器资源,例如 Flux GitRepository 和 Flux Kustomization、Argo CD 应用程序、Zarf 描述符等。 相同的工作负载可能会被调度到由不同协调器(如 Flux 和 Argo CD)管理的集群类型。 计划程序为一个群集生成 Flux GitRepository 和 Flux Kustomization,并为另一个群集生成 Argo CD 应用程序,但两者都指向包含工作负荷清单的相同工作负荷清单存储。

平台服务

平台服务是平台团队维护的工作负载(例如 Prometheus、NGINX、Fluentbit 等)。 与任何工作负载一样,它们具有源存储库和清单存储。 源存储库可能包含指向外部 Helm 图表的指针。 CI/CD 管道会通过容器拉取 Helm 图表,并执行必要的安全扫描,然后将其提交到清单存储库,从该存储库中与群集进行协调。

部署可观测性中心

部署可观测性中心是一种中央存储,可以轻松对大量数据进行复杂查询。 它包含部署数据,其中包含有关工作负载版本及其跨群集部署状态的历史信息。 群集会在存储中注册自己,并通过 GitOps 存储库更新其符合性状态。 群集仅在 Git 提交级别运行。 系统将高级信息(如应用程序版本、环境和群集类型数据)从 GitOps 存储库传输到中央存储。 此高级信息会在中央存储中与从群集发送的提交符合性数据相关联。

平台配置概念

分离关注点

部署目标上的应用程序行为由配置值决定。 但是,配置值并不完全相同。 不同的角色在应用程序生命周期的不同点提供这些值,并且具有不同的范围。 通常,有应用程序配置和平台配置。

应用程序配置

应用程序开发人员提供的应用程序配置从部署目标详细信息中抽象出来。 通常,应用程序开发人员不知道特定于主机的详细信息,例如运行应用程序的主机或有多少主机。 但他们确实知道应用程序在生产途中所经历的环境和环链。

每个环境中可能会多次部署应用程序,以发挥不同的角色。 例如,同一应用程序既可以用作 dispatcher,也可以用作 exporter。 应用程序开发人员可能希望针对各种用例以不同的方式配置应用程序。 例如,如果应用程序在 dispatcher QA 环境中运行,则应不考虑实际主机,以这种方式进行配置。 开发人员在开发时为各种环境、圈和应用程序角色创建部署描述符或清单时提供这种类型的配置。

平台配置

除了开发时的配置,应用程序通常还需要特定于平台的配置值,例如终结点、标记或机密。 在部署应用程序的每个主机上,这些值可能会有所不同。 应用程序开发人员创建的部署描述符或清单引用包含这些值的配置对象,例如配置映射或机密。 应用程序开发人员希望这些配置对象存在于主机上,并可供应用程序使用。 通常,平台团队提供这些对象及其值。 根据组织的不同,不同的部门或人员可能会备份平台团队角色,例如 IT 全球、站点 IT 或设备所有者。

应用程序开发人员和平台团队的担忧是完全独立的。 应用程序开发人员专注于应用程序;它们拥有并对其进行配置。 同样,平台团队拥有并配置平台。 关键点是平台团队未配置应用程序;它们为应用程序配置环境。 本质上,他们提供环境变量值供应用程序使用。

平台配置通常包括与使用它们的应用程序无关的常见配置,以及每个应用程序可能唯一的应用程序特定配置。

此图显示应用程序和平台配置。

配置架构

尽管平台团队可能对应用程序及其工作原理有有限的了解,但他们知道目标主机上需要提供哪些平台配置。 应用程序开发人员提供此信息。 它们指定应用程序所需的配置值及其类型和约束。 定义此协定的一种方法是使用 JSON 架构。 例如:

{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "patch-to-core Platform Config Schema",
    "description": "Schema for platform config",
    "type": "object",
    "properties": {
      "ENVIRONMENT": {
      "type": "string",
      "description": "Environment Name"
      },
      "TimeWindowShift": {
      "type": "integer",
      "description": "Time Window Shift"
      },
      "QueryIntervalSec": {
      "type": "integer",
      "description": "Query Interval Sec"
      },
      "module": {
      "type": "object",
      "description": "module",
      "properties": {
        "drop-threshold": { "type": "number" }
      },
      "required": ["drop-threshold"]      
      }
    },
      "required": [
        "ENVIRONMENT",
        "module"
      ]              
    }	    

此方法在开发人员社区中广为人知,因为 Helm 使用 JSON 架构来定义 Helm 图表的可能值。

正式协定还可以实现自动化。 平台团队使用控制平面来提供配置值。 控制平面会分析应在主机上部署的应用程序。 它使用配置架构来建议平台团队应提供哪些值。 控制平面为每个应用程序实例组合配置值,并根据架构对其进行验证,以查看所有值是否都已正确设置。

控制平面可能会在不同时间点在多个阶段执行验证。 例如,当平台团队提供配置值时,控制平面会验证其类型、格式和基本约束。 当控制平面构成配置快照中应用程序的所有可用配置值时,将进行最终和最重要的验证。 只有此时,它才能检查所需的配置值,并检查涉及来自不同源的多个值的完整性约束。

配置图模型

控制平面为部署目标上的应用程序实例创建配置值快照。 它可从不同的配置容器中拉取值。 这些容器的关系可以是层次结构或图形。 控制平面遵循规则来确定应用程序配置快照中包含哪些容器的配置值。 平台团队定义配置容器并设置冻结规则。 应用程序开发人员无需了解此结构。 它们只需要知道要提供的配置值,而不是值来自何处。

标签匹配方法

实现配置组合的简单灵活方法是标签匹配方法。

此图显示标签匹配配置组合模型。

在此关系图中,配置容器以不同级别对配置值进行分组,例如站点、线路、环境和区域。 根据组织的不同,不同的角色提供这些容器中的值,例如 IT 全局、站点 IT、设备所有者或平台团队。 每个容器都标有一组标签,用于定义此容器中的值适用的情况。 除了配置容器,还有表示应用程序和要部署应用程序的主机的抽象。 两者也都标有标签。 应用程序标签和主机标签的组合构成了实例的标签集。 该集合决定在应用程序配置快照中包括哪些来自配置容器的值。 此快照将传送到主机,并馈送到应用程序实例。 控制平面循环访问容器,并评估容器的标签是否与实例的标签集匹配。 如果匹配,控制平面会在最终快照中纳入容器的值。 如果它们不匹配,控制平面将跳过容器。 可以使用对复杂对象和数组进行重写和合并的不同策略来配置控制平面。

此方法最大的优点之一是可伸缩性。 配置容器的结构从应用程序实例中抽象出来,因此应用程序实例不需要知道值来自何处。 通过此抽象,平台团队可以轻松作配置容器,并引入新的级别和配置组,而无需重新配置数百个应用程序实例。

模板化

控制平面为每个主机上的每个应用程序实例创建配置快照。 各种应用程序、主机、基础技术和部署方法可能非常广泛。 此外,同一应用程序在从开发环境到生产环境过程中的部署方式可能完全不同。 控制平面管理配置,而不是部署。 它应该与基础应用程序和主机技术无关。 它应为每个情况生成合适的格式配置快照,例如 Kubernetes 配置映射、属性文件、交响乐目录或其他格式。

一个选项是将不同的模板分配给不同的主机类型。 当控制平面为要部署到主机上的应用程序生成配置快照时,控制平面使用这些模板。 应用在开发人员社区中广为人知的标准模板化方法是有益的。 例如,可以使用 Go 模板来定义以下模板,这些模板在行业中广泛使用:

# Standard Kubernetes config map
apiVersion: v1
kind: ConfigMap
metadata:
  name: platform-config
  namespace: {{ .Namespace}}
data:
{{ toYaml .ConfigData | indent 2}}
# Symphony catalog object
apiVersion: federation.symphony/v1
kind: Catalog
metadata:
  name: platform-config
  namespace: {{ .Namespace}}
spec:  
  type: config
  name: platform-config
  properties:
{{ toYaml .ConfigData | indent 4}}
# JSON file
{{ toJson .ConfigData}}

然后将这些模板分别分配给主机 A、B 和 C。 假设具有相同配置值的应用程序部署到所有三个主机,则控制平面会为每个实例生成三个不同的配置快照:

# Standard Kubernetes config map
apiVersion: v1
kind: ConfigMap
metadata:
  name: platform-config
  namespace: line1
data:
  FACTORY_NAME: Atlantida
  LINE_NAME_LOWER: line1
  LINE_NAME_UPPER: LINE1
  QueryIntervalSec: "911"
# Symphony catalog object
apiVersion: federation.symphony/v1
kind: Catalog
metadata:
  name: platform-config
  namespace: line1
spec:  
  type: config
  name: platform-config
  properties:
    FACTORY_NAME: Atlantida
    LINE_NAME_LOWER: line1
    LINE_NAME_UPPER: LINE1
    QueryIntervalSec: "911"
{
 "FACTORY_NAME" : "Atlantida",
 "LINE_NAME_LOWER" : "line1",
 "LINE_NAME_UPPER": "LINE1",
 "QueryIntervalSec": "911"
}

配置存储

控制平面与配置容器一起工作,这些容器在层次结构或图形中按不同级别分组配置值。 使用数据库存储容器可提供最灵活、最可靠的体验。 无论是使用 etcd、关系数据库、分层数据库还是图形数据库,都可以在各个配置容器级别精细跟踪和处理配置值。

除了存储等主要功能以及有效查询和作配置对象的能力外,还应有与更改跟踪、审批、促销、回滚、版本比较等相关的功能。 控制平面可以在数据库之上实现所有这些功能,并将所有内容封装在整体托管服务中。

或者,可以将此功能委托给 Git,以遵循“配置即代码”概念。 例如, Kalypso 是 Kubernetes 操作员,将配置容器视为基本上存储在 etcd 数据库中的自定义 Kubernetes 资源。 尽管控制平面没有规定,但在 Git 存储库中初始化配置值是一种常见做法,并利用其开箱即用的所有优势。 然后,使用 GitOps 运算符将配置值传递到 Kubernetes etcd 存储,控制平面可以处理它们以执行合成。

Git 存储库层次结构

不需要有一个 Git 存储库,其中包含整个组织的配置值。 鉴于“平台团队”的角色、职责及其访问级别多种多样,这样的存储库在规模较大时可能会成为瓶颈。 而是使用 GitOps 运算符引用(如 Flux GitRepository 和 Flux Kustomization)生成存储库层次结构并消除摩擦点:

此图显示 Git 存储库层次结构。

配置版本控制

每当应用程序开发人员在应用程序中引入更改时,都会生成新的应用程序版本。 同样,新的平台配置值也会产生新版本的配置快照。 使用版本管理可以跟踪更改、明确的发布和回滚。

应用程序配置快照是独立版本化的。 全局级别或站点级别的单个配置值更改不一定生成所有应用程序配置快照的新版本。 此更改仅影响此值被冻结的快照。 跟踪它的简单有效方法是使用快照内容的哈希作为其版本。 这样,如果快照内容因全局配置中发生更改而更改,则会出现新版本。 可以手动或自动应用此新版本。 在任何情况下,此更改都是可跟踪的事件,可以根据需要回滚。

后续步骤