5.6. 定义集群服务版本(CSV)


ClusterServiceVersion 对象定义的 集群服务版本(CSV)是一个利用 Operator 元数据创建的 YAML 清单,可辅助 Operator Lifecycle Manager(OLM)在集群中运行 Operator。它是 Operator 容器镜像附带的元数据,用于在用户界面填充徽标、描述和版本等信息。此外,CSV 还是运行 Operator 所需的技术信息来源,类似于其需要的 RBAC 规则及其管理或依赖的自定义资源 (CR)。

Operator SDK 包括 CSV 生成器,用于为当前 Operator 项目生成 CSV,使用 YAML 清单和 Operator 源文件中包含的信息自定义。

重要

红帽支持的 Operator SDK CLI 工具版本,包括 Operator 项目的相关构建和测试工具已被弃用,计划在以后的 Red Hat OpenShift Service on AWS 发行版本中删除。红帽将在当前发行生命周期中提供对这个功能的程序错误修复和支持,但此功能将不再获得改进,并将在以后的 Red Hat OpenShift Service on AWS 发行版本中删除。

对于创建新 Operator 项目,不建议使用红帽支持的 Operator SDK 版本。现有 Operator 项目的 Operator 作者可以使用 Red Hat OpenShift Service on AWS 4 发布的 Operator SDK CLI 工具版本来维护其项目,并创建针对较新版本的 Red Hat OpenShift Service on AWS 的 Operator 发行版本。

以下与 Operator 项目相关的基础镜像 没有被弃用。这些基础镜像的运行时功能和配置 API 仍然会有程序错误修复和并提供对相关 CVE 的解决方案。

  • 基于 Ansible 的 Operator 项目的基础镜像
  • 基于 Helm 的 Operator 项目的基础镜像

有关 Operator SDK 不支持的、社区维护版本的信息,请参阅 Operator SDK (Operator Framework)

借助生成 CSV 的命令,Operator 作者便无需深入掌握为了让其 Operator 与 OLM 交互或向 Catalog Registry 发布元数据所需的 OLM 知识。此外,因为实现了新的 Kubernetes 和 OLM 功能,CSV spec 可能会随着时间的推移而有所变化,而 Operator SDK 可轻松扩展其更新系统,以应对 CSV 的未来新功能。

5.6.1. CSV 生成的工作方式

Operator 捆绑包清单,其中包括集群服务版本(CSV),描述如何使用 Operator Lifecycle Manager(OLM)显示、创建和管理应用程序。Operator SDK 中的 CSV 生成器(由 generate bundle 子命令调用)是将 Operator 发布到目录并使用 OLM 部署的第一个步骤。子命令需要特定的输入清单来构造 CSV 清单,在调用命令时会读取所有输入,以及 CSV 基础,以便预先生成或重新生成 CSV。

通常,generate kustomize manifests 子命令会首先运行,以生成由 generate bundle 子命令使用的输入 Kustomize 基础。但是,Operator SDK 提供 make bundle 命令,它自动执行一些任务,包括按顺序运行以下子命令:

  1. generate kustomize manifests
  2. generate bundle
  3. bundle validate

其他资源

  • 如需了解包括生成捆绑包和 CSV 的完整流程,请参阅捆绑 Operator

5.6.1.1. 生成的文件和资源

make bundle 命令在 Operator 项目中创建以下文件和目录:

  • 名为 bundle/manifests 的捆绑包清单目录,其中包含 ClusterServiceVersion(CSV)对象
  • 名为 bundle/metadata 的捆绑包元数据目录
  • config/crd 目录中的所有自定义资源定义(CRD)
  • 一个 Dockerfile bundle.Dockerfile

以下资源通常包含在 CSV 中:

角色
定义命名空间中的 Operator 权限。
ClusterRole
定义集群范围的 Operator 权限。
Deployment
定义如何在 pod 中运行 Operator 的 Operand。
CustomResourceDefinition (CRD)
定义 Operator 协调的自定义资源。
自定义资源示例
遵循特定 CRD 规格的资源示例。

5.6.1.2. 版本管理

generate bundle 子命令的 --version 标志在首次创建以及升级现有捆绑包时,为您提供语义版本。

通过在 Makefile 中设置 VERSION 变量,当使用 make bundle 命令运行 generate bundle 子命令时使用该值自动调用 --version 标志。CSV 版本与 Operator 版本相同,在升级 Operator 版本时会生成新 CSV。

5.6.2. 手动定义的 CSV 字段

很多 CSV 字段无法使用生成的、不属于 Operator SDK 的特殊通用清单进行填充。这些字段大多由人工编写,是一些有关 Operator 和各种自定义资源定义 (CRD) 的元数据。

Operator 作者必须直接修改其集群服务版本(CSV)YAML 文件,将个性化数据添加到以下必填字段。当检测到任何必填字段中缺少数据时,Operator SDK 在生成 CSV 时发出警告。

下表详细介绍了需要手动定义的 CSV 字段,哪些是可选的。

表 5.7. 必需的 CSV 字段
字段描述

metadata.name

该 CSV 的唯一名称。Operator 版本应包含在名称中,以保证唯一性,如 app-operator.v0.1.1

metadata.capabilities

根据 Operator 成熟度模型划分的能力等级。选项包括 Basic InstallSeamless UpgradesFull LifecycleDeep InsightsAuto Pilot

spec.displayName

用于标识 Operator 的公共名称。

spec.description

有关 Operator 功能的简短描述。

spec.keywords

描述 Operator 的关键词。

spec.maintainers

维护 Operator 的个人或组织实体,含名称电子邮件地址

spec.provider

Operator 的供应商(通常是机构),含 名称

spec.labels

供 Operator 内部使用的键值对。

spec.version

Operator 的语义版本,如 0.1.1

spec.customresourcedefinitions

Operator 使用的任何 CRD。如果 deploy/ 中存在任何 CRD YAML 文件,Operator SDK 将自动填充该字段。但 CRD 清单 spec 中没有的几个字段需要用户输入:

  • description:CRD 描述。
  • resources:CRD 利用的任何 Kubernetes 资源,如 PodStatefulSet 对象。
  • specDescriptors:用于 Operator 输入和输出的 UI 提示。
