2.2. Operator Framework 打包格式

本指南概述了 OpenShift Container Platform 中 Operator Lifecycle Manager (OLM) 所支持的 Operator 打包格式。

注意

OpenShift Container Platform 4.8 及更高版本中删除了对 Operator 的传统软件包清单格式的支持。软件包清单格式中的现有 Operator 项目可使用 Operator SDK pkgman-to-bundle 命令迁移到捆绑包格式。如需了解更多详细信息,请参阅迁移软件包清单项目以使用捆绑格式

2.2.1. 捆绑包格式

Operator 的 Bundle Format 是 Operator Framework 引入的新打包格式。为提高可伸缩性并为自行托管目录的上游用户提供更好地支持,Bundle Format 规格简化了 Operator 元数据的发布。

Operator 捆绑包代表 Operator 的单一版本。磁盘上的捆绑包清单是容器化的,并作为捆绑包镜像提供,该镜像是一个不可运行的容器镜像,其中存储了 Kubernetes 清单和 Operator 元数据。然后,使用现有容器工具(如 podmandocker)和容器 registry(如 Quay)来管理捆绑包镜像的存储和发布。

Operator 元数据可以包括:

  • 标识 Operator 的信息,如名称和版本。
  • 驱动 UI 的额外信息,例如其图标和一些示例自定义资源 (CR)。
  • 所需的和所提供的 API。
  • 相关镜像。

将清单加载到 Operator Registry 数据库中时,会验证以下要求:

  • 该捆绑包必须在注解中至少定义一个频道。
  • 每个捆绑包都只有一个集群服务版本(CSV)。
  • 如果 CSV 拥有自定义资源定义(CRD),则该 CRD 必须存在于捆绑包中。

2.2.1.1. 清单

捆绑包清单指的是一组 Kubernetes 清单,用于定义 Operator 的部署和 RBAC 模型。

捆绑包包括每个目录的一个 CSV,一般情况下,用来定义 CRD 所拥有的 API 的 CRD 位于 /manifest 目录中。

捆绑包格式布局示例

etcd
├── manifests
│   ├── etcdcluster.crd.yaml
│   └── etcdoperator.clusterserviceversion.yaml
│   └── secret.yaml
│   └── configmap.yaml
└── metadata
    └── annotations.yaml
    └── dependencies.yaml

额外支持的对象

以下对象类型也可以包括在捆绑包的 /manifests 目录中:

支持的可选对象类型

  • ClusterRole
  • ClusterRoleBinding
  • ConfigMap
  • ConsoleYamlSample
  • PodDisruptionBudget
  • PriorityClass
  • PrometheusRule
  • 角色
  • RoleBinding
  • Secret
  • Service
  • ServiceAccount
  • ServiceMonitor
  • VerticalPodAutoscaler

当捆绑包中包含这些可选对象时,Operator Lifecycle Manager(OLM)可以从捆绑包创建对象,并随 CSV 一起管理其生命周期:

可选对象的生命周期

  • 删除 CSV 后,OLM 会删除可选对象。
  • 当 CSV 被升级时:

    • 如果可选对象的名称相同,OLM 会更新它。
    • 如果可选对象的名称在版本间有所变化,OLM 会删除并重新创建它。

2.2.1.2. 注解

捆绑包还在其 /metadata 文件夹中包含 annotations.yaml 文件。此文件定义了更高级别的聚合数据,以帮助描述有关如何将捆绑包添加到捆绑包索引中的格式和软件包信息:

annotations.yaml 示例

annotations:
  operators.operatorframework.io.bundle.mediatype.v1: "registry+v1" 1
  operators.operatorframework.io.bundle.manifests.v1: "manifests/" 2
  operators.operatorframework.io.bundle.metadata.v1: "metadata/" 3
  operators.operatorframework.io.bundle.package.v1: "test-operator" 4
  operators.operatorframework.io.bundle.channels.v1: "beta,stable" 5
  operators.operatorframework.io.bundle.channel.default.v1: "stable" 6

