×

关于依赖项解析

Operator Lifecycle Manager (OLM) 管理运行中的 Operator 的依赖项解析和升级生命周期。在许多方面,OLM 面临的问题与其他系统或语言包管理器(例如 `yum` 和 `rpm`)类似。

但是,OLM 有一个类似系统通常没有的约束:因为 Operator 始终在运行,所以 OLM 尝试确保您永远不会留下无法相互协作的 Operator 集。

因此,OLM 绝不能创建以下场景

  • 安装需要无法提供的 API 的 Operator 集

  • 以破坏依赖它的另一个操作符的方式更新操作符

这可以通过两种类型的数据实现

属性

关于操作符的类型化元数据,构成其在依赖关系解析器中的公共接口。例如,操作符提供的 API 的组/版本/种类 (GVK) 和操作符的语义版本 (semver)。

约束或依赖关系

操作符的要求,这些要求应该由其他操作符满足,这些操作符可能已经安装在目标集群上,也可能没有。它们充当对所有可用操作符的查询或过滤器,并在依赖关系解析和安装过程中限制选择。例如,要求集群上可用特定的 API,或期望安装具有特定版本的特定操作符。

OLM 将这些属性和约束转换为布尔公式系统,并将它们传递给 SAT 求解器(一个建立布尔可满足性的程序),该程序负责确定应安装哪些操作符。

操作符属性

目录中的所有操作符都具有以下属性

olm.package

包含包的名称和操作符的版本

olm.gvk

集群服务版本 (CSV) 中每个提供的 API 的单个属性

操作符作者还可以通过在操作符包的metadata/目录中包含一个properties.yaml文件来直接声明附加属性。

任意属性示例
properties:
- type: olm.kubeversion
  value:
    version: "1.16.0"

任意属性

操作符作者可以在操作符包的metadata/目录中properties.yaml文件中声明任意属性。这些属性被转换为地图数据结构,在运行时用作操作符生命周期管理器 (OLM) 解析器的输入。

这些属性对解析器是不透明的,因为它不理解这些属性,但它可以根据这些属性评估通用约束,以确定在给定属性列表的情况下是否可以满足约束。

任意属性示例
properties:
  - property:
      type: color
      value: red
  - property:
      type: shape
      value: square
  - property:
      type: olm.gvk
      value:
        group: olm.coreos.io
        version: v1alpha1
        kind: myresource

此结构可用于构建通用约束的通用表达式语言 (CEL) 表达式。

操作符依赖关系

操作符的依赖关系列在包的metadata/文件夹中的dependencies.yaml文件中。此文件是可选的,目前仅用于指定显式操作符版本依赖关系。

依赖项列表为每个项目包含一个type字段,用于指定这是什么类型的依赖项。支持以下类型的操作符依赖项

olm.package

此类型表示对特定操作符版本的依赖关系。依赖信息必须包括包名和semver格式的包版本。例如,您可以指定确切的版本,例如0.5.2,或版本的范围,例如>0.5.1

olm.gvk

使用此类型,作者可以指定具有组/版本/种类 (GVK) 信息的依赖关系,类似于 CSV 中现有的基于 CRD 和 API 的用法。这是一种路径,使操作符作者能够将所有依赖关系(API 或显式版本)合并到同一位置。

olm.constraint

此类型声明对任意操作符属性的通用约束。

在以下示例中,为 Prometheus Operator 和 etcd CRD 指定了依赖关系

dependencies.yaml文件示例
dependencies:
  - type: olm.package
    value:
      packageName: prometheus
      version: ">0.27.0"
  - type: olm.gvk
    value:
      group: etcd.database.coreos.com
      kind: EtcdCluster
      version: v1beta2

通用约束

olm.constraint属性声明特定类型的依赖约束,区分非约束属性和约束属性。它的value字段是一个对象,包含一个failureMessage字段,该字段保存约束消息的字符串表示形式。如果在运行时无法满足约束,则此消息将作为信息性注释显示给用户。