表 5.8. 可选的 CSV 字段
字段描述

spec.replaces

被该 CSV 替换的 CSV 名称。

spec.links

与被管理的 Operator 或应用程序相关的 URL(如网站和文档),各自含名称url

spec.selector

Operator 可用于配对群集中资源的选择器。

spec.icon

Operator 独有的 base64 编码图标,通过 mediatypebase64data 字段中设置。

spec.maturity

软件在这个版本中达到的成熟度。选项包括 planningpre-alphaalphabetastablematureinactivedeprecated

有关以上每个字段应包含哪些数据的更多详情,请参见 CSV spec

注意

目前需要用户干预的几个 YAML 字段可能会从 Operator 代码中解析。

5.6.3. Operator 元数据注解

Operator 开发人员可以在集群服务版本(CSV)的元数据中设置某些注解,以启用功能或在用户界面(UI)中突出显示功能,如 OperatorHub 或 Red Hat Ecosystem Catalog。通过在 CSV YAML 文件中设置 metadata.annotations 字段来手动定义 Operator 元数据注解。

5.6.3.1. 基础架构功能注解

features.operators.openshift.io 组中的注解详细说明 Operator 可能支持的基础架构功能,通过设置 "true""false" 值来指定。在 web 控制台或 红帽生态系统目录 中通过 OperatorHub 发现 Operator 时,用户可以查看和过滤这些功能。Red Hat OpenShift Service on AWS 4.10 及更新的版本支持这些注解。

重要

features.operators.openshift.io 基础架构功能注解弃用 Red Hat OpenShift Service on AWS 的早期版本中使用的 operators.openshift.io/infrastructure-features 注解。如需更多信息,请参阅"过时的基础架构功能注解"。

表 5.9. 基础架构功能注解
注解描述有效值[1]

features.operators.openshift.io/disconnected

指定 Operator 支持被镜像到断开连接的目录中,包括所有依赖项,且不需要访问互联网。Operator 利用 spec.relatedImages CSV 字段来根据其摘要引用任何相关镜像。

"true" or "false"

features.operators.openshift.io/fips-compliant

指定 Operator 是否接受底层平台的 FIPS-140 配置,并可用于引导到 FIPS 模式的节点。在这个模式中,Operator 及其管理(操作)的任何工作负载都只调用为 FIPS-140 验证提交的 Red Hat Enterprise Linux (RHEL)加密库。

"true" or "false"

features.operators.openshift.io/proxy-aware

通过接受标准 HTTP_PROXYHTTPS_PROXY 代理环境变量来指定 Operator 支持在代理后面的集群中运行。如果适用,Operator 会将此信息传递给它管理的工作负载(操作)。

"true" or "false"

features.operators.openshift.io/tls-profiles

指定 Operator 是否实现已知的可调项,以修改 Operator 使用的 TLS 密码套件;如果适用,它管理的任何工作负载(操作)。

"true" or "false"

features.operators.openshift.io/token-auth-aws

使用 Cloud Credential Operator (CCO)指定 Operator 是否支持通过 AWS Secure Token Service (STS)配置 AWS API 的令牌验证。

"true" or "false"

features.operators.openshift.io/token-auth-azure

指定 Operator 是否支持使用 Cloud Credential Operator (CCO)通过 Azure Managed Identity 配置对 Azure API 进行令牌身份验证。

"true" or "false"

features.operators.openshift.io/token-auth-gcp

使用 Cloud Credential Operator (CCO)指定 Operator 是否支持通过 GCP Workload Identity Foundation (WIF)配置 Google Cloud API 进行令牌化身份验证。

"true" or "false"

features.operators.openshift.io/cnf

指定 Operator 是否提供 Cloud-Native Network Function (CNF) Kubernetes 插件。

"true" or "false"

features.operators.openshift.io/cni

指定 Operator 是否提供 Container Network Interface (CNI) Kubernetes 插件。

"true" or "false"

features.operators.openshift.io/csi

指定 Operator 是否提供 Container Storage Interface (CSI) Kubernetes 插件。

"true" or "false"

  1. 有效值会有意显示双引号,因为 Kubernetes 注解必须是字符串。

具有基础架构功能注解的 CSV 示例

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
    features.operators.openshift.io/disconnected: "true"
    features.operators.openshift.io/fips-compliant: "false"
    features.operators.openshift.io/proxy-aware: "false"
    features.operators.openshift.io/tls-profiles: "false"
    features.operators.openshift.io/token-auth-aws: "false"
    features.operators.openshift.io/token-auth-azure: "false"
    features.operators.openshift.io/token-auth-gcp: "false"

其他资源

5.6.3.2. 弃用的基础架构功能注解

从 Red Hat OpenShift Service on AWS 4.14 开始,operator.openshift.io/infrastructure-features 组注解组使用 features.operators.openshift.io 命名空间已弃用。虽然我们鼓励使用较新的注解,但当前会并行使用这两个组。

这些注解详细介绍了 Operator 支持的基础架构功能。在 web 控制台或 红帽生态系统目录 中通过 OperatorHub 发现 Operator 时,用户可以查看和过滤这些功能。

表 5.10. 弃用的 operators.openshift.io/infrastructure-features 注解
有效注解值描述

断开连接

Operator 支持被镜像到断开连接的目录中,包括所有依赖项,且不需要访问互联网。Operator 列出了镜像所需的所有相关镜像。

cnf

Operator 提供了一个 Cloud-native Network Function (CNF) Kubernetes 插件。

CNI

Operator 提供了一个 Container Network Interface (CNI) Kubernetes 插件。

csi

Operator 提供了一个 Container Storage Interface (CSI) Kubernetes 插件。

fips

Operator 接受底层平台的 FIPS 模式,并可用于引导到 FIPS 模式的节点。

重要

当以 FIPS 模式运行 Red Hat Enterprise Linux (RHEL)或 Red Hat Enterprise Linux CoreOS (RHCOS)时,Red Hat OpenShift Service on AWS 核心组件使用 RHEL 加密库,在 x86_64、ppc64le 和 s390x 架构上提交给 NIST 的 FIPS 140-2/140-3 Validation。