1
Operator 捆绑包的介质类型或格式。registry+v1 格式表示它包含 CSV 及其关联的 Kubernetes 对象。
2
镜像中的该路径指向含有 Operator 清单的目录。该标签保留给以后使用,当前默认为 manifests/manifests.v1 值表示捆绑包包含 Operator 清单。
3
镜像中的该路径指向包含捆绑包元数据文件的目录。该标签保留给以后使用,当前默认为 metadata/metadata.v1 值表示这个捆绑包包含 Operator 元数据。
4
捆绑包的软件包名称。
5
捆绑包添加到 Operator Registry 时订阅的频道列表。
6
从 registry 安装时,Operator 应该订阅到的默认频道。
注意

如果出现不匹配的情况,则以 annotations.yaml 文件为准,因为依赖这些注解的集群 Operator Registry 只能访问此文件。

2.2.1.3. 依赖项文件

Operator 的依赖项列在捆绑包的 metadata/ 目录中的 dependencies.yaml 文件中。此文件是可选的,目前仅用于指明 Operator-version 依赖项。

依赖项列表中,每个项目包含一个 type 字段,用于指定这一依赖项的类型。受支持的 Operator 依赖项有两种:

  • olm.package:此类型表示特定 Operator 版本的依赖项。依赖项信息必须包含软件包名称以及软件包的版本,格式为 semver。例如,您可以指定具体版本,如 0.5.2,也可指定一系列版本,如 >0.5.1
  • olm.gvk:使用 gvk 类型,作者可以使用 group/version/kind(GVK)信息指定依赖项,类似于 CSV 中现有 CRD 和基于 API 的使用。该路径使 Operator 作者可以合并所有依赖项、API 或显式版本,使它们处于同一位置。

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

2.2.1.4. 关于 opm CLI

opm CLI 工具由 Operator Framework 提供,用于 Operator 捆绑格式。您可以通过此工具从与软件存储库类似的 Operator 捆绑包列表中创建和维护 Operator 目录。其结果是一个容器镜像,它可以存储在容器的 registry 中,然后安装到集群中。

目录包含一个指向 Operator 清单内容的指针数据库,可通过在运行容器镜像时提供的已包含 API 进行查询。在 OpenShift Container Platform 中,Operator Lifecycle Manager (OLM) 可以引用由 CatalogSource 对象定义的目录源中的镜像,它会定期轮询镜像,以对集群上安装的 Operator 进行更新。

  • 有关安装 opm CLI 的步骤,请参阅 CLI 工具

2.2.2. 基于文件的目录

基于文件的目录是 Operator Lifecycle Manager (OLM) 中目录格式的最新迭代。它是基于纯文本(JSON 或 YAML)和早期 SQLite 数据库格式的声明式配置演变,并且完全向后兼容。此格式的目标是启用 Operator 目录编辑、可组合性和可扩展性。

注意

OpenShift Container Platform 4.6 及更新的版本为 Red Hat 提供的默认 Operator 目录当前仍然以 SQLite 数据库格式提供。

编辑

使用基于文件的目录,与目录内容交互的用户可以对格式进行直接更改,并验证其更改是否有效。由于这种格式是纯文本 JSON 或 YAML,因此目录维护人员可以通过手动或广泛支持的 JSON 或 YAML 工具(如 jq CLI)轻松操作目录元数据。

此可编辑功能启用以下功能和用户定义的扩展:

  • 将现有捆绑包提升到新频道
  • 更改软件包的默认频道
  • 用于添加、更新和删除升级边缘的自定义算法
Composability

基于文件的目录存储在任意目录层次结构中,从而启用目录组成。例如,考虑两个单独的基于文件的目录目录:catalogAcatalogB。目录维护人员可以通过生成新目录 catalogC 并将 catalogAcatalogB 复制到其中来创建新的组合目录。

这种可组合性支持分散的目录。格式允许 Operator 作者维护特定于 Operator 的目录,它允许维护人员轻松构建由单个 Operator 目录组成的目录。基于文件的目录可以通过组合多个其他目录、提取一个目录的子集或两者的组合来组成。

注意