以下键表示可用的约束类型

gvk

其值和解释与olm.gvk类型相同的类型

package

其值和解释与olm.package类型相同的类型

cel

操作符生命周期管理器 (OLM) 解析器在运行时针对任意包属性和集群信息评估的通用表达式语言 (CEL) 表达式

allanynot

分别表示合取、析取和否定约束,包含一个或多个具体约束,例如gvk或嵌套的复合约束

通用表达式语言 (CEL) 约束

cel约束类型支持通用表达式语言 (CEL)作为表达式语言。cel结构体具有一个rule字段,其中包含在运行时针对操作符属性评估的 CEL 表达式字符串,以确定操作符是否满足约束。

cel约束示例
type: olm.constraint
value:
  failureMessage: 'require to have "certified"'
  cel:
    rule: 'properties.exists(p, p.type == "certified")'

CEL 语法支持各种逻辑运算符,例如ANDOR。因此,单个 CEL 表达式可以包含多个规则,用于通过这些逻辑运算符链接在一起的多个条件。这些规则针对来自包或任何给定来源的多个不同属性的数据集进行评估,并将输出解决为单个包或满足单个约束内所有这些规则的操作符。

具有多个规则的cel约束示例
type: olm.constraint
value:
  failureMessage: 'require to have "certified" and "stable" properties'
  cel:
    rule: 'properties.exists(p, p.type == "certified") && properties.exists(p, p.type == "stable")'

复合约束 (all、any、not)

复合约束类型按照其逻辑定义进行评估。

以下是一个两个包和一个 GVK 的合取约束 (all) 的示例。也就是说,它们都必须由已安装的包满足

all约束示例
schema: olm.bundle
name: red.v1.0.0
properties:
- type: olm.constraint
  value:
    failureMessage: All are required for Red because...
    all:
      constraints:
      - failureMessage: Package blue is needed for...
        package:
          name: blue
          versionRange: '>=1.0.0'
      - failureMessage: GVK Green/v1 is needed for...
        gvk:
          group: greens.example.com
          version: v1
          kind: Green

以下是一个相同 GVK 的三个版本的析取约束 (any) 的示例。也就是说,至少必须由已安装的包满足一个

any约束示例
schema: olm.bundle
name: red.v1.0.0
properties:
- type: olm.constraint
  value:
    failureMessage: Any are required for Red because...
    any:
      constraints:
      - gvk:
          group: blues.example.com
          version: v1beta1
          kind: Blue
      - gvk:
          group: blues.example.com
          version: v1beta2
          kind: Blue
      - gvk:
          group: blues.example.com
          version: v1
          kind: Blue

以下是一个 GVK 的一个版本的否定约束 (not) 的示例。也就是说,此 GVK 不能由结果集中的任何包提供

not约束示例
schema: olm.bundle
name: red.v1.0.0
properties:
- type: olm.constraint
  value:
  all:
    constraints:
    - failureMessage: Package blue is needed for...
      package:
        name: blue
        versionRange: '>=1.0.0'
    - failureMessage: Cannot be required for Red because...
      not:
        constraints:
        - gvk:
            group: greens.example.com
            version: v1alpha1
            kind: greens

not约束上下文中,否定语义可能显得不清楚。为了澄清,否定实际上是在指示解析器从结果集中删除任何包含特定 GVK、特定版本的包或满足某个子复合约束的可能解决方案。

作为推论,not复合约束应该只在allany约束中使用,因为在不首先选择一组可能的依赖项的情况下进行否定是没有意义的。

嵌套复合约束

嵌套复合约束(包含至少一个子复合约束以及零个或多个简单约束的约束)自下而上进行评估,遵循前面描述的每种约束类型的过程。

以下是一个合取的析取示例,其中一个、另一个或两者都可以满足约束