proxy-aware

Operator 支持在代理后面的集群上运行。Operator 接受标准代理环境变量 HTTP_PROXYHTTPS_PROXY,Operator Lifecycle Manager(OLM)在集群配置为使用代理时自动为 Operator 提供这些环境变量。传递给受管工作负载的 Operands 所需的环境变量。

带有 断开连接的 和代理支持的 CSV 示例

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
    operators.openshift.io/infrastructure-features: '["disconnected", "proxy-aware"]'

5.6.3.3. 其他可选注解

以下 Operator 注解是可选的。

表 5.11. 其他可选注解
注解描述

alm-examples

提供自定义资源定义(CRD)模板最小配置集。兼容的 UI 会预先填充此模板,供用户进一步自定义。

operatorframework.io/initialization-resource

通过在 Operator 安装过程中将 operatorframework.io/initialization-resource 注解添加到集群服务版本 (CSV) 来指定所需的自定义资源。然后,系统会提示您通过 CSV 中提供的模板创建自定义资源。必须包含带有完整 YAML 定义的模板。

operatorframework.io/suggested-namespace

设置部署 Operator 的建议命名空间。

operatorframework.io/suggested-namespace-template

Namespace 对象设置清单,为指定命名空间使用默认节点选择器。

operators.openshift.io/valid-subscription

用于列出使用 Operator 所需的任何特定订阅的空闲数组。例如,'["3Scale Commercial License", "Red Hat Managed Integration"]'

operators.operatorframework.io/internal-objects

在 UI 中隐藏不用于用户操作的 CRD。

具有 Red Hat OpenShift Service on AWS 许可证要求的 CSV 示例

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
    operators.openshift.io/valid-subscription: '["OpenShift Container Platform"]'

具有 3scale 许可证要求的 CSV 示例

apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
  annotations:
    operators.openshift.io/valid-subscription: '["3Scale Commercial License", "Red Hat Managed Integration"]'

5.6.4. 为受限网络环境启用 Operator

作为 Operator 作者,您的 Operator 必须满足额外要求才能在受限网络或断开连接的环境中正常运行。

支持断开连接模式的 Operator 的要求

  • 使用环境变量替换硬编码的镜像引用。
  • 在 Operator 的集群服务版本(CSV)中:

    • 列出 Operator 执行其功能可能需要的任何 相关镜像或其他容器镜像。
    • 通过摘要 (SHA) 而不是标签来引用所有指定的镜像。
  • Operator 的所有依赖项还必须支持以断开连接的模式运行。
  • 您的 Operator 不得要求任何非集群资源。

前提条件

  • 包含 CSV 的 Operator 项目。以下流程使用 Memcached Operator 作为基于 Go-、Ansible 和 Helm 的项目的示例。