不允许软件包中重复软件包和重复捆绑包。如果找到任何重复项,opm validate 命令将返回错误。

因为 Operator 作者最熟悉其 Operator、其依赖项及其升级兼容性,所以他们可以维护自己的 Operator 目录并直接控制其内容。对于基于文件的目录,Operator 作者负责在目录中构建和维护其软件包的任务。但是,复合目录维护者仅拥有在其目录中管理软件包并将目录发布到用户的任务。

可扩展性

基于文件的目录规格是目录的一个低级别表示。虽然目录维护器可以直接以低级形式维护,但目录维护人员可以在其自己的自定义工具上构建有趣的扩展,以供其自身的自定义工具用于实现任意数量的变异。

例如,工具可以将一个高级 API (如(mode=semver)) 转换为升级边缘基于文件的低级别目录格式。或目录维护人员可能需要通过添加新属性到符合特定标准的捆绑包来自定义所有捆绑包元数据。

虽然这种可扩展性允许在低级别 API 上开发额外的官方工具,用于将来的 OpenShift Container Platform 版本,但目录维护人员也具有此功能。

2.2.2.1. 目录结构

基于文件的目录可从基于目录的文件系统进行存储和加载。opm CLI 通过遍历根目录并递归到子目录来加载目录。CLI 尝试加载它找到的每个文件,如果发生错误,则会失败。

可以使用 .indexignore 文件忽略非目录文件,这些文件对模式和优先级与 .gitignore 文件具有相同的规则。

示例 .indexignore 文件

