×

关于依赖项解析

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

但是,OLM 具有类似系统通常不具备的一个约束:因为操作符始终处于运行状态,所以 OLM 尝试确保您永远不会遇到一组彼此不兼容的操作符。

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

  • 安装一组需要无法提供的 API 的操作符

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

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

属性

关于操作符的类型化元数据,构成了其在依赖项解析器中的公共接口。例如,操作符提供的 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) 表达式。

操作符依赖项

Operator 的依赖项列在 bundle 的 `metadata/` 文件夹中的 `dependencies.yaml` 文件中。此文件是可选的,目前仅用于指定明确的 Operator 版本依赖关系。

依赖项列表中每个项目都包含一个 `type` 字段,用于指定依赖项的类型。支持以下类型的 Operator 依赖关系:

olm.package

此类型表示对特定 Operator 版本的依赖关系。依赖信息必须包含包名称和包的语义版本(semver)格式的版本。例如,您可以指定确切的版本,例如 `0.5.2`,或指定版本范围,例如 `>0.5.1`。

olm.gvk

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

olm.constraint

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

在以下示例中,为 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

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

allanynot

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

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

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

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

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

包含多个规则的示例 `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) 示例。也就是说,它们都必须由已安装的 bundle 满足。

示例 `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) 示例。也就是说,已安装的 bundle 必须至少满足其中一个。

示例 `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) 示例。也就是说,任何 bundle 中都不能提供此 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` 复合约束应该只在 `all` 或 `any` 约束中使用,因为在不首先选择可能的依赖项集的情况下进行否定是没有意义的。

嵌套复合约束

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

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

示例嵌套复合约束
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 Dedicated 集群上,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 指定 `legacy` 或 `restricted` 的值。如果未设置该字段,则默认值为 `legacy`。在未来的 OpenShift Dedicated 版本中,计划将默认值更改为 `restricted`。如果您的目录无法在 `restricted` 权限下运行,建议您手动将此字段设置为 `legacy`。

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

有两个规则控制目录偏好:

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

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

通道排序

目录中的 Operator 包是用户可以在 OpenShift Dedicated 集群中订阅的更新通道的集合。通道可用于提供次要版本 (例如 `1.2`、`1.3`) 或发布频率 (例如 `stable`、`fast`) 的特定更新流。

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

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

通道内的顺序

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

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

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

其他约束

除了包依赖关系提供的约束之外,OLM 还包含其他约束以表示所需的用户状态并强制执行解析不变性。

订阅约束

订阅约束会过滤可以满足订阅的 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 造成问题,反之亦然,则可能会导致更新死锁。

依赖关系解析场景示例

在以下示例中,提供程序 是“拥有”CRD 或 API 服务的 Operator。

示例:弃用依赖 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,反之亦然,则它将无法升级到 Operator 的新版本,即使可以找到新的兼容集。

这是 OLM 使用其升级策略阻止的另一种情况。