嵌套复合约束示例
schema: olm.bundle
name: red.v1.0.0
properties:
- type: olm.constraint
  value:
    failureMessage: Required for Red because...
    any:
      constraints:
      - all:
          constraints:
          - package:
              name: blue
              versionRange: '>=1.0.0'
          - gvk:
              group: blues.example.com
              version: v1
              kind: Blue
      - all:
          constraints:
          - package:
              name: blue
              versionRange: '<1.0.0'
          - gvk:
              group: blues.example.com
              version: v1beta1
              kind: Blue

olm.constraint类型的最大原始大小为 64KB,以限制资源耗尽攻击。

依赖项首选项

一个 Operator 的依赖可能有多个选项都能满足。Operator 生命周期管理器 (OLM) 中的依赖解析器会确定哪个选项最符合请求的 Operator 的要求。作为 Operator 的作者或用户,了解这些选择的依据非常重要,以便清晰地理解依赖关系的解析。

目录优先级

在 OpenShift Container Platform 集群上,OLM 读取目录源以了解哪些 Operator 可用于安装。

CatalogSource 对象示例
apiVersion: "operators.coreos.com/v1alpha1"
kind: "CatalogSource"
metadata:
  name: "my-operators"
  namespace: "operators"
spec:
  sourceType: grpc
  grpcPodConfig:
    securityContextConfig: <security_mode> (1)
  image: example.com/my/operator-index:v1
  displayName: "My Operators"
  priority: 100
1 指定legacyrestricted的值。如果未设置此字段,则默认值为legacy。在未来的 OpenShift Container Platform 版本中,计划将默认值更改为restricted。如果您的目录无法以restricted权限运行,建议您手动将此字段设置为legacy

CatalogSource 对象具有一个priority字段,解析器使用此字段来确定如何优先选择依赖项的选项。

有两个规则决定目录的优先级

  • 优先级较高的目录中的选项优先于优先级较低的目录中的选项。

  • 与依赖项位于同一目录中的选项优先于任何其他目录中的选项。

通道排序

目录中的 Operator 包是用户可以在 OpenShift Container Platform 集群中订阅的一组更新通道。通道可用于为次要版本 (1.21.3) 或发布频率 (stablefast) 提供特定的更新流。

依赖项很可能由同一包中但不同通道的 Operator 满足。例如,Operator 的 1.2 版本可能同时存在于stablefast通道中。

每个包都有一个默认通道,它始终优先于非默认通道。如果默认通道中没有选项可以满足依赖关系,则会按通道名称的字典顺序考虑其余通道中的选项。

通道内的顺序

在一个通道内,几乎总是有多个选项可以满足依赖关系。例如,一个包和通道中的 Operator 提供相同的 API 集。

当用户创建订阅时,他们会指示要接收更新的通道。这会立即将搜索范围缩小到该通道。但在通道内,许多 Operator 都可能满足依赖关系。

在通道内,更新图中较新的 Operator 优先。如果通道的头部满足依赖关系,则会首先尝试它。

其他约束

除了包依赖项提供的约束外,OLM 还包含其他约束来表示所需的用户信息并强制执行解析不变性。

订阅约束

订阅约束会过滤可以满足订阅的 Operator 集。订阅是用户为依赖解析器提供的约束。它们声明了以下意图:如果集群中不存在新的 Operator,则安装新的 Operator,或者保持现有 Operator 更新。

包约束

在一个命名空间中,不允许两个 Operator 来自同一包。

其他资源

CRD 升级

如果自定义资源定义 (CRD) 由单个集群服务版本 (CSV) 拥有,则 OLM 会立即升级它。如果 CRD 由多个 CSV 拥有,则当它满足以下所有向后兼容条件时,才会升级 CRD

  • 当前 CRD 中所有现有的服务版本都存在于新的 CRD 中。

  • 与 CRD 的服务版本关联的所有现有实例(或自定义资源)在针对新 CRD 的验证模式进行验证时都是有效的。

依赖最佳实践

在指定依赖项时,您应该考虑一些最佳实践。

依赖 API 或 Operator 的特定版本范围