# Ignore everything except non-object .json and .yaml files
**/*
!*.json
!*.yaml
**/objects/*.json
**/objects/*.yaml

目录维护人员具有选择所需的布局的灵活性,但建议将每个软件包基于文件的目录 Blob 存储在单独的子目录中。每个单独的文件可以是 JSON 或 YAML;目录中的每一文件并不需要使用相同的格式。

基本推荐结构

catalog
├── packageA
│   └── index.yaml
├── packageB
│   ├── .indexignore
│   ├── index.yaml
│   └── objects
│       └── packageB.v0.1.0.clusterserviceversion.yaml
└── packageC
    └── index.json

此推荐结构具有目录层次结构中的每个子目录都是自包含目录的属性,它使得目录组成、发现和导航简单文件系统操作。通过将目录复制到父目录的根目录,目录也可以包含在父目录中。

2.2.2.2. 模式

基于文件的目录使用基于 CUE 语言规范 的格式,该格式可使用任意模式进行扩展。以下 _Meta CUE 模式定义了所有基于文件的目录 Blob 必须遵循的格式:

_Meta 架构

_Meta: {
  // schema is required and must be a non-empty string
  schema: string & !=""

  // package is optional, but if it's defined, it must be a non-empty string
  package?: string & !=""

  // properties is optional, but if it's defined, it must be a list of 0 or more properties
  properties?: [... #Property]
}

#Property: {
  // type is required
  type: string & !=""

  // value is required, and it must not be null
  value: !=null
}

注意

此规格中列出的 CUE 模式不可被视为详尽模式。opm validate 命令具有额外的验证,很难或不可能在 CUE 中简洁地表达。

Operator Lifecycle Manager (OLM) 目录目前使用三种模式(olm.packageolm.channelolm.bundle),它们对应于 OLM 的现有软件包和捆绑包概念。

目录中的每个 Operator 软件包都需要一个 olm.package blob、至少一个 olm.channel blob 以及一个或多个 olm.bundle blob。

注意

所有 olm.* 模式都为 OLM 定义的模式保留。自定义模式必须使用唯一前缀,如您拥有的域。

2.2.2.2.1. olm.package schema

olm.package 模式为 Operator 定义软件包级别的元数据。这包括其名称、描述、默认频道和图标。

例 2.1. olm.package schema

#Package: {
  schema: "olm.package"

  // Package name
  name: string & !=""

  // A description of the package
  description?: string

  // The package's default channel
  defaultChannel: string & !=""

  // An optional icon
  icon?: {
    base64data: string
    mediatype:  string
  }
}
2.2.2.2.2. olm.channel schema

olm.channel 模式在软件包中定义频道、属于频道成员的捆绑包条目,以及这些捆绑包的升级边缘。

捆绑包可作为条目包含在多个 olm.channel blob 中,但它每个频道只能有一个条目。

它对条目的 replaces 值有效,以引用无法在此目录或其他目录中找到的另一捆绑包名称。但是,所有其他频道变量都必须为 true,比如频道没有多个磁头。

例 2.2. olm.channel schema

#Channel: {
  schema: "olm.channel"
  package: string & !=""
  name: string & !=""
  entries: [...#ChannelEntry]
}

#ChannelEntry: {
  // name is required. It is the name of an `olm.bundle` that
  // is present in the channel.
  name: string & !=""

  // replaces is optional. It is the name of bundle that is replaced
  // by this entry. It does not have to be present in the entry list.
  replaces?: string & !=""

  // skips is optional. It is a list of bundle names that are skipped by
  // this entry. The skipped bundles do not have to be present in the
  // entry list.
  skips?: [...string & !=""]

  // skipRange is optional. It is the semver range of bundle versions
  // that are skipped by this entry.
  skipRange?: string & !=""
}
2.2.2.2.3. olm.bundle schema

例 2.3. olm.bundle schema

#Bundle: {
  schema: "olm.bundle"
  package: string & !=""
  name: string & !=""
  image: string & !=""
  properties: [...#Property]
  relatedImages?: [...#RelatedImage]
}

#Property: {
  // type is required
  type: string & !=""

  // value is required, and it must not be null
  value: !=null
}

#RelatedImage: {
  // image is the image reference
  image: string & !=""

  // name is an optional descriptive name for an image that
  // helps identify its purpose in the context of the bundle
  name?: string & !=""
}

2.2.2.3. 属性

属性是可附加到基于文件的目录方案的任意元数据片段。type 字段是一个有效指定 value 字段语义和语法含义的字符串。该值可以是任意 JSON 或 YAML。

OLM 定义几个属性类型,再次使用保留的 olm.* 前缀。

2.2.2.3.1. olm.package 属性

olm.package 属性定义软件包名称和版本。这是捆绑包上的必要属性,必须正好有一个这些属性。packageName 字段必须与捆绑包的 first-class package 字段匹配,并且 version 字段必须是有效的语义版本。

例 2.4. olm.package 属性

#PropertyPackage: {
  type: "olm.package"
  value: {
    packageName: string & !=""
    version: string & !=""
  }
}
2.2.2.3.2. olm.gvk 属性

olm.gvk 属性定义此捆绑包提供的 Kubernetes API 的 group/version/kind (GVK)。OLM 使用此属性解析捆绑包,作为列出与所需 API 相同的 GVK 的其他捆绑包的依赖项。GVK 必须遵循 Kubernetes GVK 验证。

例 2.5. olm.gvk 属性

#PropertyGVK: {
  type: "olm.gvk"
  value: {
    group: string & !=""
    version: string & !=""
    kind: string & !=""
  }
}
2.2.2.3.3. olm.package.required

olm.package.required 属性定义此捆绑包需要的另一软件包的软件包名称和版本范围。对于捆绑包列表的每个所需软件包属性,OLM 确保集群中为列出的软件包和所需版本范围安装了一个 Operator。versionRange 字段必须是有效的语义版本(模拟)范围。

例 2.6. olm.package.required 属性

#PropertyPackageRequired: {
  type: "olm.package.required"
  value: {
    packageName: string & !=""
    versionRange: string & !=""
  }
}
2.2.2.3.4. olm.gvk.required

olm.gvk.required 属性定义此捆绑包需要的 Kubernetes API 的 group/version/kind (GVK)。对于捆绑包列表的每个必需的 GVK 属性,OLM 确保集群中安装了提供它的 Operator。GVK 必须遵循 Kubernetes GVK 验证。

例 2.7. olm.gvk.required 属性

#PropertyGVKRequired: {
  type: "olm.gvk.required"
  value: {
    group: string & !=""
    version: string & !=""
    kind: string & !=""
  }
}

2.2.2.4. 目录示例

对于基于文件的目录,目录维护人员可以专注于 Operator 策展和兼容性。由于 Operator 作者已为其 Operator 创建了特定于 Operator 的目录,因此目录维护人员可以通过将每个 Operator 目录渲染到目录根目录的子目录来构建其目录。

构建基于文件的目录的方法有很多;以下步骤概述了一个简单的方法:

  1. 为目录维护一个配置文件,其中包含目录中每个 Operator 的镜像引用:

    目录配置文件示例

    name: community-operators
    repo: quay.io/community-operators/catalog
    tag: latest
    references:
    - name: etcd-operator
      image: quay.io/etcd-operator/index@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
    - name: prometheus-operator
      image: quay.io/prometheus-operator/index@sha256:e258d248fda94c63753607f7c4494ee0fcbe92f1a76bfdac795c9d84101eb317

  2. 运行一个脚本,该脚本将解析配置文件并从其引用中创建新目录:

    脚本示例

    name=$(yq eval '.name' catalog.yaml)
    mkdir "$name"
    yq eval '.name + "/" + .references[].name' catalog.yaml | xargs mkdir
    for l in $(yq e '.name as $catalog | .references[] | .image + "|" + $catalog + "/" + .name + "/index.yaml"' catalog.yaml); do
      image=$(echo $l | cut -d'|' -f1)
      file=$(echo $l | cut -d'|' -f2)
      opm render "$image" > "$file"
    done
    opm alpha generate dockerfile "$name"
    indexImage=$(yq eval '.repo + ":" + .tag' catalog.yaml)
    docker build -t "$indexImage" -f "$name.Dockerfile" .
    docker push "$indexImage"

2.2.2.5. 指南

在维护基于文件的目录时,请考虑以下准则。

2.2.2.5.1. 不可变捆绑包

Operator Lifecycle Manager (OLM) 的常规建议是捆绑包镜像及其元数据应视为不可变。

如果一个错误的捆绑包被推送到目录,您必须假设至少有一个用户已升级到该捆绑包。基于这种假设,您必须从损坏的捆绑包中发布另一个带有升级边缘的捆绑包,以确保安装了有问题的捆绑包的用户收到升级。如果目录中更新了该捆绑包的内容,OLM 将不会重新安装已安装的捆绑包。

然而,在某些情况下首选更改目录元数据:

  • 频道升级:如果您已发布了捆绑包,且之后决定将其添加到另一个频道,您可以在另一个 olm.channel blob 中添加捆绑包条目。
  • 新的升级边缘:如果您发布一个新的 1.2.z 捆绑包版本,如 1.2.4,但 1.3.0 已发布,您可以更新 1.3.0 的目录元数据以跳过 1.2.4
2.2.2.5.2. 源控制

目录元数据应存储在源控制中,并被视为事实来源。目录镜像的更新应包括以下步骤:

  1. 使用新的提交来更新源控制的目录目录。
  2. 构建并推送目录镜像。使用一致的标记分类,如 :latest:<target_cluster_version>,以便用户可以在目录可用时接收到更新。

2.2.2.6. CLI 用法

有关使用 opm CLI 创建基于文件的目录的说明,请参阅管理自定义目录

有关管理基于文件的目录的 opm CLI 命令的参考文档,请参阅 CLI 工具

2.2.2.7. 自动化

建议 Operator 作者和目录维护人员使用 CI/CD 工作流自动化其目录维护。目录维护人员可通过构建 GitOps 自动化以完成以下任务来进一步改进:

  • 检查是否允许拉取请求 (PR) 作者进行请求的更改,例如更新其软件包的镜像引用。
  • 检查目录更新是否通过 opm validate 命令。
  • 检查是否有更新的捆绑包或目录镜像引用,目录镜像在集群中成功运行,来自该软件包的 Operator 可以成功安装。
  • 自动合并通过之前检查的 PR。
  • 自动重新构建和重新发布目录镜像。