流程

  1. 为 Operator 在 config/manager/manager.yaml 文件中被 Operator 使用的额外镜像引用设置环境变量:

    例 5.3. config/manager/manager.yaml 文件示例

    ...
    spec:
      ...
        spec:
          ...
          containers:
          - command:
            - /manager
            ...
            env:
            - name: <related_image_environment_variable> 1
              value: "<related_image_reference_with_tag>" 2
    1
    定义 环境变量,如 RELATED_IMAGE_MEMCACHED
    2
    设置相关的镜像引用和标签,如 docker.io/memcached:1.4.36-alpine
  2. 将硬编码镜像引用替换为 Operator 项目类型的相关文件中的环境变量:

    • 对于基于 Go 的 Operator 项目,将环境变量添加到 controllers/memcached_controller.go 文件中,如下例所示:

      例 5.4. controllers/memcached_controller.go 文件示例

        // deploymentForMemcached returns a memcached Deployment object
      
      ...
      
      	Spec: corev1.PodSpec{
              	Containers: []corev1.Container{{
      -			Image:   "memcached:1.4.36-alpine", 1
      +			Image:   os.Getenv("<related_image_environment_variable>"), 2
      			Name:    "memcached",
      			Command: []string{"memcached", "-m=64", "-o", "modern", "-v"},
      			Ports: []corev1.ContainerPort{{
      
      ...
      1
      删除镜像引用和标签。
      2
      使用 os.Getenv 函数调用 <related_image_environment_variable>
      注意

      如果未设置变量,则 os.Getenv 函数会返回空字符串。在更改文件前设置 <related_image_environment_variable>

    • 对于基于 Ansible 的 Operator 项目,将环境变量添加到 roles/memcached/tasks/main.yml 文件,如下例所示:

      例 5.5. roles/memcached/tasks/main.yml 文件示例

      spec:
        containers:
        - name: memcached
          command:
          - memcached
          - -m=64
          - -o
          - modern
          - -v
      -   image: "docker.io/memcached:1.4.36-alpine" 1
      +   image: "{{ lookup('env', '<related_image_environment_variable>') }}" 2
          ports:
            - containerPort: 11211
      
      ...
      1
      删除镜像引用和标签。
      2
      使用 lookup 功能调用 <related_image_environment_variable>
    • 对于基于 Helm 的 Operator 项目,将 overrideValues 字段添加到 watches.yaml 文件中,如下例所示:

      例 5.6. watches.yaml 文件示例

      ...
      - group: demo.example.com
        version: v1alpha1
        kind: Memcached
        chart: helm-charts/memcached
        overrideValues: 1
          relatedImage: ${<related_image_environment_variable>} 2
      1
      添加 overrideValues 字段。
      2
      使用 <related_image_environment_variable> 来定义 overrideValues 字段,如 RELATED_IMAGE_MEMCACHED
      1. overrideValues 字段的值添加到 helm-charts/memchached/values.yaml 文件中,如下例所示:

        helm-charts/memchached/values.yaml 文件示例

        ...
        relatedImage: ""

      2. 编辑 helm-charts/memcached/templates/deployment.yaml 文件中的 chart 模板,如下例所示:

        例 5.7. helm-charts/memcached/templates/deployment.yaml 文件示例

        containers:
          - name: {{ .Chart.Name }}
            securityContext:
              - toYaml {{ .Values.securityContext | nindent 12 }}
            image: "{{ .Values.image.pullPolicy }}
            env: 1
              - name: related_image 2
                value: "{{ .Values.relatedImage }}" 3
        1
        添加 env 字段。
        2
        命名环境变量。
        3
        定义环境变量的值。
  3. 使用以下更改将 BUNDLE_GEN_FLAGS 变量定义添加到 Makefile 中:

    Makefile示例

       BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
    
       # USE_IMAGE_DIGESTS defines if images are resolved via tags or digests
       # You can enable this value if you would like to use SHA Based Digests
       # To enable set flag to true
       USE_IMAGE_DIGESTS ?= false
       ifeq ($(USE_IMAGE_DIGESTS), true)
             BUNDLE_GEN_FLAGS += --use-image-digests
       endif
    
    ...
    
    -  $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 1
    +  $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) 2
    
    ...

    1
    删除 Makefile 中的这一行。
    2
    将上面的行替换为这一行。
  4. 要将 Operator 镜像更新为使用摘要(SHA)而不是标签,请运行 make bundle 命令,并将 USE_IMAGE_DIGESTS 设置为 true

    $ make bundle USE_IMAGE_DIGESTS=true
  5. 添加 disconnected 注解,这表示 Operator 在断开连接的环境中工作:

    metadata:
      annotations:
        operators.openshift.io/infrastructure-features: '["disconnected"]'

    OperatorHub 中可根据此基础架构功能来过滤 Operator。

5.6.5. 为多个架构和操作系统启用您的 Operator

Operator Lifecycle Manager (OLM) 假设所有 Operator 都在 Linux 主机中运行。但是,作为 Operator 作者,如果 Red Hat OpenShift Service on AWS 集群中提供了 worker 节点,您可以指定 Operator 是否支持管理其他架构上的工作负载。

如果 Operator 支持 AMD64 和 Linux 以外的变体,您可以向 CSV 添加标签,从而提供 Operator 列出支持的变体。标注支持的架构和操作系统的标签定义如下:

labels:
    operatorframework.io/arch.<arch>: supported 1
    operatorframework.io/os.<os>: supported 2
1
<arch> 设置为受支持的字符串。
2
<os> 设置为受支持的字符串。
注意

只有默认频道的频道头的标签才会在根据标签进行过滤时考虑软件包清单。例如,这表示有可能在非默认频道中为 Operator 提供额外的架构,但该架构在 PackageManifest API 中不可用。

如果 CSV 不包括 os 标签,它将被视为默认具有以下 Linux 支持标签:

labels:
    operatorframework.io/os.linux: supported

如果 CSV 不包括 arch 标签,它将被视为默认具有以下 AMD64 支持标签:

labels:
    operatorframework.io/arch.amd64: supported

如果 Operator 支持多个节点架构或操作系统,您也可以添加多个标签。

先决条件

  • 包含 CSV 的 Operator 项目。
  • 要支持列出多个架构和操作系统,CSV 中引用的 Operator 镜像必须是清单列表镜像。
  • 要使 Operator 在受限网络或断开连接的环境中正常工作,还必须使用摘要(SHA)而不是标签(tag)来指定引用的镜像。

流程

  • 在 CSV 的 metadata.labels 中为每个 Operator 支持的架构和操作系统添加标签:

    labels:
      operatorframework.io/arch.s390x: supported
      operatorframework.io/os.zos: supported
      operatorframework.io/os.linux: supported 1
      operatorframework.io/arch.amd64: supported 2
    1 2
    在添加新的构架或操作系统后,您必须明确包含默认的 os.linuxarch.amd64 变体。

其他资源

5.6.5.1. Operator 的架构和操作系统支持

在标记或过滤支持多个架构和操作系统的 Operator 时,Red Hat OpenShift Service on AWS 上的 Operator Lifecycle Manager (OLM)支持以下字符串:

表 5.12. Red Hat OpenShift Service on AWS 支持的架构
架构字符串

AMD64

amd64

ARM64

arm64

IBM Power®

ppc64le

IBM Z®

s390x

表 5.13. Red Hat OpenShift Service on AWS 支持的操作系统
操作系统字符串

Linux

linux

z/OS

zos

注意

AWS 上的不同版本的 Red Hat OpenShift Service 和其他基于 Kubernetes 的发行版本可能支持一组不同的架构和操作系统。

5.6.6. 设置建议的命名空间

有些 Operator 必须部署到特定命名空间中,或使用特定命名空间中的辅助资源进行部署,才能正常工作。如果从订阅中解析,Operator Lifecycle Manager(OLM)会将 Operator 的命名空间的资源默认设置为订阅的命名空间。

作为 Operator 作者,您可以将所需的目标命名空间作为集群服务版本(CSV)的一部分来控制为 Operator 安装的资源的最终命名空间。当使用 OperatorHub 将 Operator 添加到集群时,这可让 Web 控制台在安装过程中为安装程序自动填充建议的命名空间。

流程

  • 在 CSV 中,将 operatorframework.io/suggested-namespace 注解设置为建议的命名空间:

    metadata:
      annotations:
        operatorframework.io/suggested-namespace: <namespace> 1
    1
    设置建议的命名空间。

5.6.7. 使用默认节点选择器设置建议的命名空间

有些 Operator 只在 control plane 节点上运行,这可以通过由 Operator 本身在 Pod 规格中设置 nodeSelector 来完成。

为了避免重复重复且可能冲突集群范围的默认 nodeSelector,您可以在运行 Operator 的命名空间上设置默认节点选择器。默认节点选择器优先于集群默认值,因此集群默认不会应用到 Operator 命名空间中的 pod。

当使用 OperatorHub 将 Operator 添加到集群时,Web 控制台会在安装过程中自动填充安装程序的建议命名空间。建议的命名空间使用 YAML 中的命名空间清单创建,该清单包含在集群服务版本 (CSV) 中。

流程

  • 在 CSV 中,使用 Namespace 对象的清单设置 operatorframework.io/suggested-namespace-template。以下示例是指定了命名空间默认节点选择器的示例命名空间的清单:

    metadata:
      annotations:
        operatorframework.io/suggested-namespace-template: 1
          {
            "apiVersion": "v1",
            "kind": "Namespace",
            "metadata": {
              "name": "vertical-pod-autoscaler-suggested-template",
              "annotations": {
                "openshift.io/node-selector": ""
              }
            }
          }
    1
    设置建议的命名空间。
    注意

    如果 CSV 中存在 recommendations-namespacerecommended-namespace-template 注解,则 recommend -namespace-template 应该优先使用。

5.6.8. 启用 Operator 条件

Operator Lifecycle Manager(OLM)为 Operator 提供一个频道来交流影响 Operator 在管理 Operator 的复杂状态。默认情况下,OLM 在安装 Operator 时会创建一个 OperatorCondition 自定义资源定义(CRD)。根据 OperatorCondition 自定义资源(CR)中设置的条件,OLM 的行为会相应更改。

要支持 Operator 条件,Operator 必须能够读取 OLM 创建的 OperatorCondition CR,并能够完成以下任务:

  • 获取特定条件。
  • 设置特定条件的状态。

这可以通过使用 operator-lib 库来实现。Operator 作者可在 Operator 中提供 controller-runtime 客户端,以便该程序库访问集群中 Operator 拥有的 OperatorCondition CR。

该程序库提供了一个通用的 Conditions 接口,它使以下方法在 OperatorCondition CR 中 GetSet 一个 conditionType

Get
要获得特定条件,程序库使用来自 controller-runtimeclient.Get 函数,它需要在 conditionAccessor 中存在类型 type.NamespacedNameObjectKey
Set
要更新特定条件的状态,程序库使用来自 controller-runtimeclient.Update 功能。如果 CRD 中不存在 conditionType,则会出现错误。

Operator 只允许修改 CR 的 status 子资源。operator 可以删除或更新 status.conditions 数组,使其包含条件。有关条件中字段的格式和描述的详情,请查看上游的条件 GoDocs

注意

Operator SDK 1.36.1 支持 operator-lib v0.11.0。

前提条件

  • 使用 Operator SDK 生成一个 Operator 项目。

流程

在 Operator 项目中启用 Operator 条件:

  1. 在 Operator 项目的 go.mod 文件中,将 operator-framework/operator-lib 添加为所需的库:

    module github.com/example-inc/memcached-operator
    
    go 1.19
    
    require (
      k8s.io/apimachinery v0.26.0
      k8s.io/client-go v0.26.0
      sigs.k8s.io/controller-runtime v0.14.1
      operator-framework/operator-lib v0.11.0
    )
  2. 在 Operator 逻辑中编写您自己的构造器会导致以下结果:

    • 接受 controller-runtime 客户端。
    • 接受 conditionType
    • 返回一个 Condition 接口以更新或添加条件。

    由于 OLM 目前支持 Upgradeable 条件,因此可以创建一个接口,它具有访问 Upgradeable 条件的方法。例如:

    import (
      ...
      apiv1 "github.com/operator-framework/api/pkg/operators/v1"
    )
    
    func NewUpgradeable(cl client.Client) (Condition, error) {
      return NewCondition(cl, "apiv1.OperatorUpgradeable")
    }
    
    cond, err := NewUpgradeable(cl);

    在这个示例中,NewUpgradeable constructor 被进一步使用来创建类型为 Condition 的一个变量 condcond 变量依次使用 GetSet 方法,可用于处理 OLM 的 Upgradeable 条件。

其他资源

5.6.9. 定义 webhook

Webhook 允许 Operator 作者在资源被保存到对象存储并由 Operator 控制器处理之前,拦截、修改、接受或拒绝资源。当 webhook 与 Operator 一同提供时,Operator Lifecycle Manager(OLM)可以管理这些 webhook 的生命周期。

Operator 的集群服务版本(CSV)资源可能包含 webhookdefinitions 部分,以定义以下 Webhook 类型:

  • Admission webhook(validating and mutating)
  • webhook 转换

流程

  • 在 Operator 的 spec 部分添加 webhookdefinitions 部分,并使用 ValidatingAdmissionWebhookMutatingAdmissionWebhookConversionWebhook type 包括任何 webhook 定义。以下示例包含所有三种类型的 Webhook:

    包含 Webhook 的 CSV

      apiVersion: operators.coreos.com/v1alpha1
      kind: ClusterServiceVersion
      metadata:
        name: webhook-operator.v0.0.1
      spec:
        customresourcedefinitions:
          owned:
          - kind: WebhookTest
            name: webhooktests.webhook.operators.coreos.io 1
            version: v1
        install:
          spec:
            deployments:
            - name: webhook-operator-webhook
              ...
              ...
              ...
          strategy: deployment
        installModes:
        - supported: false
          type: OwnNamespace
        - supported: false
          type: SingleNamespace
        - supported: false
          type: MultiNamespace
        - supported: true
          type: AllNamespaces
        webhookdefinitions:
        - type: ValidatingAdmissionWebhook 2
          admissionReviewVersions:
          - v1beta1
          - v1
          containerPort: 443
          targetPort: 4343
          deploymentName: webhook-operator-webhook
          failurePolicy: Fail
          generateName: vwebhooktest.kb.io
          rules:
          - apiGroups:
            - webhook.operators.coreos.io
            apiVersions:
            - v1
            operations:
            - CREATE
            - UPDATE
            resources:
            - webhooktests
          sideEffects: None
          webhookPath: /validate-webhook-operators-coreos-io-v1-webhooktest
        - type: MutatingAdmissionWebhook 3
          admissionReviewVersions:
          - v1beta1
          - v1
          containerPort: 443
          targetPort: 4343
          deploymentName: webhook-operator-webhook
          failurePolicy: Fail
          generateName: mwebhooktest.kb.io
          rules:
          - apiGroups:
            - webhook.operators.coreos.io
            apiVersions:
            - v1
            operations:
            - CREATE
            - UPDATE
            resources:
            - webhooktests
          sideEffects: None
          webhookPath: /mutate-webhook-operators-coreos-io-v1-webhooktest
        - type: ConversionWebhook 4
          admissionReviewVersions:
          - v1beta1
          - v1
          containerPort: 443
          targetPort: 4343
          deploymentName: webhook-operator-webhook
          generateName: cwebhooktest.kb.io
          sideEffects: None
          webhookPath: /convert
          conversionCRDs:
          - webhooktests.webhook.operators.coreos.io 5
    ...

    1
    转换 Webhook 的目标 CRD 必须在这里存在。
    2
    验证准入 Webhook。
    3
    变异准入 Webhook。
    4
    转换 Webhook。
    5
    每个 CRD 的 spec.PreserveUnknownFields 属性必须设置为 falsenil

其他资源

5.6.9.1. 针对 OLM 的 Webhook 注意事项

使用 Operator Lifecycle Manager(OLM)部署带有 webhook 的 Operator 时,您必须定义以下内容:

  • type 字段必须设置为 ValidatingAdmissionWebhookMutatingAdmissionWebhookConversionWebhook,否则 CSV 会进入失败的阶段。
  • CSV 必须包含一个部署,它的名称相当于 webhookdefinitiondeploymentName 字段中提供的值。

创建 webhook 时,OLM 确保 webhook 仅在与 Operator 部署的 Operator 组相匹配的命名空间上操作。

证书颁发机构限制

将 OLM 配置为为每个部署提供一个单独的证书颁发机构(CA)。将 CA 生成并挂载到部署的逻辑最初由 API 服务生命周期逻辑使用。因此:

  • TLS 证书文件挂载到部署的 /apiserver.local.config/certificates/apiserver.crt
  • TLS 密钥文件挂载到部署的 /apiserver.local.config/certificates/apiserver.key
Admission webhook 规则约束

为防止 Operator 将集群配置为无法恢复的状态,OLM 如果准入 webhook 中定义的规则拦截了以下请求中的规则,则 OLM 会将 CSV 放置到失败的阶段:

  • 请求目标所有组
  • 请求以 operators.coreos.com 组为目标
  • 请求目标为 ValidatingWebhookConfigurationsMutatingWebhookConfigurations 资源
转换 Webhook 约束

如果转换 Webhook 定义未遵循以下限制,OLM 会将 CSV 放置到失败的阶段:

  • 带有转换 Webhook 的 CSV 只能支持 AllNamespaces 安装模式。
  • 转换 Webhook 的目标 CRD 必须将其 spec.preserveUnknownFields 字段设置为 falsenil
  • CSV 中定义的转换 webhook 必须针对拥有的 CRD。
  • 在整个集群中,给定 CRD 只能有一个转换 Webhook。

5.6.10. 了解您的自定义资源定义(CRD)

您的 Operator 可能会使用两类自定义资源定义 (CRD):一类归 Operator 拥有,另一类为 Operator 依赖的必要 CRD。

5.6.10.1. 拥有的 CRD

Operator 拥有的自定义资源定义(CRD)是 CSV 最重要的部分。这类 CRD 会在您的 Operator 与所需 RBAC 规则、依赖项管理和其他 Kubernetes 概念之间建立联系。

Operator 通常会使用多个 CRD 将各个概念链接在一起,例如一个对象中的顶级数据库配置和另一对象中的副本集表示代表。这在 CSV 文件中应逐一列出。

表 5.14. 拥有的 CRD 字段
字段描述必需/可选

名称

CRD 的全名。

必填

Version

该对象 API 的版本。

必填

Kind

CRD 的机器可读名称。

必填

DisplayName

CRD 名称的人类可读版本,如 MongoDB Standalone

必填

描述

有关 Operator 如何使用该 CRD 的简短描述,或有关 CRD 所提供功能的描述。

必填

Group

该 CRD 所属的 API 组,如 database.example.com

选填

Resources

您的 CRD 可能拥有一类或多类 Kubernetes 对象。它们将在 resources 部分列出,用于告知用户他们可能需要排除故障的对象或如何连接至应用程序,如公开数据库的服务或 Ingress 规则。

建议仅列出对人重要的对象,而不必列出您编排的所有对象。例如,不要列出存储用户不会修改的内部状态的配置映射。

选填

SpecDescriptorsStatusDescriptorsActionDescriptors

这些描述符是通过对终端用户来说最重要的 Operator 的某些输入或输出提示 UI 的一种方式。如果您的 CRD 包含用户必须提供的 Secret 或 ConfigMap 的名称,您可在此处指定。这些项目在兼容的 UI 中链接并突出显示。

共有以下三类描述符:

  • SpecDescriptors:引用对象 spec 块中的字段。
  • StatusDescriptors:引用对象 status 块中的字段。
  • ActionDescriptors:引用对象上可执行的操作。

所有描述符都接受以下字段:

  • DisplayName: SpecStatusAction 的人类可读名称。
  • Description:有关 SpecStatusAction 以及 Operator 如何使用它的简短描述。
  • Path:描述符描述的对象上字段的点分隔路径。
  • X-Descriptors:用于决定该描述符拥有哪些“功能”以及要使用哪个 UI 组件。如需 Red Hat OpenShift Service on AWS 的 React UI X-Descriptors 列表,请参阅 openshift/console 项目。

有关描述符的更多一般信息,请参见 openshift/console 项目。

选填

以下示例描述了一个 MongoDB Standalone CRD,要求某些用户以 Secret 和配置映射的形式输入,并编排服务、有状态集、pod 和 配置映射:

拥有的 CRD 示例

      - displayName: MongoDB Standalone
        group: mongodb.com
        kind: MongoDbStandalone
        name: mongodbstandalones.mongodb.com
        resources:
          - kind: Service
            name: ''
            version: v1
          - kind: StatefulSet
            name: ''
            version: v1beta2
          - kind: Pod
            name: ''
            version: v1
          - kind: ConfigMap
            name: ''
            version: v1
        specDescriptors:
          - description: Credentials for Ops Manager or Cloud Manager.
            displayName: Credentials
            path: credentials
            x-descriptors:
              - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:Secret'
          - description: Project this deployment belongs to.
            displayName: Project
            path: project
            x-descriptors:
              - 'urn:alm:descriptor:com.tectonic.ui:selector:core:v1:ConfigMap'
          - description: MongoDB version to be installed.
            displayName: Version
            path: version
            x-descriptors:
              - 'urn:alm:descriptor:com.tectonic.ui:label'
        statusDescriptors:
          - description: The status of each of the pods for the MongoDB cluster.
            displayName: Pod Status
            path: pods
            x-descriptors:
              - 'urn:alm:descriptor:com.tectonic.ui:podStatuses'
        version: v1
        description: >-
          MongoDB Deployment consisting of only one host. No replication of
          data.

5.6.10.2. 必需的 CRD

是否依赖其他必需 CRD 完全可以自由选择,它们存在的目的只是为了缩小单个 Operator 的范围,并提供一种将多个 Operator 组合到一起来解决端到端用例的办法。

例如,一个 Operator 可设置一个应用程序并(从 etcd Operator)安装一个 etcd 集群以用于分布式锁定,以及一个 Postgres 数据库(来自 Postgres Operator)以用于数据存储。

Operator Lifecycle Manager (OLM) 对照集群中可用的 CRD 和 Operator 进行检查,以满足这些要求。如果找到合适的版本,Operator 将在所需命名空间中启动,并为每个 Operator 创建一个服务账户,以创建、监视和修改所需的 Kubernetes 资源。

表 5.15. 必需的 CRD 字段
字段描述必需/可选

名称

所需 CRD 的全称。

必填

Version

该对象 API 的版本。

必填

Kind

Kubernetes 对象类型。

必填

DisplayName

CRD 的人类可读版本。

必填

描述

概述该组件如何适合您的更大架构。

必填

必需的 CRD 示例

    required:
    - name: etcdclusters.etcd.database.coreos.com
      version: v1beta2
      kind: EtcdCluster
      displayName: etcd Cluster
      description: Represents a cluster of etcd nodes.

5.6.10.3. CRD 升级

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

  • 所有已存在于当前 CRD 中的服务版本都包括在新 CRD 中。
  • 在根据新 CRD 的验证模式(schema)进行验证后,与 CRD 的服务版本关联的所有现有实例或自定义资源均有效。
5.6.10.3.1. 添加新版 CRD

流程

将新版 CRD 添加到 Operator:

  1. 在 CSV 的 versions 部分的 CRD 资源中添加新条目。

    例如,如果当前 CRD 有一个 v1alpha1 版本,而您想要添加新的 v1beta1 版本并 将其标记为新的存储版本,请为 v1beta1 添加新条目:

    versions:
      - name: v1alpha1
        served: true
        storage: false
      - name: v1beta1 1
        served: true
        storage: true
    1
    新条目。
  2. 如果 CSV 打算使用新版本,请确保更新您的 CSV owned 部分中的 CRD 引用版本:

    customresourcedefinitions:
      owned:
      - name: cluster.example.com
        version: v1beta1 1
        kind: cluster
        displayName: Cluster
    1
    更新 version
  3. 将更新的 CRD 和 CSV 推送至您的捆绑包中。
5.6.10.3.2. 弃用或删除 CRD 版本

Operator Lifecycle Manager(OLM)不允许立即删除自定义资源定义(CRD)的服务版本。弃用的 CRD 版本应首先通过将 CRD 的 served 字段设置为 false 来禁用。随后在升级 CRD 时便可将非服务版本删除。

流程

要弃用和删除特定 CRD 版本:

  1. 将弃用版本标记为非服务版本,表明该版本已不再使用且后续升级时可删除。例如:

    versions:
      - name: v1alpha1
        served: false 1
        storage: true
    1
    设置为 false
  2. 如果要弃用的版本目前为 storage 版本,则将该 storage 版本切换至服务版本。例如:

    versions:
      - name: v1alpha1
        served: false
        storage: false 1
      - name: v1beta1
        served: true
        storage: true 2
    1 2
    对应更新 storage 字段。
    注意

    要从 CRD 中删除曾是或现在是 storage 的特定版本,该版本必须从 CRD 状态下的 storedVersion 中删除。OLM 一旦检测到某个已存储版本在新 CRD 中不再存在,OLM 将尝试执行这一操作。

  3. 使用以上更改来升级 CRD。
  4. 在后续升级周期中,非服务版本可从 CRD 中完全删除。例如:

    versions:
      - name: v1beta1
        served: true
        storage: true
  5. 如果该版本已从 CRD 中删除,请确保相应更新您的 CSV owned 部分中的引用 CRD 版本。

5.6.10.4. CRD 模板

Operator 用户必须了解哪个选项必填,而不是可选选项。您可为您的每个 CRD 提供模板,并以最小配置集作为名为 alm-examples 的注解。兼容 UI 会预先填充该模板,供用户进一步自定义。

该注解由一个 kind 列表组成,如 CRD 名称和对应的 Kubernetes 对象的 metadataspec

以下完整示例提供了 EtcdClusterEtcdBackupEtcdRestore 模板:

metadata:
  annotations:
    alm-examples: >-
      [{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"<operator_namespace>"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":["<etcd-cluster-endpoints>"],"storageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}}]

5.6.10.5. 隐藏内部对象

Operator 在内部使用自定义资源定义 (CRD) 来完成任务是常见的。这些对象并不是供用户操作的,且可能会让 Operator 用户混淆。例如,数据库 Operator 可能会有一个 Replication CRD,当用户创建带有 replication:true 的数据库对象时就会创建它。

作为 Operator 作者,您可以通过将 operators.operatorframework.io/internal-objects 注解添加到 Operator 的 ClusterServiceVersion(CSV)来隐藏用户界面中不用于用户操作的任何 CRD。

流程

  1. 在将一个 CRD 标记为 internal 之前,请确保任何管理应用程序所需的调试信息或配置都会反映在 CR 的状态或 spec 块中(如果适用于您的 Operator)。
  2. 向 Operator 的 CSV 添加 operators.operatorframework.io/internal-objects 注解,以指定要在用户界面中隐藏的任何内部对象:

    内部对象注解

    apiVersion: operators.coreos.com/v1alpha1
    kind: ClusterServiceVersion
    metadata:
      name: my-operator-v1.2.3
      annotations:
        operators.operatorframework.io/internal-objects: '["my.internal.crd1.io","my.internal.crd2.io"]' 1
    ...

    1
    将任何内部 CRD 设置为字符串数组。

5.6.10.6. 初始化所需的自定义资源

Operator 可能需要用户在 Operator 完全正常工作前实例化自定义资源。然而,用户很难确定需要什么或怎样定义资源。

作为 Operator 开发人员,您可以通过在 Operator 安装过程中将 operatorframework.io/initialization-resource 添加到集群服务版本 (CSV) 来指定单个所需的自定义资源。然后,系统会提示您通过 CSV 中提供的模板创建自定义资源。该注解必须有包含完整 YAML 定义模板,该定义是在安装过程中初始化资源所需的。

如果定义了此注解,则在从 Red Hat OpenShift Service on AWS Web 控制台安装 Operator 后,系统会提示用户使用 CSV 中提供的模板创建资源。

流程

  • 为 Operator 的 CSV 添加 operatorframework.io/initialization-resource 注解,以指定所需的自定义资源。例如,以下注解需要创建 StorageCluster 资源,并提供完整的 YAML 定义:

    初始化资源注解

    apiVersion: operators.coreos.com/v1alpha1
    kind: ClusterServiceVersion
    metadata:
      name: my-operator-v1.2.3
      annotations:
        operatorframework.io/initialization-resource: |-
            {
                "apiVersion": "ocs.openshift.io/v1",
                "kind": "StorageCluster",
                "metadata": {
                    "name": "example-storagecluster"
                },
                "spec": {
                    "manageNodes": false,
                    "monPVCTemplate": {
                        "spec": {
                            "accessModes": [
                                "ReadWriteOnce"
                            ],
                            "resources": {
                                "requests": {
                                    "storage": "10Gi"
                                }
                            },
                            "storageClassName": "gp2"
                        }
                    },
                    "storageDeviceSets": [
                        {
                            "count": 3,
                            "dataPVCTemplate": {
                                "spec": {
                                    "accessModes": [
                                        "ReadWriteOnce"
                                    ],
                                    "resources": {
                                        "requests": {
                                            "storage": "1Ti"
                                        }
                                    },
                                    "storageClassName": "gp2",
                                    "volumeMode": "Block"
                                }
                            },
                            "name": "example-deviceset",
                            "placement": {},
                            "portable": true,
                            "resources": {}
                        }
                    ]
                }
            }
    ...

5.6.11. 了解您的 API 服务

与 CRD 一样,您的 Operator 可使用两类 APIService:拥有的必需的

5.6.11.1. 拥有的 API 服务

当 CSV 拥有 API 服务时,它将负责描述为其提供支持的扩展 api-server 的部署及其提供的组/version/kind(GVK)。

API 服务由它提供的 group/version 唯一标识,并可以多次列出,以表示期望提供的不同类型。

表 5.16. 拥有的 API 服务字段
字段描述必需/可选

Group

API 服务提供的组,如 database.example.com

必填

Version

API 服务的版本,如 v1alpha1

必填

Kind

API 服务应提供的类型。

必填

名称

提供的 API 服务的复数名称。

必填

DeploymentName

由您的 CSV 定义的部署名称,对应您的 API 服务(对于拥有的 API 服务是必需的)。在 CSV 待定阶段,OLM Operator 会在您的 CSV InstallStrategy 中搜索具有匹配名称的 Deployment spec,如果未找到,则不会将 CSV 转换至安装就绪阶段。

必填

DisplayName

API 服务名称的人类可读版本,如 MongoDB Standalone

必填

描述

有关 Operator 如何使用此 API 服务的简短描述,或有关 API 服务提供的功能描述。

必填

资源

您的 API 服务拥有一类或多类 Kubernetes 对象。它们将在 resources 部分列出,用于告知用户他们可能需要排除故障的对象或如何连接至应用程序,如公开数据库的服务或 Ingress 规则。

建议仅列出对人重要的对象,而不必列出您编排的所有对象。例如,不要列出存储用户不会修改的内部状态的配置映射。

选填

SpecDescriptorsStatusDescriptorsActionDescriptors

与拥有的 CRD 基本相同。

选填

5.6.11.1.1. API 服务资源创建

Operator Lifecycle Manager (OLM) 负责为每个唯一拥有的 API 服务创建或替换服务及 API 服务资源:

  • Service pod 选择器从与 API 服务描述的 DeploymentName 字段匹配的 CSV 部署中复制。
  • 每次安装都会生成一个新的 CA 密钥/证书对,并且将 base64 编码的 CA 捆绑包嵌入到对应的 API 服务资源中。
5.6.11.1.2. API service serving 证书

每当安装拥有的 API 服务时,OLM 均会处理服务密钥/证书对的生成。服务证书有一个通用名称(CN),其中包含生成的 Service 资源的主机名,并由嵌入在对应 API 服务资源中的 CA 捆绑包的私钥签名。

该证书作为类型 kubernetes.io/tls secret 存储在部署命名空间中,名为 apiservice-cert 的卷会自动附加至 CSV 中与 API 服务描述的 DeploymentName 字段匹配的 volumes 部分中。

如果尚不存在,则具有匹配名称的卷挂载也会附加至该部署的所有容器中。这样用户便可使用预期名称来定义卷挂载,以适应任何自定义路径要求。所生成的卷挂载的默认路径为 /apiserver.local.config/certificates,具有相同路径的任何现有卷挂载都会被替换。

5.6.11.2. 所需的 API 服务

OLM 可保证所有必需的 CSV 均有可用的 API 服务,且所有预期的 GVK 在试图安装前均可发现。这允许 CSV 依赖于由它拥有的 API 服务提供的特定类型。

表 5.17. 所需的 API 服务字段
字段描述必需/可选

Group

API 服务提供的组,如 database.example.com

必填

Version

API 服务的版本,如 v1alpha1

必填

Kind

API 服务应提供的类型。

必填

DisplayName

API 服务名称的人类可读版本,如 MongoDB Standalone

必填

描述

有关 Operator 如何使用此 API 服务的简短描述,或有关 API 服务提供的功能描述。

必填

Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

© 2024 Red Hat, Inc.