Operator 随时可以添加或删除 API;始终对 Operator 需要的任何 API 指定olm.gvk依赖关系。例外情况是,如果您改为指定olm.package约束。

设置最小版本

Kubernetes 关于 API 变更的文档描述了允许对 Kubernetes 风格的 Operator 进行哪些变更。这些版本控制约定允许 Operator 更新 API 而不更改 API 版本,只要 API 向后兼容即可。

对于 Operator 依赖项,这意味着仅了解依赖项的 API 版本可能不足以确保依赖项 Operator 按预期工作。

例如

  • TestOperator v1.0.0 提供MyObject资源的 v1alpha1 API 版本。

  • TestOperator v1.0.1 向MyObject添加了一个新字段spec.newfield,但仍然是 v1alpha1。

您的 Operator 可能需要能够将spec.newfield写入MyObject资源。仅靠olm.gvk约束不足以让 OLM 确定您需要 TestOperator v1.0.1 而不是 TestOperator v1.0.0。

如果预先知道提供 API 的特定 Operator,则应尽可能指定额外的olm.package约束以设置最小值。

省略最大版本或允许非常宽的范围

由于 Operator 提供集群范围的资源(例如 API 服务和 CRD),因此指定依赖项的小范围的 Operator 可能会不必要地限制该依赖项的其他用户的更新。

尽可能不要设置最大版本。或者,设置非常宽的语义范围以防止与其他 Operator 发生冲突。例如,>1.0.0 <2.0.0

与传统的包管理器不同,Operator 作者通过 OLM 中的通道明确编码更新是安全的。如果现有订阅有可用的更新,则假定 Operator 作者表示可以从先前版本更新。为依赖项设置最大版本会通过不必要地将其截断在特定上限来覆盖作者的更新流。

集群管理员无法覆盖 Operator 作者设置的依赖项。

但是,如果必须避免已知的兼容性问题,则可以并且应该设置最大版本。可以使用版本范围语法省略特定版本,例如> 1.0.0 !1.2.1

其他资源

依赖项注意事项

在指定依赖项时,您应该考虑一些注意事项。

没有复合约束 (AND)

当前没有方法可以指定约束之间的 AND 关系。换句话说,无法指定一个 Operator 依赖于另一个同时提供给定 API 并具有版本>1.1.0的 Operator。

这意味着,当指定诸如以下依赖项时

dependencies:
- type: olm.package
  value:
    packageName: etcd
    version: ">3.1.0"
- type: olm.gvk
  value:
    group: etcd.database.coreos.com
    kind: EtcdCluster
    version: v1beta2

OLM 可能可以使用两个 Operator 来满足此要求:一个提供 EtcdCluster,另一个具有版本>3.1.0。发生这种情况,或者选择满足这两个约束的 Operator,取决于访问潜在选项的顺序。依赖项偏好和排序选项定义明确,并且可以进行推理,但为了谨慎起见,Operator 应该坚持使用一种或另一种机制。

跨命名空间兼容性

OLM 在命名空间范围内执行依赖关系解析。如果在一个命名空间中更新 Operator 会影响另一个命名空间中的 Operator,反之亦然,则可能会导致更新死锁。

依赖关系解析场景示例

在下述示例中,“提供者”(provider)是指“拥有”CRD或API服务的运营商。

示例:弃用依赖的API

A和B是API(CRD)

  • A的提供者依赖于B。

  • B的提供者已订阅。

  • B的提供者更新为提供C,但弃用B。

这将导致

  • B不再有提供者。

  • A不再工作。

这是OLM通过其升级策略避免的情况。

示例:版本死锁

A和B是API

  • A的提供者需要B。

  • B的提供者需要A。

  • A的提供者更新为(提供A2,需要B2)并弃用A。

  • B的提供者更新为(提供B2,需要A2)并弃用B。

如果OLM尝试更新A而不同时更新B,反之亦然,则它将无法升级到运营商的新版本,即使可以找到新的兼容集。

这是OLM通过其升级策略避免的另一种情况。